Public/Sessions.ps1
|
function Get-CopilotSession { <# .SYNOPSIS Lists GitHub Copilot CLI sessions, optionally filtered to the current directory. .DESCRIPTION Enumerates session-state directories under ~/.copilot/session-state/ and parses each workspace.yaml to extract session metadata. Includes EventCount and EventSize for diagnosing oversized sessions. By default only sessions matching the current working directory are returned. Use -All to return every session regardless of directory. .PARAMETER All Return sessions for all directories, not just the current one. .PARAMETER Id Return only the session with the specified ID. .EXAMPLE Get-CopilotSession # Lists sessions for the current directory. .EXAMPLE Get-CopilotSession -All # Lists all sessions across every directory. .EXAMPLE Get-CopilotSession -Id "abc-123" # Returns the session with the given ID. #> [OutputType('CopilotSession')] [CmdletBinding(DefaultParameterSetName = 'Filter')] param( [Parameter(ParameterSetName = 'Filter')] [switch]$All, [Parameter(ParameterSetName = 'ById', Mandatory)] [ValidateNotNullOrEmpty()] [string]$Id ) $sessionStateDir = Join-Path $env:USERPROFILE '.copilot' 'session-state' if (-not (Test-Path $sessionStateDir)) { return } $cwd = (Get-Location).Path $dirs = if ($Id) { $target = Join-Path $sessionStateDir $Id if (Test-Path $target) { Get-Item $target } else { return } } else { Get-ChildItem $sessionStateDir -Directory } $dirs | ForEach-Object { $wsFile = Join-Path $_.FullName 'workspace.yaml' if (Test-Path $wsFile) { $content = Get-Content $wsFile -Raw $sessionCwd = if ($content -match '(?m)^cwd:\s*(.+)$') { $Matches[1].Trim() } $updatedAt = if ($content -match '(?m)^updated_at:\s*(.+)$') { $Matches[1].Trim() } $summary = if ($content -match '(?m)^summary:\s*(.+)$') { $Matches[1].Trim() } else { $null } $branch = if ($content -match '(?m)^branch:\s*(.+)$') { $Matches[1].Trim() } $repository = if ($content -match '(?m)^repository:\s*(.+)$') { $Matches[1].Trim() } $createdAt = if ($content -match '(?m)^created_at:\s*(.+)$') { $Matches[1].Trim() } # Parse name field, handling YAML block scalars (|- or >-) $name = if ($content -match '(?m)^name:\s*[\|>]-?\s*$') { if ($content -match '(?m)^name:\s*[\|>]-?\s*\r?\n(\s{2,}.+)') { $Matches[1].Trim() } } elseif ($content -match '(?m)^name:\s+(.+)$') { $Matches[1].Trim() } if ($All -or $Id -or $sessionCwd -eq $cwd) { $eventsFile = Join-Path $_.FullName 'events.jsonl' $eventCount = 0 $eventSize = [long]0 if (Test-Path $eventsFile) { $fi = [System.IO.FileInfo]::new($eventsFile) $eventSize = $fi.Length # Stream-count lines without allocating the full string array $reader = [System.IO.StreamReader]::new($fi.FullName) try { while ($null -ne $reader.ReadLine()) { $eventCount++ } } finally { $reader.Dispose() } } [PSCustomObject]@{ PSTypeName = 'CopilotSession' Id = $_.Name Name = $name ?? $summary ?? '(no summary)' Summary = $name ?? $summary ?? '(no summary)' Cwd = $sessionCwd Branch = $branch Repository = $repository CreatedAt = if ($createdAt) { [DateTimeOffset]::Parse($createdAt) } else { $null } UpdatedAt = if ($updatedAt) { [DateTimeOffset]::Parse($updatedAt) } else { $null } EventCount = $eventCount EventSize = $eventSize Path = $_.FullName } } } } | Sort-Object UpdatedAt -Descending } function Remove-CopilotSession { <# .SYNOPSIS Removes one or more GitHub Copilot CLI sessions. .DESCRIPTION Deletes session-state directories by ID or by pipeline input from Get-CopilotSession. Supports -WhatIf and -Confirm via ShouldProcess. .PARAMETER Id One or more session IDs to remove. .PARAMETER InputObject Session objects piped from Get-CopilotSession. .EXAMPLE Remove-CopilotSession -Id "abc-123" # Removes a single session by ID. .EXAMPLE Get-CopilotSession -All | Remove-CopilotSession # Removes all sessions. .EXAMPLE Get-CopilotSession | Remove-CopilotSession -WhatIf # Shows which sessions would be removed for the current directory. #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'ById')] param( [Parameter(ParameterSetName = 'ById', Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string[]]$Id, [Parameter(ParameterSetName = 'ByObject', Mandatory, ValueFromPipeline)] [PSObject]$InputObject ) process { $sessions = if ($PSCmdlet.ParameterSetName -eq 'ById') { $Id | ForEach-Object { Get-CopilotSession -Id $_ } } else { $InputObject } foreach ($s in $sessions) { if ($null -eq $s) { continue } if ($PSCmdlet.ShouldProcess("$($s.Summary) ($($s.Id))", 'Remove session')) { Remove-Item -LiteralPath $s.Path -Recurse -Force } } } } function Rename-CopilotSession { <# .SYNOPSIS Changes the display name of a Copilot CLI session. .DESCRIPTION Updates the name and/or summary field in workspace.yaml for the specified session. Handles both the newer 'name' field (including YAML block scalars) and the legacy 'summary' field. If both exist, both are updated. .PARAMETER Id The session ID to rename. .PARAMETER InputObject A session object piped from Get-CopilotSession. .PARAMETER Summary The new display name for the session. .EXAMPLE Rename-CopilotSession -Id "abc-123" -Name "Auth module refactor" # Sets the session name to "Auth module refactor". .EXAMPLE Get-CopilotSession | Where-Object Summary -eq '(no summary)' | Rename-CopilotSession -Name "Untitled session" # Renames all sessions with no name. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'ById')] param( [Parameter(ParameterSetName = 'ById', Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Id, [Parameter(ParameterSetName = 'ByObject', Mandatory, ValueFromPipeline)] [PSObject]$InputObject, [Parameter(Mandatory, Position = 1)] [ValidateNotNullOrEmpty()] [Alias('Name')] [string]$Summary ) process { $session = if ($PSCmdlet.ParameterSetName -eq 'ById') { Get-CopilotSession -Id $Id } else { $InputObject } if ($null -eq $session) { Write-Error 'Session not found.' return } $wsFile = Join-Path $session.Path 'workspace.yaml' if (-not (Test-Path $wsFile)) { Write-Error "workspace.yaml not found for session $($session.Id)." return } if (-not $PSCmdlet.ShouldProcess("$($session.Summary) ($($session.Id.Substring(0,8)))", "Rename to '$Summary'")) { return } $content = Get-Content $wsFile -Raw # Replace name field (handles block scalars like |- or >-) $hasName = $content -match '(?m)^name:' if ($hasName) { $yamlLines = $content -split '\r?\n' $newLines = [System.Collections.Generic.List[string]]::new() $skipBlock = $false foreach ($line in $yamlLines) { if ($line -match '^name:') { $newLines.Add("name: $Summary") $skipBlock = ($line -match ':\s*[\|>]') continue } if ($skipBlock -and ($line -match '^\s' -or $line -eq '')) { continue } $skipBlock = $false $newLines.Add($line) } $content = $newLines -join "`n" } # Also update summary field if present if ($content -match '(?m)^summary:') { $content = $content -replace '(?m)^summary:\s*.+$', "summary: $Summary" } [System.IO.File]::WriteAllText($wsFile, $content, [System.Text.UTF8Encoding]::new($false)) Write-Verbose "Renamed session $($session.Id.Substring(0,8)) to '$Summary'" Get-CopilotSession -Id $session.Id } } function Resume-CopilotSession { <# .SYNOPSIS Resumes a specific GitHub Copilot CLI session by ID or pipeline input. .DESCRIPTION Launches the Copilot CLI with --resume targeting the specified session. Accepts a session ID directly or a session object piped from Get-CopilotSession. .PARAMETER Id The session ID to resume. .PARAMETER InputObject A session object piped from Get-CopilotSession. .PARAMETER Prompt Optional prompt to execute in autopilot mode within the resumed session. .PARAMETER RemainingArgs Any additional arguments passed through to the copilot executable. .EXAMPLE Resume-CopilotSession -Id "abc-123" # Resumes the session interactively. .EXAMPLE Get-CopilotSession | Select-Object -First 1 | Resume-CopilotSession # Resumes the most recent session for the current directory. .EXAMPLE Resume-CopilotSession -Id "abc-123" -Prompt "Continue refactoring" # Resumes the session in autopilot mode with the given prompt. #> [CmdletBinding(DefaultParameterSetName = 'ById')] param( [Parameter(ParameterSetName = 'ById', Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Id, [Parameter(ParameterSetName = 'ByObject', Mandatory, ValueFromPipeline)] [PSObject]$InputObject, [Parameter(Position = 1)] [string]$Prompt, [Parameter(ValueFromRemainingArguments)] [string[]]$RemainingArgs ) process { $sessionId = if ($PSCmdlet.ParameterSetName -eq 'ById') { $Id } else { $InputObject.Id } $session = Get-CopilotSession -Id $sessionId if ($null -eq $session) { Write-Error "Session '$sessionId' not found." return } $copilotExe = (Get-Command copilot -CommandType Application -ErrorAction Stop | Select-Object -First 1).Source $copilotArgs = @('--allow-all', '--experimental', '--resume', $session.Id) Write-Verbose "Resuming session: $($session.Summary)" Write-Verbose " Last updated $($session.UpdatedAt.LocalDateTime)" if ($Prompt) { $copilotArgs += '--autopilot', '-p', $Prompt } if ($RemainingArgs) { $copilotArgs += $RemainingArgs } & $copilotExe @copilotArgs } } |