diff --git a/docs/guides/session-persistence.md b/docs/guides/session-persistence.md
index d1fb39e6..527f5ecc 100644
--- a/docs/guides/session-persistence.md
+++ b/docs/guides/session-persistence.md
@@ -293,12 +293,16 @@ session_id = create_session_id("alice", "code-review")
### Listing Active Sessions
```typescript
+// List all sessions
const sessions = await client.listSessions();
console.log(`Found ${sessions.length} sessions`);
for (const session of sessions) {
console.log(`- ${session.sessionId} (created: ${session.createdAt})`);
}
+
+// Filter sessions by repository
+const repoSessions = await client.listSessions({ repository: "owner/repo" });
```
### Cleaning Up Old Sessions
@@ -521,7 +525,7 @@ await withSessionLock("user-123-task-456", async () => {
| **Create resumable session** | Provide your own `sessionId` |
| **Resume session** | `client.resumeSession(sessionId)` |
| **BYOK resume** | Re-provide `provider` config |
-| **List sessions** | `client.listSessions()` |
+| **List sessions** | `client.listSessions(filter?)` |
| **Delete session** | `client.deleteSession(sessionId)` |
| **Destroy active session** | `session.destroy()` |
| **Containerized deployment** | Mount `~/.copilot/session-state/` to persistent storage |
diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs
index 74f1c66f..b0b014eb 100644
--- a/dotnet/src/Client.cs
+++ b/dotnet/src/Client.cs
@@ -652,6 +652,7 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
///
/// Lists all sessions known to the Copilot server.
///
+ /// Optional filter to narrow down the session list by cwd, git root, repository, or branch.
/// A that can be used to cancel the operation.
/// A task that resolves with a list of for all available sessions.
/// Thrown when the client is not connected.
@@ -664,12 +665,12 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
/// }
///
///
- public async Task> ListSessionsAsync(CancellationToken cancellationToken = default)
+ public async Task> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default)
{
var connection = await EnsureConnectedAsync(cancellationToken);
var response = await InvokeRpcAsync(
- connection.Rpc, "session.list", [], cancellationToken);
+ connection.Rpc, "session.list", [new ListSessionsRequest(filter)], cancellationToken);
return response.Sessions;
}
@@ -1369,6 +1370,9 @@ internal record DeleteSessionResponse(
bool Success,
string? Error);
+ internal record ListSessionsRequest(
+ SessionListFilter? Filter);
+
internal record ListSessionsResponse(
List Sessions);
@@ -1438,6 +1442,7 @@ public override void WriteLine(string? message) =>
[JsonSerializable(typeof(DeleteSessionResponse))]
[JsonSerializable(typeof(GetLastSessionIdResponse))]
[JsonSerializable(typeof(HooksInvokeResponse))]
+ [JsonSerializable(typeof(ListSessionsRequest))]
[JsonSerializable(typeof(ListSessionsResponse))]
[JsonSerializable(typeof(PermissionRequestResponse))]
[JsonSerializable(typeof(PermissionRequestResult))]
diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs
index 02258839..05c71a5d 100644
--- a/dotnet/src/Generated/SessionEvents.cs
+++ b/dotnet/src/Generated/SessionEvents.cs
@@ -6,7 +6,7 @@
//
// Generated from: @github/copilot/session-events.schema.json
// Generated by: scripts/generate-session-types.ts
-// Generated at: 2026-02-06T20:38:23.832Z
+// Generated at: 2026-02-12T22:32:12.047Z
//
// To update these types:
// 1. Update the schema in copilot-agent-runtime
@@ -37,6 +37,7 @@ namespace GitHub.Copilot.SDK;
[JsonDerivedType(typeof(PendingMessagesModifiedEvent), "pending_messages.modified")]
[JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")]
[JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")]
+[JsonDerivedType(typeof(SessionContextChangedEvent), "session.context_changed")]
[JsonDerivedType(typeof(SessionErrorEvent), "session.error")]
[JsonDerivedType(typeof(SessionHandoffEvent), "session.handoff")]
[JsonDerivedType(typeof(SessionIdleEvent), "session.idle")]
@@ -46,8 +47,10 @@ namespace GitHub.Copilot.SDK;
[JsonDerivedType(typeof(SessionShutdownEvent), "session.shutdown")]
[JsonDerivedType(typeof(SessionSnapshotRewindEvent), "session.snapshot_rewind")]
[JsonDerivedType(typeof(SessionStartEvent), "session.start")]
+[JsonDerivedType(typeof(SessionTitleChangedEvent), "session.title_changed")]
[JsonDerivedType(typeof(SessionTruncationEvent), "session.truncation")]
[JsonDerivedType(typeof(SessionUsageInfoEvent), "session.usage_info")]
+[JsonDerivedType(typeof(SessionWarningEvent), "session.warning")]
[JsonDerivedType(typeof(SkillInvokedEvent), "skill.invoked")]
[JsonDerivedType(typeof(SubagentCompletedEvent), "subagent.completed")]
[JsonDerivedType(typeof(SubagentFailedEvent), "subagent.failed")]
@@ -136,6 +139,18 @@ public partial class SessionIdleEvent : SessionEvent
public required SessionIdleData Data { get; set; }
}
+///
+/// Event: session.title_changed
+///
+public partial class SessionTitleChangedEvent : SessionEvent
+{
+ [JsonIgnore]
+ public override string Type => "session.title_changed";
+
+ [JsonPropertyName("data")]
+ public required SessionTitleChangedData Data { get; set; }
+}
+
///
/// Event: session.info
///
@@ -148,6 +163,18 @@ public partial class SessionInfoEvent : SessionEvent
public required SessionInfoData Data { get; set; }
}
+///
+/// Event: session.warning
+///
+public partial class SessionWarningEvent : SessionEvent
+{
+ [JsonIgnore]
+ public override string Type => "session.warning";
+
+ [JsonPropertyName("data")]
+ public required SessionWarningData Data { get; set; }
+}
+
///
/// Event: session.model_change
///
@@ -208,6 +235,18 @@ public partial class SessionShutdownEvent : SessionEvent
public required SessionShutdownData Data { get; set; }
}
+///
+/// Event: session.context_changed
+///
+public partial class SessionContextChangedEvent : SessionEvent
+{
+ [JsonIgnore]
+ public override string Type => "session.context_changed";
+
+ [JsonPropertyName("data")]
+ public required SessionContextChangedData Data { get; set; }
+}
+
///
/// Event: session.usage_info
///
@@ -596,6 +635,12 @@ public partial class SessionIdleData
{
}
+public partial class SessionTitleChangedData
+{
+ [JsonPropertyName("title")]
+ public required string Title { get; set; }
+}
+
public partial class SessionInfoData
{
[JsonPropertyName("infoType")]
@@ -605,6 +650,15 @@ public partial class SessionInfoData
public required string Message { get; set; }
}
+public partial class SessionWarningData
+{
+ [JsonPropertyName("warningType")]
+ public required string WarningType { get; set; }
+
+ [JsonPropertyName("message")]
+ public required string Message { get; set; }
+}
+
public partial class SessionModelChangeData
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@@ -705,6 +759,24 @@ public partial class SessionShutdownData
public string? CurrentModel { get; set; }
}
+public partial class SessionContextChangedData
+{
+ [JsonPropertyName("cwd")]
+ public required string Cwd { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("gitRoot")]
+ public string? GitRoot { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("repository")]
+ public string? Repository { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("branch")]
+ public string? Branch { get; set; }
+}
+
public partial class SessionUsageInfoData
{
[JsonPropertyName("tokenLimit")]
@@ -787,6 +859,10 @@ public partial class UserMessageData
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("source")]
public string? Source { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("agentMode")]
+ public UserMessageDataAgentMode? AgentMode { get; set; }
}
public partial class PendingMessagesModifiedData
@@ -847,6 +923,10 @@ public partial class AssistantMessageData
[JsonPropertyName("encryptedContent")]
public string? EncryptedContent { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("phase")]
+ public string? Phase { get; set; }
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("parentToolCallId")]
public string? ParentToolCallId { get; set; }
@@ -1054,6 +1134,9 @@ public partial class SubagentCompletedData
[JsonPropertyName("agentName")]
public required string AgentName { get; set; }
+
+ [JsonPropertyName("agentDisplayName")]
+ public required string AgentDisplayName { get; set; }
}
public partial class SubagentFailedData
@@ -1064,6 +1147,9 @@ public partial class SubagentFailedData
[JsonPropertyName("agentName")]
public required string AgentName { get; set; }
+ [JsonPropertyName("agentDisplayName")]
+ public required string AgentDisplayName { get; set; }
+
[JsonPropertyName("error")]
public required string Error { get; set; }
}
@@ -1203,6 +1289,15 @@ public partial class SessionCompactionCompleteDataCompactionTokensUsed
public required double CachedInput { get; set; }
}
+public partial class UserMessageDataAttachmentsItemFileLineRange
+{
+ [JsonPropertyName("start")]
+ public required double Start { get; set; }
+
+ [JsonPropertyName("end")]
+ public required double End { get; set; }
+}
+
public partial class UserMessageDataAttachmentsItemFile : UserMessageDataAttachmentsItem
{
[JsonIgnore]
@@ -1213,6 +1308,19 @@ public partial class UserMessageDataAttachmentsItemFile : UserMessageDataAttachm
[JsonPropertyName("displayName")]
public required string DisplayName { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("lineRange")]
+ public UserMessageDataAttachmentsItemFileLineRange? LineRange { get; set; }
+}
+
+public partial class UserMessageDataAttachmentsItemDirectoryLineRange
+{
+ [JsonPropertyName("start")]
+ public required double Start { get; set; }
+
+ [JsonPropertyName("end")]
+ public required double End { get; set; }
}
public partial class UserMessageDataAttachmentsItemDirectory : UserMessageDataAttachmentsItem
@@ -1225,6 +1333,10 @@ public partial class UserMessageDataAttachmentsItemDirectory : UserMessageDataAt
[JsonPropertyName("displayName")]
public required string DisplayName { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("lineRange")]
+ public UserMessageDataAttachmentsItemDirectoryLineRange? LineRange { get; set; }
}
public partial class UserMessageDataAttachmentsItemSelectionSelectionStart
@@ -1302,6 +1414,131 @@ public partial class AssistantMessageDataToolRequestsItem
public AssistantMessageDataToolRequestsItemType? Type { get; set; }
}
+public partial class ToolExecutionCompleteDataResultContentsItemText : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "text";
+
+ [JsonPropertyName("text")]
+ public required string Text { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemTerminal : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "terminal";
+
+ [JsonPropertyName("text")]
+ public required string Text { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("exitCode")]
+ public double? ExitCode { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("cwd")]
+ public string? Cwd { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemImage : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "image";
+
+ [JsonPropertyName("data")]
+ public required string Data { get; set; }
+
+ [JsonPropertyName("mimeType")]
+ public required string MimeType { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemAudio : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "audio";
+
+ [JsonPropertyName("data")]
+ public required string Data { get; set; }
+
+ [JsonPropertyName("mimeType")]
+ public required string MimeType { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem
+{
+ [JsonPropertyName("src")]
+ public required string Src { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("mimeType")]
+ public string? MimeType { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("sizes")]
+ public string[]? Sizes { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("theme")]
+ public ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme? Theme { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemResourceLink : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "resource_link";
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("icons")]
+ public ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem[]? Icons { get; set; }
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
+
+ [JsonPropertyName("uri")]
+ public required string Uri { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("mimeType")]
+ public string? MimeType { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("size")]
+ public double? Size { get; set; }
+}
+
+public partial class ToolExecutionCompleteDataResultContentsItemResource : ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonIgnore]
+ public override string Type => "resource";
+
+ [JsonPropertyName("resource")]
+ public required object Resource { get; set; }
+}
+
+[JsonPolymorphic(
+ TypeDiscriminatorPropertyName = "type",
+ UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemText), "text")]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemTerminal), "terminal")]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemImage), "image")]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemAudio), "audio")]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemResourceLink), "resource_link")]
+[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemResource), "resource")]
+public partial class ToolExecutionCompleteDataResultContentsItem
+{
+ [JsonPropertyName("type")]
+ public virtual string Type { get; set; } = string.Empty;
+}
+
+
public partial class ToolExecutionCompleteDataResult
{
[JsonPropertyName("content")]
@@ -1310,6 +1547,10 @@ public partial class ToolExecutionCompleteDataResult
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("detailedContent")]
public string? DetailedContent { get; set; }
+
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("contents")]
+ public ToolExecutionCompleteDataResultContentsItem[]? Contents { get; set; }
}
public partial class ToolExecutionCompleteDataError
@@ -1361,6 +1602,19 @@ public enum SessionShutdownDataShutdownType
Error,
}
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum UserMessageDataAgentMode
+{
+ [JsonStringEnumMemberName("interactive")]
+ Interactive,
+ [JsonStringEnumMemberName("plan")]
+ Plan,
+ [JsonStringEnumMemberName("autopilot")]
+ Autopilot,
+ [JsonStringEnumMemberName("shell")]
+ Shell,
+}
+
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum AssistantMessageDataToolRequestsItemType
{
@@ -1370,6 +1624,15 @@ public enum AssistantMessageDataToolRequestsItemType
Custom,
}
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme
+{
+ [JsonStringEnumMemberName("light")]
+ Light,
+ [JsonStringEnumMemberName("dark")]
+ Dark,
+}
+
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SystemMessageDataRole
{
@@ -1415,6 +1678,8 @@ public enum SystemMessageDataRole
[JsonSerializable(typeof(SessionCompactionCompleteEvent))]
[JsonSerializable(typeof(SessionCompactionStartData))]
[JsonSerializable(typeof(SessionCompactionStartEvent))]
+[JsonSerializable(typeof(SessionContextChangedData))]
+[JsonSerializable(typeof(SessionContextChangedEvent))]
[JsonSerializable(typeof(SessionErrorData))]
[JsonSerializable(typeof(SessionErrorEvent))]
[JsonSerializable(typeof(SessionEvent))]
@@ -1438,10 +1703,14 @@ public enum SystemMessageDataRole
[JsonSerializable(typeof(SessionStartData))]
[JsonSerializable(typeof(SessionStartDataContext))]
[JsonSerializable(typeof(SessionStartEvent))]
+[JsonSerializable(typeof(SessionTitleChangedData))]
+[JsonSerializable(typeof(SessionTitleChangedEvent))]
[JsonSerializable(typeof(SessionTruncationData))]
[JsonSerializable(typeof(SessionTruncationEvent))]
[JsonSerializable(typeof(SessionUsageInfoData))]
[JsonSerializable(typeof(SessionUsageInfoEvent))]
+[JsonSerializable(typeof(SessionWarningData))]
+[JsonSerializable(typeof(SessionWarningEvent))]
[JsonSerializable(typeof(SkillInvokedData))]
[JsonSerializable(typeof(SkillInvokedEvent))]
[JsonSerializable(typeof(SubagentCompletedData))]
@@ -1458,6 +1727,14 @@ public enum SystemMessageDataRole
[JsonSerializable(typeof(ToolExecutionCompleteData))]
[JsonSerializable(typeof(ToolExecutionCompleteDataError))]
[JsonSerializable(typeof(ToolExecutionCompleteDataResult))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItem))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemAudio))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemImage))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResource))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResourceLink))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemTerminal))]
+[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemText))]
[JsonSerializable(typeof(ToolExecutionCompleteEvent))]
[JsonSerializable(typeof(ToolExecutionPartialResultData))]
[JsonSerializable(typeof(ToolExecutionPartialResultEvent))]
@@ -1470,7 +1747,9 @@ public enum SystemMessageDataRole
[JsonSerializable(typeof(UserMessageData))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItem))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemDirectory))]
+[JsonSerializable(typeof(UserMessageDataAttachmentsItemDirectoryLineRange))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemFile))]
+[JsonSerializable(typeof(UserMessageDataAttachmentsItemFileLineRange))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelection))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelection))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionEnd))]
diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs
index 664b35d9..c5fa5fd3 100644
--- a/dotnet/src/Types.cs
+++ b/dotnet/src/Types.cs
@@ -881,6 +881,36 @@ public class MessageOptions
public delegate void SessionEventHandler(SessionEvent sessionEvent);
+///
+/// Working directory context for a session.
+///
+public class SessionContext
+{
+ /// Working directory where the session was created.
+ public string Cwd { get; set; } = string.Empty;
+ /// Git repository root (if in a git repo).
+ public string? GitRoot { get; set; }
+ /// GitHub repository in "owner/repo" format.
+ public string? Repository { get; set; }
+ /// Current git branch.
+ public string? Branch { get; set; }
+}
+
+///
+/// Filter options for listing sessions.
+///
+public class SessionListFilter
+{
+ /// Filter by exact cwd match.
+ public string? Cwd { get; set; }
+ /// Filter by git root.
+ public string? GitRoot { get; set; }
+ /// Filter by repository (owner/repo format).
+ public string? Repository { get; set; }
+ /// Filter by branch.
+ public string? Branch { get; set; }
+}
+
public class SessionMetadata
{
public string SessionId { get; set; } = string.Empty;
@@ -888,6 +918,8 @@ public class SessionMetadata
public DateTime ModifiedTime { get; set; }
public string? Summary { get; set; }
public bool IsRemote { get; set; }
+ /// Working directory context (cwd, git info) from session creation.
+ public SessionContext? Context { get; set; }
}
internal class PingRequest
@@ -1159,8 +1191,10 @@ public class SetForegroundSessionResponse
[JsonSerializable(typeof(PingRequest))]
[JsonSerializable(typeof(PingResponse))]
[JsonSerializable(typeof(ProviderConfig))]
+[JsonSerializable(typeof(SessionContext))]
[JsonSerializable(typeof(SessionLifecycleEvent))]
[JsonSerializable(typeof(SessionLifecycleEventMetadata))]
+[JsonSerializable(typeof(SessionListFilter))]
[JsonSerializable(typeof(SessionMetadata))]
[JsonSerializable(typeof(SetForegroundSessionResponse))]
[JsonSerializable(typeof(SystemMessageConfig))]
diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs
index 13b23522..920ee67d 100644
--- a/dotnet/test/SessionTests.cs
+++ b/dotnet/test/SessionTests.cs
@@ -369,6 +369,25 @@ public async Task SendAndWait_Blocks_Until_Session_Idle_And_Returns_Final_Assist
Assert.Contains("assistant.message", events);
}
+ // TODO: Re-enable once test harness CAPI proxy supports this test's session lifecycle
+ [Fact(Skip = "Needs test harness CAPI proxy support")]
+ public async Task Should_List_Sessions_With_Context()
+ {
+ var session = await Client.CreateSessionAsync();
+
+ var sessions = await Client.ListSessionsAsync();
+ Assert.NotEmpty(sessions);
+
+ var ourSession = sessions.Find(s => s.SessionId == session.SessionId);
+ Assert.NotNull(ourSession);
+
+ // Context may be present on sessions that have been persisted with workspace.yaml
+ if (ourSession.Context != null)
+ {
+ Assert.False(string.IsNullOrEmpty(ourSession.Context.Cwd), "Expected context.Cwd to be non-empty when context is present");
+ }
+ }
+
[Fact]
public async Task SendAndWait_Throws_On_Timeout()
{
diff --git a/go/README.md b/go/README.md
index 58207101..9f1ec393 100644
--- a/go/README.md
+++ b/go/README.md
@@ -80,7 +80,7 @@ func main() {
- `CreateSession(config *SessionConfig) (*Session, error)` - Create a new session
- `ResumeSession(sessionID string) (*Session, error)` - Resume an existing session
- `ResumeSessionWithOptions(sessionID string, config *ResumeSessionConfig) (*Session, error)` - Resume with additional configuration
-- `ListSessions() ([]SessionMetadata, error)` - List all sessions known to the server
+- `ListSessions(filter *SessionListFilter) ([]SessionMetadata, error)` - List sessions (with optional filter)
- `DeleteSession(sessionID string) error` - Delete a session permanently
- `GetState() ConnectionState` - Get connection state
- `Ping(message string) (*PingResponse, error)` - Ping the server
diff --git a/go/client.go b/go/client.go
index 319c6588..616808ea 100644
--- a/go/client.go
+++ b/go/client.go
@@ -609,23 +609,33 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
// ListSessions returns metadata about all sessions known to the server.
//
// Returns a list of SessionMetadata for all available sessions, including their IDs,
-// timestamps, and optional summaries.
+// timestamps, optional summaries, and context information.
+//
+// An optional filter can be provided to filter sessions by cwd, git root, repository, or branch.
//
// Example:
//
-// sessions, err := client.ListSessions(context.Background())
+// sessions, err := client.ListSessions(context.Background(), nil)
// if err != nil {
// log.Fatal(err)
// }
// for _, session := range sessions {
// fmt.Printf("Session: %s\n", session.SessionID)
// }
-func (c *Client) ListSessions(ctx context.Context) ([]SessionMetadata, error) {
+//
+// Example with filter:
+//
+// sessions, err := client.ListSessions(context.Background(), &SessionListFilter{Repository: "owner/repo"})
+func (c *Client) ListSessions(ctx context.Context, filter *SessionListFilter) ([]SessionMetadata, error) {
if err := c.ensureConnected(); err != nil {
return nil, err
}
- result, err := c.client.Request("session.list", listSessionsRequest{})
+ params := listSessionsRequest{}
+ if filter != nil {
+ params.Filter = filter
+ }
+ result, err := c.client.Request("session.list", params)
if err != nil {
return nil, err
}
diff --git a/go/generated_session_events.go b/go/generated_session_events.go
index ec4de9be..74e8b0ed 100644
--- a/go/generated_session_events.go
+++ b/go/generated_session_events.go
@@ -2,7 +2,7 @@
//
// Generated from: @github/copilot/session-events.schema.json
// Generated by: scripts/generate-session-types.ts
-// Generated at: 2026-02-06T20:38:23.463Z
+// Generated at: 2026-02-12T22:32:11.694Z
//
// To update these types:
// 1. Update the schema in copilot-agent-runtime
@@ -56,12 +56,14 @@ type Data struct {
ProviderCallID *string `json:"providerCallId,omitempty"`
Stack *string `json:"stack,omitempty"`
StatusCode *int64 `json:"statusCode,omitempty"`
+ Title *string `json:"title,omitempty"`
InfoType *string `json:"infoType,omitempty"`
+ WarningType *string `json:"warningType,omitempty"`
NewModel *string `json:"newModel,omitempty"`
PreviousModel *string `json:"previousModel,omitempty"`
HandoffTime *time.Time `json:"handoffTime,omitempty"`
RemoteSessionID *string `json:"remoteSessionId,omitempty"`
- Repository *Repository `json:"repository,omitempty"`
+ Repository *RepositoryUnion `json:"repository"`
SourceType *SourceType `json:"sourceType,omitempty"`
Summary *string `json:"summary,omitempty"`
MessagesRemovedDuringTruncation *float64 `json:"messagesRemovedDuringTruncation,omitempty"`
@@ -82,6 +84,9 @@ type Data struct {
ShutdownType *ShutdownType `json:"shutdownType,omitempty"`
TotalAPIDurationMS *float64 `json:"totalApiDurationMs,omitempty"`
TotalPremiumRequests *float64 `json:"totalPremiumRequests,omitempty"`
+ Branch *string `json:"branch,omitempty"`
+ Cwd *string `json:"cwd,omitempty"`
+ GitRoot *string `json:"gitRoot,omitempty"`
CurrentTokens *float64 `json:"currentTokens,omitempty"`
MessagesLength *float64 `json:"messagesLength,omitempty"`
CheckpointNumber *float64 `json:"checkpointNumber,omitempty"`
@@ -96,6 +101,7 @@ type Data struct {
Success *bool `json:"success,omitempty"`
SummaryContent *string `json:"summaryContent,omitempty"`
TokensRemoved *float64 `json:"tokensRemoved,omitempty"`
+ AgentMode *AgentMode `json:"agentMode,omitempty"`
Attachments []Attachment `json:"attachments,omitempty"`
Content *string `json:"content,omitempty"`
Source *string `json:"source,omitempty"`
@@ -107,6 +113,7 @@ type Data struct {
EncryptedContent *string `json:"encryptedContent,omitempty"`
MessageID *string `json:"messageId,omitempty"`
ParentToolCallID *string `json:"parentToolCallId,omitempty"`
+ Phase *string `json:"phase,omitempty"`
ReasoningOpaque *string `json:"reasoningOpaque,omitempty"`
ReasoningText *string `json:"reasoningText,omitempty"`
ToolRequests []ToolRequest `json:"toolRequests,omitempty"`
@@ -149,6 +156,7 @@ type Data struct {
type Attachment struct {
DisplayName string `json:"displayName"`
+ LineRange *LineRange `json:"lineRange,omitempty"`
Path *string `json:"path,omitempty"`
Type AttachmentType `json:"type"`
FilePath *string `json:"filePath,omitempty"`
@@ -156,6 +164,11 @@ type Attachment struct {
Text *string `json:"text,omitempty"`
}
+type LineRange struct {
+ End float64 `json:"end"`
+ Start float64 `json:"start"`
+}
+
type SelectionClass struct {
End End `json:"end"`
Start Start `json:"start"`
@@ -229,15 +242,46 @@ type QuotaSnapshot struct {
UsedRequests float64 `json:"usedRequests"`
}
-type Repository struct {
+type RepositoryClass struct {
Branch *string `json:"branch,omitempty"`
Name string `json:"name"`
Owner string `json:"owner"`
}
type Result struct {
- Content string `json:"content"`
- DetailedContent *string `json:"detailedContent,omitempty"`
+ Content string `json:"content"`
+ Contents []Content `json:"contents,omitempty"`
+ DetailedContent *string `json:"detailedContent,omitempty"`
+}
+
+type Content struct {
+ Text *string `json:"text,omitempty"`
+ Type ContentType `json:"type"`
+ Cwd *string `json:"cwd,omitempty"`
+ ExitCode *float64 `json:"exitCode,omitempty"`
+ Data *string `json:"data,omitempty"`
+ MIMEType *string `json:"mimeType,omitempty"`
+ Description *string `json:"description,omitempty"`
+ Icons []Icon `json:"icons,omitempty"`
+ Name *string `json:"name,omitempty"`
+ Size *float64 `json:"size,omitempty"`
+ Title *string `json:"title,omitempty"`
+ URI *string `json:"uri,omitempty"`
+ Resource *ResourceClass `json:"resource,omitempty"`
+}
+
+type Icon struct {
+ MIMEType *string `json:"mimeType,omitempty"`
+ Sizes []string `json:"sizes,omitempty"`
+ Src string `json:"src"`
+ Theme *Theme `json:"theme,omitempty"`
+}
+
+type ResourceClass struct {
+ MIMEType *string `json:"mimeType,omitempty"`
+ Text *string `json:"text,omitempty"`
+ URI string `json:"uri"`
+ Blob *string `json:"blob,omitempty"`
}
type ToolRequest struct {
@@ -247,6 +291,15 @@ type ToolRequest struct {
Type *ToolRequestType `json:"type,omitempty"`
}
+type AgentMode string
+
+const (
+ Autopilot AgentMode = "autopilot"
+ Interactive AgentMode = "interactive"
+ Plan AgentMode = "plan"
+ Shell AgentMode = "shell"
+)
+
type AttachmentType string
const (
@@ -255,6 +308,24 @@ const (
Selection AttachmentType = "selection"
)
+type Theme string
+
+const (
+ Dark Theme = "dark"
+ Light Theme = "light"
+)
+
+type ContentType string
+
+const (
+ Audio ContentType = "audio"
+ Image ContentType = "image"
+ Resource ContentType = "resource"
+ ResourceLink ContentType = "resource_link"
+ Terminal ContentType = "terminal"
+ Text ContentType = "text"
+)
+
type Role string
const (
@@ -300,6 +371,7 @@ const (
PendingMessagesModified SessionEventType = "pending_messages.modified"
SessionCompactionComplete SessionEventType = "session.compaction_complete"
SessionCompactionStart SessionEventType = "session.compaction_start"
+ SessionContextChanged SessionEventType = "session.context_changed"
SessionError SessionEventType = "session.error"
SessionHandoff SessionEventType = "session.handoff"
SessionIdle SessionEventType = "session.idle"
@@ -309,8 +381,10 @@ const (
SessionShutdown SessionEventType = "session.shutdown"
SessionSnapshotRewind SessionEventType = "session.snapshot_rewind"
SessionStart SessionEventType = "session.start"
+ SessionTitleChanged SessionEventType = "session.title_changed"
SessionTruncation SessionEventType = "session.truncation"
SessionUsageInfo SessionEventType = "session.usage_info"
+ SessionWarning SessionEventType = "session.warning"
SkillInvoked SessionEventType = "skill.invoked"
SubagentCompleted SessionEventType = "subagent.completed"
SubagentFailed SessionEventType = "subagent.failed"
@@ -369,6 +443,28 @@ func (x *ErrorUnion) MarshalJSON() ([]byte, error) {
return marshalUnion(nil, nil, nil, x.String, false, nil, x.ErrorClass != nil, x.ErrorClass, false, nil, false, nil, false)
}
+type RepositoryUnion struct {
+ RepositoryClass *RepositoryClass
+ String *string
+}
+
+func (x *RepositoryUnion) UnmarshalJSON(data []byte) error {
+ x.RepositoryClass = nil
+ var c RepositoryClass
+ object, err := unmarshalUnion(data, nil, nil, nil, &x.String, false, nil, true, &c, false, nil, false, nil, false)
+ if err != nil {
+ return err
+ }
+ if object {
+ x.RepositoryClass = &c
+ }
+ return nil
+}
+
+func (x *RepositoryUnion) MarshalJSON() ([]byte, error) {
+ return marshalUnion(nil, nil, nil, x.String, false, nil, x.RepositoryClass != nil, x.RepositoryClass, false, nil, false, nil, false)
+}
+
func unmarshalUnion(data []byte, pi **int64, pf **float64, pb **bool, ps **string, haveArray bool, pa interface{}, haveObject bool, pc interface{}, haveMap bool, pm interface{}, haveEnum bool, pe interface{}, nullable bool) (bool, error) {
if pi != nil {
*pi = nil
diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go
index 62183286..6a98da60 100644
--- a/go/internal/e2e/session_test.go
+++ b/go/internal/e2e/session_test.go
@@ -775,7 +775,7 @@ func TestSession(t *testing.T) {
time.Sleep(200 * time.Millisecond)
// List sessions and verify they're included
- sessions, err := client.ListSessions(t.Context())
+ sessions, err := client.ListSessions(t.Context(), nil)
if err != nil {
t.Fatalf("Failed to list sessions: %v", err)
}
@@ -812,6 +812,15 @@ func TestSession(t *testing.T) {
}
// isRemote is a boolean, so it's always set
}
+
+ // Verify context field is present on sessions
+ for _, s := range sessions {
+ if s.Context != nil {
+ if s.Context.Cwd == "" {
+ t.Error("Expected context.Cwd to be non-empty when context is present")
+ }
+ }
+ }
})
t.Run("should delete session", func(t *testing.T) {
@@ -834,7 +843,7 @@ func TestSession(t *testing.T) {
time.Sleep(200 * time.Millisecond)
// Verify session exists in the list
- sessions, err := client.ListSessions(t.Context())
+ sessions, err := client.ListSessions(t.Context(), nil)
if err != nil {
t.Fatalf("Failed to list sessions: %v", err)
}
@@ -855,7 +864,7 @@ func TestSession(t *testing.T) {
}
// Verify session no longer exists in the list
- sessionsAfter, err := client.ListSessions(t.Context())
+ sessionsAfter, err := client.ListSessions(t.Context(), nil)
if err != nil {
t.Fatalf("Failed to list sessions after delete: %v", err)
}
diff --git a/go/types.go b/go/types.go
index a3b38ee3..2f972450 100644
--- a/go/types.go
+++ b/go/types.go
@@ -541,13 +541,38 @@ type ModelInfo struct {
DefaultReasoningEffort string `json:"defaultReasoningEffort,omitempty"`
}
+// SessionContext contains working directory context for a session
+type SessionContext struct {
+ // Cwd is the working directory where the session was created
+ Cwd string `json:"cwd"`
+ // GitRoot is the git repository root (if in a git repo)
+ GitRoot string `json:"gitRoot,omitempty"`
+ // Repository is the GitHub repository in "owner/repo" format
+ Repository string `json:"repository,omitempty"`
+ // Branch is the current git branch
+ Branch string `json:"branch,omitempty"`
+}
+
+// SessionListFilter contains filter options for listing sessions
+type SessionListFilter struct {
+ // Cwd filters by exact working directory match
+ Cwd string `json:"cwd,omitempty"`
+ // GitRoot filters by git root
+ GitRoot string `json:"gitRoot,omitempty"`
+ // Repository filters by repository (owner/repo format)
+ Repository string `json:"repository,omitempty"`
+ // Branch filters by branch
+ Branch string `json:"branch,omitempty"`
+}
+
// SessionMetadata contains metadata about a session
type SessionMetadata struct {
- SessionID string `json:"sessionId"`
- StartTime string `json:"startTime"`
- ModifiedTime string `json:"modifiedTime"`
- Summary *string `json:"summary,omitempty"`
- IsRemote bool `json:"isRemote"`
+ SessionID string `json:"sessionId"`
+ StartTime string `json:"startTime"`
+ ModifiedTime string `json:"modifiedTime"`
+ Summary *string `json:"summary,omitempty"`
+ IsRemote bool `json:"isRemote"`
+ Context *SessionContext `json:"context,omitempty"`
}
// SessionLifecycleEventType represents the type of session lifecycle event
@@ -655,7 +680,9 @@ type hooksInvokeRequest struct {
}
// listSessionsRequest is the request for session.list
-type listSessionsRequest struct{}
+type listSessionsRequest struct {
+ Filter *SessionListFilter `json:"filter,omitempty"`
+}
// listSessionsResponse is the response from session.list
type listSessionsResponse struct {
diff --git a/nodejs/README.md b/nodejs/README.md
index 3a78f419..ed0d897c 100644
--- a/nodejs/README.md
+++ b/nodejs/README.md
@@ -108,9 +108,25 @@ Ping the server to check connectivity.
Get current connection state.
-##### `listSessions(): Promise`
+##### `listSessions(filter?: SessionListFilter): Promise`
-List all available sessions.
+List all available sessions. Optionally filter by working directory context.
+
+**SessionMetadata:**
+
+- `sessionId: string` - Unique session identifier
+- `startTime: Date` - When the session was created
+- `modifiedTime: Date` - When the session was last modified
+- `summary?: string` - Optional session summary
+- `isRemote: boolean` - Whether the session is remote
+- `context?: SessionContext` - Working directory context from session creation
+
+**SessionContext:**
+
+- `cwd: string` - Working directory where the session was created
+- `gitRoot?: string` - Git repository root (if in a git repo)
+- `repository?: string` - GitHub repository in "owner/repo" format
+- `branch?: string` - Current git branch
##### `deleteSession(sessionId: string): Promise`
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index 266d994e..fb3a5f91 100644
--- a/nodejs/package-lock.json
+++ b/nodejs/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.1.8",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^0.0.405",
+ "@github/copilot": "^0.0.409",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
@@ -662,26 +662,26 @@
}
},
"node_modules/@github/copilot": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.405.tgz",
- "integrity": "sha512-zp0kGSkoKrO4MTWefAxU5w2VEc02QnhPY3FmVxOeduh6ayDIz2V368mXxs46ThremdMnMyZPL1k989BW4NpOVw==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.409.tgz",
+ "integrity": "sha512-rkYWOKjTSuGg99KsgmA0QAP4X2cpJzAYk6lZDlVxKPhuLP03wC5E+jLctrSLjpxhX32p9n13rm1+7Jun80a1hw==",
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "0.0.405",
- "@github/copilot-darwin-x64": "0.0.405",
- "@github/copilot-linux-arm64": "0.0.405",
- "@github/copilot-linux-x64": "0.0.405",
- "@github/copilot-win32-arm64": "0.0.405",
- "@github/copilot-win32-x64": "0.0.405"
+ "@github/copilot-darwin-arm64": "0.0.409",
+ "@github/copilot-darwin-x64": "0.0.409",
+ "@github/copilot-linux-arm64": "0.0.409",
+ "@github/copilot-linux-x64": "0.0.409",
+ "@github/copilot-win32-arm64": "0.0.409",
+ "@github/copilot-win32-x64": "0.0.409"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.405.tgz",
- "integrity": "sha512-RVFpU1cEMqjR0rLpwLwbIfT7XzqqVoQX99G6nsj+WrHu3TIeCgfffyd2YShd4QwZYsMRoTfKB+rirQ+0G5Uiig==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.409.tgz",
+ "integrity": "sha512-yjrrp++UNNvRoWsZ1+UioBqb3DEVxL5M5ePnMO5/Sf1sngxh0y5P9P6ePFZU4PVlM5BgC38DtrcauZaKf/oArQ==",
"cpu": [
"arm64"
],
@@ -695,9 +695,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.405.tgz",
- "integrity": "sha512-Xj2FyPzpZlfqPTuMrXtPNEijSmm2ivHvyMWgy5Ijv7Slabxe+2s3WXDaokE3SQHodK6M0Yle2yrx9kxiwWA+qw==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.409.tgz",
+ "integrity": "sha512-EhLfY5DGU/BZmwjVcfnwKuJA7BxS9zdNCGeynUq7z/SI93ziastFqOddUX4D+ySz6yMrrXieN8cUKgzAlRCOJg==",
"cpu": [
"x64"
],
@@ -711,9 +711,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.405.tgz",
- "integrity": "sha512-16Wiq8EYB6ghwqZdYytnNkcCN4sT3jyt9XkjfMxI5DDdjLuPc8wbj5VV5pw8S6lZvBL4eAwXGE3+fPqXKxH6GQ==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.409.tgz",
+ "integrity": "sha512-O7b/9LmBO8ljPqNngonx+v5d3cOs6HKvj2E9f5/Flb9Uw2lut7g6KGerfDYCMZUpvFCMDfbZSBJD3SDuJj1uPg==",
"cpu": [
"arm64"
],
@@ -727,9 +727,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.405.tgz",
- "integrity": "sha512-HXpg7p235//pAuCvcL9m2EeIrL/K6OUEkFeHF3BFHzqUJR4a69gKLsxtUg0ZctypHqo2SehGCRAyVippTVlTyg==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.409.tgz",
+ "integrity": "sha512-zSfFqyPxNaBE5/ClrSjsKxhhTpJaVOqSJY0q87iV9fw6xwdzcJ1/FlZGKjE7W8YVb4tdJx+OBMjQCU8WYewF1A==",
"cpu": [
"x64"
],
@@ -743,9 +743,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.405.tgz",
- "integrity": "sha512-4JCUMiRjP7zB3j1XpEtJq7b7cxTzuwDJ9o76jayAL8HL9NhqKZ6Ys6uxhDA6f/l0N2GVD1TEICxsnPgadz6srg==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.409.tgz",
+ "integrity": "sha512-VizZsdK7L3ym/OR4wahiFx+6hFtaOYN9qvsHmNSo8pb65AZ6ORdRnCPE7w9ZejMpdNEa6x6WqHfxDKJlF85zyA==",
"cpu": [
"arm64"
],
@@ -759,9 +759,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "0.0.405",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.405.tgz",
- "integrity": "sha512-uHoJ9N8kZbTLbzgqBE1szHwLElv2f+P2OWlqmRSawQhwPl0s7u55dka7mZYvj2ZoNvIyb0OyShCO56OpmCcy/w==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.409.tgz",
+ "integrity": "sha512-c6dP3XRFk550PmH1Vxe7n/bStNSLnVGH5B+ErUKXk/SPqmZ59pyoa7H2USNdoC6Nav5tkwYYR1vwNZRy+iKvrA==",
"cpu": [
"x64"
],
diff --git a/nodejs/package.json b/nodejs/package.json
index b6e23f40..435f4300 100644
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -40,7 +40,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^0.0.405",
+ "@github/copilot": "^0.0.409",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts
index af6260c9..af426766 100644
--- a/nodejs/src/client.ts
+++ b/nodejs/src/client.ts
@@ -37,6 +37,8 @@ import type {
SessionLifecycleEvent,
SessionLifecycleEventType,
SessionLifecycleHandler,
+ SessionContext,
+ SessionListFilter,
SessionMetadata,
Tool,
ToolCallRequestPayload,
@@ -804,27 +806,24 @@ export class CopilotClient {
}
/**
- * Lists all available sessions known to the server.
+ * List all available sessions.
*
- * Returns metadata about each session including ID, timestamps, and summary.
- *
- * @returns A promise that resolves with an array of session metadata
- * @throws Error if the client is not connected
+ * @param filter - Optional filter to limit returned sessions by context fields
*
* @example
- * ```typescript
+ * // List all sessions
* const sessions = await client.listSessions();
- * for (const session of sessions) {
- * console.log(`${session.sessionId}: ${session.summary}`);
- * }
- * ```
+ *
+ * @example
+ * // List sessions for a specific repository
+ * const sessions = await client.listSessions({ repository: "owner/repo" });
*/
- async listSessions(): Promise {
+ async listSessions(filter?: SessionListFilter): Promise {
if (!this.connection) {
throw new Error("Client not connected");
}
- const response = await this.connection.sendRequest("session.list", {});
+ const response = await this.connection.sendRequest("session.list", { filter });
const { sessions } = response as {
sessions: Array<{
sessionId: string;
@@ -832,6 +831,7 @@ export class CopilotClient {
modifiedTime: string;
summary?: string;
isRemote: boolean;
+ context?: SessionContext;
}>;
};
@@ -841,6 +841,7 @@ export class CopilotClient {
modifiedTime: new Date(s.modifiedTime),
summary: s.summary,
isRemote: s.isRemote,
+ context: s.context,
}));
}
diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts
index 86783a04..2d4af7c1 100644
--- a/nodejs/src/generated/session-events.ts
+++ b/nodejs/src/generated/session-events.ts
@@ -3,7 +3,7 @@
*
* Generated from: @github/copilot/session-events.schema.json
* Generated by: scripts/generate-session-types.ts
- * Generated at: 2026-02-06T20:38:23.139Z
+ * Generated at: 2026-02-12T22:32:11.508Z
*
* To update these types:
* 1. Update the schema in copilot-agent-runtime
@@ -71,6 +71,16 @@ export type SessionEvent =
type: "session.idle";
data: {};
}
+ | {
+ id: string;
+ timestamp: string;
+ parentId: string | null;
+ ephemeral: true;
+ type: "session.title_changed";
+ data: {
+ title: string;
+ };
+ }
| {
id: string;
timestamp: string;
@@ -82,6 +92,17 @@ export type SessionEvent =
message: string;
};
}
+ | {
+ id: string;
+ timestamp: string;
+ parentId: string | null;
+ ephemeral?: boolean;
+ type: "session.warning";
+ data: {
+ warningType: string;
+ message: string;
+ };
+ }
| {
id: string;
timestamp: string;
@@ -174,6 +195,19 @@ export type SessionEvent =
currentModel?: string;
};
}
+ | {
+ id: string;
+ timestamp: string;
+ parentId: string | null;
+ ephemeral?: boolean;
+ type: "session.context_changed";
+ data: {
+ cwd: string;
+ gitRoot?: string;
+ repository?: string;
+ branch?: string;
+ };
+ }
| {
id: string;
timestamp: string;
@@ -233,11 +267,19 @@ export type SessionEvent =
type: "file";
path: string;
displayName: string;
+ lineRange?: {
+ start: number;
+ end: number;
+ };
}
| {
type: "directory";
path: string;
displayName: string;
+ lineRange?: {
+ start: number;
+ end: number;
+ };
}
| {
type: "selection";
@@ -257,6 +299,7 @@ export type SessionEvent =
}
)[];
source?: string;
+ agentMode?: "interactive" | "plan" | "autopilot" | "shell";
};
}
| {
@@ -327,6 +370,7 @@ export type SessionEvent =
reasoningOpaque?: string;
reasoningText?: string;
encryptedContent?: string;
+ phase?: string;
parentToolCallId?: string;
};
}
@@ -457,6 +501,57 @@ export type SessionEvent =
result?: {
content: string;
detailedContent?: string;
+ contents?: (
+ | {
+ type: "text";
+ text: string;
+ }
+ | {
+ type: "terminal";
+ text: string;
+ exitCode?: number;
+ cwd?: string;
+ }
+ | {
+ type: "image";
+ data: string;
+ mimeType: string;
+ }
+ | {
+ type: "audio";
+ data: string;
+ mimeType: string;
+ }
+ | {
+ icons?: {
+ src: string;
+ mimeType?: string;
+ sizes?: string[];
+ theme?: "light" | "dark";
+ }[];
+ name: string;
+ title?: string;
+ uri: string;
+ description?: string;
+ mimeType?: string;
+ size?: number;
+ type: "resource_link";
+ }
+ | {
+ type: "resource";
+ resource:
+ | {
+ uri: string;
+ mimeType?: string;
+ text: string;
+ }
+ | {
+ uri: string;
+ mimeType?: string;
+ blob: string;
+ };
+ }
+ )[];
};
error?: {
message: string;
@@ -503,6 +598,7 @@ export type SessionEvent =
data: {
toolCallId: string;
agentName: string;
+ agentDisplayName: string;
};
}
| {
@@ -514,6 +610,7 @@ export type SessionEvent =
data: {
toolCallId: string;
agentName: string;
+ agentDisplayName: string;
error: string;
};
}
diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts
index 4f9fcbf6..5e73a1bb 100644
--- a/nodejs/src/index.ts
+++ b/nodejs/src/index.ts
@@ -39,6 +39,8 @@ export type {
SessionLifecycleEvent,
SessionLifecycleEventType,
SessionLifecycleHandler,
+ SessionContext,
+ SessionListFilter,
SessionMetadata,
SystemMessageAppendConfig,
SystemMessageConfig,
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index ffb96801..c2806804 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -869,6 +869,34 @@ export type SessionEventHandler = (event: SessionEvent) => void;
*/
export type ConnectionState = "disconnected" | "connecting" | "connected" | "error";
+/**
+ * Working directory context for a session
+ */
+export interface SessionContext {
+ /** Working directory where the session was created */
+ cwd: string;
+ /** Git repository root (if in a git repo) */
+ gitRoot?: string;
+ /** GitHub repository in "owner/repo" format */
+ repository?: string;
+ /** Current git branch */
+ branch?: string;
+}
+
+/**
+ * Filter options for listing sessions
+ */
+export interface SessionListFilter {
+ /** Filter by exact cwd match */
+ cwd?: string;
+ /** Filter by git root */
+ gitRoot?: string;
+ /** Filter by repository (owner/repo format) */
+ repository?: string;
+ /** Filter by branch */
+ branch?: string;
+}
+
/**
* Metadata about a session
*/
@@ -878,6 +906,8 @@ export interface SessionMetadata {
modifiedTime: Date;
summary?: string;
isRemote: boolean;
+ /** Working directory context (cwd, git info) from session creation */
+ context?: SessionContext;
}
/**
diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts
index 01a3ad0b..de1e9e6d 100644
--- a/nodejs/test/e2e/session.test.ts
+++ b/nodejs/test/e2e/session.test.ts
@@ -22,6 +22,27 @@ describe("Sessions", async () => {
await expect(() => session.getMessages()).rejects.toThrow(/Session not found/);
});
+ // TODO: Re-enable once test harness CAPI proxy supports this test's session lifecycle
+ it.skip("should list sessions with context field", { timeout: 60000 }, async () => {
+ // Create a session — just creating it is enough for it to appear in listSessions
+ const session = await client.createSession();
+ expect(session.sessionId).toMatch(/^[a-f0-9-]+$/);
+
+ // Verify it has a start event (confirms session is active)
+ const messages = await session.getMessages();
+ expect(messages.length).toBeGreaterThan(0);
+
+ // List sessions and find the one we just created
+ const sessions = await client.listSessions();
+ const ourSession = sessions.find((s) => s.sessionId === session.sessionId);
+
+ expect(ourSession).toBeDefined();
+ // Context may not be populated if workspace.yaml hasn't been written yet
+ if (ourSession?.context) {
+ expect(ourSession.context.cwd).toMatch(/^(\/|[A-Za-z]:)/);
+ }
+ });
+
it("should have stateful conversation", async () => {
const session = await client.createSession();
const assistantMessage = await session.sendAndWait({ prompt: "What is 1+1?" });
diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py
index 90a05563..f5f7ed0b 100644
--- a/python/copilot/__init__.py
+++ b/python/copilot/__init__.py
@@ -28,7 +28,9 @@
ProviderConfig,
ResumeSessionConfig,
SessionConfig,
+ SessionContext,
SessionEvent,
+ SessionListFilter,
SessionMetadata,
StopError,
Tool,
@@ -62,7 +64,9 @@
"ProviderConfig",
"ResumeSessionConfig",
"SessionConfig",
+ "SessionContext",
"SessionEvent",
+ "SessionListFilter",
"SessionMetadata",
"StopError",
"Tool",
diff --git a/python/copilot/client.py b/python/copilot/client.py
index 85b72897..e4c9104c 100644
--- a/python/copilot/client.py
+++ b/python/copilot/client.py
@@ -41,6 +41,7 @@
SessionLifecycleEvent,
SessionLifecycleEventType,
SessionLifecycleHandler,
+ SessionListFilter,
SessionMetadata,
StopError,
ToolHandler,
@@ -837,12 +838,18 @@ async def list_models(self) -> list["ModelInfo"]:
return list(models) # Return a copy to prevent cache mutation
- async def list_sessions(self) -> list["SessionMetadata"]:
+ async def list_sessions(
+ self, filter: "SessionListFilter | None" = None
+ ) -> list["SessionMetadata"]:
"""
List all available sessions known to the server.
Returns metadata about each session including ID, timestamps, and summary.
+ Args:
+ filter: Optional filter to narrow down the list of sessions by cwd, git root,
+ repository, or branch.
+
Returns:
A list of SessionMetadata objects.
@@ -853,11 +860,18 @@ async def list_sessions(self) -> list["SessionMetadata"]:
>>> sessions = await client.list_sessions()
>>> for session in sessions:
... print(f"Session: {session.sessionId}")
+ >>> # Filter sessions by repository
+ >>> from copilot import SessionListFilter
+ >>> filtered = await client.list_sessions(SessionListFilter(repository="owner/repo"))
"""
if not self._client:
raise RuntimeError("Client not connected")
- response = await self._client.request("session.list", {})
+ payload: dict = {}
+ if filter is not None:
+ payload["filter"] = filter.to_dict()
+
+ response = await self._client.request("session.list", payload)
sessions_data = response.get("sessions", [])
return [SessionMetadata.from_dict(session) for session in sessions_data]
diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py
index 84dff82e..0621daa6 100644
--- a/python/copilot/generated/session_events.py
+++ b/python/copilot/generated/session_events.py
@@ -3,16 +3,16 @@
Generated from: @github/copilot/session-events.schema.json
Generated by: scripts/generate-session-types.ts
-Generated at: 2026-02-06T20:38:23.376Z
+Generated at: 2026-02-12T22:32:11.650Z
To update these types:
1. Update the schema in copilot-agent-runtime
2. Run: npm run generate:session-types
"""
+from enum import Enum
from dataclasses import dataclass
from typing import Any, Optional, List, Dict, Union, TypeVar, Type, cast, Callable
-from enum import Enum
from datetime import datetime
from uuid import UUID
import dateutil.parser
@@ -85,6 +85,32 @@ def from_int(x: Any) -> int:
return x
+class AgentMode(Enum):
+ AUTOPILOT = "autopilot"
+ INTERACTIVE = "interactive"
+ PLAN = "plan"
+ SHELL = "shell"
+
+
+@dataclass
+class LineRange:
+ end: float
+ start: float
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'LineRange':
+ assert isinstance(obj, dict)
+ end = from_float(obj.get("end"))
+ start = from_float(obj.get("start"))
+ return LineRange(end, start)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["end"] = to_float(self.end)
+ result["start"] = to_float(self.start)
+ return result
+
+
@dataclass
class End:
character: float
@@ -152,6 +178,7 @@ class AttachmentType(Enum):
class Attachment:
display_name: str
type: AttachmentType
+ line_range: Optional[LineRange] = None
path: Optional[str] = None
file_path: Optional[str] = None
selection: Optional[Selection] = None
@@ -162,16 +189,19 @@ def from_dict(obj: Any) -> 'Attachment':
assert isinstance(obj, dict)
display_name = from_str(obj.get("displayName"))
type = AttachmentType(obj.get("type"))
+ line_range = from_union([LineRange.from_dict, from_none], obj.get("lineRange"))
path = from_union([from_str, from_none], obj.get("path"))
file_path = from_union([from_str, from_none], obj.get("filePath"))
selection = from_union([Selection.from_dict, from_none], obj.get("selection"))
text = from_union([from_str, from_none], obj.get("text"))
- return Attachment(display_name, type, path, file_path, selection, text)
+ return Attachment(display_name, type, line_range, path, file_path, selection, text)
def to_dict(self) -> dict:
result: dict = {}
result["displayName"] = from_str(self.display_name)
result["type"] = to_enum(AttachmentType, self.type)
+ if self.line_range is not None:
+ result["lineRange"] = from_union([lambda x: to_class(LineRange, x), from_none], self.line_range)
if self.path is not None:
result["path"] = from_union([from_str, from_none], self.path)
if self.file_path is not None:
@@ -402,18 +432,18 @@ def to_dict(self) -> dict:
@dataclass
-class Repository:
+class RepositoryClass:
name: str
owner: str
branch: Optional[str] = None
@staticmethod
- def from_dict(obj: Any) -> 'Repository':
+ def from_dict(obj: Any) -> 'RepositoryClass':
assert isinstance(obj, dict)
name = from_str(obj.get("name"))
owner = from_str(obj.get("owner"))
branch = from_union([from_str, from_none], obj.get("branch"))
- return Repository(name, owner, branch)
+ return RepositoryClass(name, owner, branch)
def to_dict(self) -> dict:
result: dict = {}
@@ -424,21 +454,159 @@ def to_dict(self) -> dict:
return result
+class Theme(Enum):
+ DARK = "dark"
+ LIGHT = "light"
+
+
+@dataclass
+class Icon:
+ src: str
+ mime_type: Optional[str] = None
+ sizes: Optional[List[str]] = None
+ theme: Optional[Theme] = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'Icon':
+ assert isinstance(obj, dict)
+ src = from_str(obj.get("src"))
+ mime_type = from_union([from_str, from_none], obj.get("mimeType"))
+ sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes"))
+ theme = from_union([Theme, from_none], obj.get("theme"))
+ return Icon(src, mime_type, sizes, theme)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["src"] = from_str(self.src)
+ if self.mime_type is not None:
+ result["mimeType"] = from_union([from_str, from_none], self.mime_type)
+ if self.sizes is not None:
+ result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes)
+ if self.theme is not None:
+ result["theme"] = from_union([lambda x: to_enum(Theme, x), from_none], self.theme)
+ return result
+
+
+@dataclass
+class Resource:
+ uri: str
+ mime_type: Optional[str] = None
+ text: Optional[str] = None
+ blob: Optional[str] = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'Resource':
+ assert isinstance(obj, dict)
+ uri = from_str(obj.get("uri"))
+ mime_type = from_union([from_str, from_none], obj.get("mimeType"))
+ text = from_union([from_str, from_none], obj.get("text"))
+ blob = from_union([from_str, from_none], obj.get("blob"))
+ return Resource(uri, mime_type, text, blob)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["uri"] = from_str(self.uri)
+ if self.mime_type is not None:
+ result["mimeType"] = from_union([from_str, from_none], self.mime_type)
+ if self.text is not None:
+ result["text"] = from_union([from_str, from_none], self.text)
+ if self.blob is not None:
+ result["blob"] = from_union([from_str, from_none], self.blob)
+ return result
+
+
+class ContentType(Enum):
+ AUDIO = "audio"
+ IMAGE = "image"
+ RESOURCE = "resource"
+ RESOURCE_LINK = "resource_link"
+ TERMINAL = "terminal"
+ TEXT = "text"
+
+
+@dataclass
+class Content:
+ type: ContentType
+ text: Optional[str] = None
+ cwd: Optional[str] = None
+ exit_code: Optional[float] = None
+ data: Optional[str] = None
+ mime_type: Optional[str] = None
+ description: Optional[str] = None
+ icons: Optional[List[Icon]] = None
+ name: Optional[str] = None
+ size: Optional[float] = None
+ title: Optional[str] = None
+ uri: Optional[str] = None
+ resource: Optional[Resource] = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'Content':
+ assert isinstance(obj, dict)
+ type = ContentType(obj.get("type"))
+ text = from_union([from_str, from_none], obj.get("text"))
+ cwd = from_union([from_str, from_none], obj.get("cwd"))
+ exit_code = from_union([from_float, from_none], obj.get("exitCode"))
+ data = from_union([from_str, from_none], obj.get("data"))
+ mime_type = from_union([from_str, from_none], obj.get("mimeType"))
+ description = from_union([from_str, from_none], obj.get("description"))
+ icons = from_union([lambda x: from_list(Icon.from_dict, x), from_none], obj.get("icons"))
+ name = from_union([from_str, from_none], obj.get("name"))
+ size = from_union([from_float, from_none], obj.get("size"))
+ title = from_union([from_str, from_none], obj.get("title"))
+ uri = from_union([from_str, from_none], obj.get("uri"))
+ resource = from_union([Resource.from_dict, from_none], obj.get("resource"))
+ return Content(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["type"] = to_enum(ContentType, self.type)
+ if self.text is not None:
+ result["text"] = from_union([from_str, from_none], self.text)
+ if self.cwd is not None:
+ result["cwd"] = from_union([from_str, from_none], self.cwd)
+ if self.exit_code is not None:
+ result["exitCode"] = from_union([to_float, from_none], self.exit_code)
+ if self.data is not None:
+ result["data"] = from_union([from_str, from_none], self.data)
+ if self.mime_type is not None:
+ result["mimeType"] = from_union([from_str, from_none], self.mime_type)
+ if self.description is not None:
+ result["description"] = from_union([from_str, from_none], self.description)
+ if self.icons is not None:
+ result["icons"] = from_union([lambda x: from_list(lambda x: to_class(Icon, x), x), from_none], self.icons)
+ if self.name is not None:
+ result["name"] = from_union([from_str, from_none], self.name)
+ if self.size is not None:
+ result["size"] = from_union([to_float, from_none], self.size)
+ if self.title is not None:
+ result["title"] = from_union([from_str, from_none], self.title)
+ if self.uri is not None:
+ result["uri"] = from_union([from_str, from_none], self.uri)
+ if self.resource is not None:
+ result["resource"] = from_union([lambda x: to_class(Resource, x), from_none], self.resource)
+ return result
+
+
@dataclass
class Result:
content: str
+ contents: Optional[List[Content]] = None
detailed_content: Optional[str] = None
@staticmethod
def from_dict(obj: Any) -> 'Result':
assert isinstance(obj, dict)
content = from_str(obj.get("content"))
+ contents = from_union([lambda x: from_list(Content.from_dict, x), from_none], obj.get("contents"))
detailed_content = from_union([from_str, from_none], obj.get("detailedContent"))
- return Result(content, detailed_content)
+ return Result(content, contents, detailed_content)
def to_dict(self) -> dict:
result: dict = {}
result["content"] = from_str(self.content)
+ if self.contents is not None:
+ result["contents"] = from_union([lambda x: from_list(lambda x: to_class(Content, x), x), from_none], self.contents)
if self.detailed_content is not None:
result["detailedContent"] = from_union([from_str, from_none], self.detailed_content)
return result
@@ -507,12 +675,14 @@ class Data:
provider_call_id: Optional[str] = None
stack: Optional[str] = None
status_code: Optional[int] = None
+ title: Optional[str] = None
info_type: Optional[str] = None
+ warning_type: Optional[str] = None
new_model: Optional[str] = None
previous_model: Optional[str] = None
handoff_time: Optional[datetime] = None
remote_session_id: Optional[str] = None
- repository: Optional[Repository] = None
+ repository: Optional[Union[RepositoryClass, str]] = None
source_type: Optional[SourceType] = None
summary: Optional[str] = None
messages_removed_during_truncation: Optional[float] = None
@@ -533,6 +703,9 @@ class Data:
shutdown_type: Optional[ShutdownType] = None
total_api_duration_ms: Optional[float] = None
total_premium_requests: Optional[float] = None
+ branch: Optional[str] = None
+ cwd: Optional[str] = None
+ git_root: Optional[str] = None
current_tokens: Optional[float] = None
messages_length: Optional[float] = None
checkpoint_number: Optional[float] = None
@@ -547,6 +720,7 @@ class Data:
success: Optional[bool] = None
summary_content: Optional[str] = None
tokens_removed: Optional[float] = None
+ agent_mode: Optional[AgentMode] = None
attachments: Optional[List[Attachment]] = None
content: Optional[str] = None
source: Optional[str] = None
@@ -558,6 +732,7 @@ class Data:
encrypted_content: Optional[str] = None
message_id: Optional[str] = None
parent_tool_call_id: Optional[str] = None
+ phase: Optional[str] = None
reasoning_opaque: Optional[str] = None
reasoning_text: Optional[str] = None
tool_requests: Optional[List[ToolRequest]] = None
@@ -614,12 +789,14 @@ def from_dict(obj: Any) -> 'Data':
provider_call_id = from_union([from_str, from_none], obj.get("providerCallId"))
stack = from_union([from_str, from_none], obj.get("stack"))
status_code = from_union([from_int, from_none], obj.get("statusCode"))
+ title = from_union([from_str, from_none], obj.get("title"))
info_type = from_union([from_str, from_none], obj.get("infoType"))
+ warning_type = from_union([from_str, from_none], obj.get("warningType"))
new_model = from_union([from_str, from_none], obj.get("newModel"))
previous_model = from_union([from_str, from_none], obj.get("previousModel"))
handoff_time = from_union([from_datetime, from_none], obj.get("handoffTime"))
remote_session_id = from_union([from_str, from_none], obj.get("remoteSessionId"))
- repository = from_union([Repository.from_dict, from_none], obj.get("repository"))
+ repository = from_union([RepositoryClass.from_dict, from_str, from_none], obj.get("repository"))
source_type = from_union([SourceType, from_none], obj.get("sourceType"))
summary = from_union([from_str, from_none], obj.get("summary"))
messages_removed_during_truncation = from_union([from_float, from_none], obj.get("messagesRemovedDuringTruncation"))
@@ -640,6 +817,9 @@ def from_dict(obj: Any) -> 'Data':
shutdown_type = from_union([ShutdownType, from_none], obj.get("shutdownType"))
total_api_duration_ms = from_union([from_float, from_none], obj.get("totalApiDurationMs"))
total_premium_requests = from_union([from_float, from_none], obj.get("totalPremiumRequests"))
+ branch = from_union([from_str, from_none], obj.get("branch"))
+ cwd = from_union([from_str, from_none], obj.get("cwd"))
+ git_root = from_union([from_str, from_none], obj.get("gitRoot"))
current_tokens = from_union([from_float, from_none], obj.get("currentTokens"))
messages_length = from_union([from_float, from_none], obj.get("messagesLength"))
checkpoint_number = from_union([from_float, from_none], obj.get("checkpointNumber"))
@@ -654,6 +834,7 @@ def from_dict(obj: Any) -> 'Data':
success = from_union([from_bool, from_none], obj.get("success"))
summary_content = from_union([from_str, from_none], obj.get("summaryContent"))
tokens_removed = from_union([from_float, from_none], obj.get("tokensRemoved"))
+ agent_mode = from_union([AgentMode, from_none], obj.get("agentMode"))
attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments"))
content = from_union([from_str, from_none], obj.get("content"))
source = from_union([from_str, from_none], obj.get("source"))
@@ -665,6 +846,7 @@ def from_dict(obj: Any) -> 'Data':
encrypted_content = from_union([from_str, from_none], obj.get("encryptedContent"))
message_id = from_union([from_str, from_none], obj.get("messageId"))
parent_tool_call_id = from_union([from_str, from_none], obj.get("parentToolCallId"))
+ phase = from_union([from_str, from_none], obj.get("phase"))
reasoning_opaque = from_union([from_str, from_none], obj.get("reasoningOpaque"))
reasoning_text = from_union([from_str, from_none], obj.get("reasoningText"))
tool_requests = from_union([lambda x: from_list(ToolRequest.from_dict, x), from_none], obj.get("toolRequests"))
@@ -703,7 +885,7 @@ def from_dict(obj: Any) -> 'Data':
output = obj.get("output")
metadata = from_union([Metadata.from_dict, from_none], obj.get("metadata"))
role = from_union([Role, from_none], obj.get("role"))
- return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, info_type, new_model, previous_model, handoff_time, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, current_model, error_reason, model_metrics, session_start_time, shutdown_type, total_api_duration_ms, total_premium_requests, current_tokens, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, encrypted_content, message_id, parent_tool_call_id, reasoning_opaque, reasoning_text, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, name, path, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, role)
+ return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, title, info_type, warning_type, new_model, previous_model, handoff_time, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, current_model, error_reason, model_metrics, session_start_time, shutdown_type, total_api_duration_ms, total_premium_requests, branch, cwd, git_root, current_tokens, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, encrypted_content, message_id, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, name, path, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, role)
def to_dict(self) -> dict:
result: dict = {}
@@ -735,8 +917,12 @@ def to_dict(self) -> dict:
result["stack"] = from_union([from_str, from_none], self.stack)
if self.status_code is not None:
result["statusCode"] = from_union([from_int, from_none], self.status_code)
+ if self.title is not None:
+ result["title"] = from_union([from_str, from_none], self.title)
if self.info_type is not None:
result["infoType"] = from_union([from_str, from_none], self.info_type)
+ if self.warning_type is not None:
+ result["warningType"] = from_union([from_str, from_none], self.warning_type)
if self.new_model is not None:
result["newModel"] = from_union([from_str, from_none], self.new_model)
if self.previous_model is not None:
@@ -746,7 +932,7 @@ def to_dict(self) -> dict:
if self.remote_session_id is not None:
result["remoteSessionId"] = from_union([from_str, from_none], self.remote_session_id)
if self.repository is not None:
- result["repository"] = from_union([lambda x: to_class(Repository, x), from_none], self.repository)
+ result["repository"] = from_union([lambda x: to_class(RepositoryClass, x), from_str, from_none], self.repository)
if self.source_type is not None:
result["sourceType"] = from_union([lambda x: to_enum(SourceType, x), from_none], self.source_type)
if self.summary is not None:
@@ -787,6 +973,12 @@ def to_dict(self) -> dict:
result["totalApiDurationMs"] = from_union([to_float, from_none], self.total_api_duration_ms)
if self.total_premium_requests is not None:
result["totalPremiumRequests"] = from_union([to_float, from_none], self.total_premium_requests)
+ if self.branch is not None:
+ result["branch"] = from_union([from_str, from_none], self.branch)
+ if self.cwd is not None:
+ result["cwd"] = from_union([from_str, from_none], self.cwd)
+ if self.git_root is not None:
+ result["gitRoot"] = from_union([from_str, from_none], self.git_root)
if self.current_tokens is not None:
result["currentTokens"] = from_union([to_float, from_none], self.current_tokens)
if self.messages_length is not None:
@@ -815,6 +1007,8 @@ def to_dict(self) -> dict:
result["summaryContent"] = from_union([from_str, from_none], self.summary_content)
if self.tokens_removed is not None:
result["tokensRemoved"] = from_union([to_float, from_none], self.tokens_removed)
+ if self.agent_mode is not None:
+ result["agentMode"] = from_union([lambda x: to_enum(AgentMode, x), from_none], self.agent_mode)
if self.attachments is not None:
result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments)
if self.content is not None:
@@ -837,6 +1031,8 @@ def to_dict(self) -> dict:
result["messageId"] = from_union([from_str, from_none], self.message_id)
if self.parent_tool_call_id is not None:
result["parentToolCallId"] = from_union([from_str, from_none], self.parent_tool_call_id)
+ if self.phase is not None:
+ result["phase"] = from_union([from_str, from_none], self.phase)
if self.reasoning_opaque is not None:
result["reasoningOpaque"] = from_union([from_str, from_none], self.reasoning_opaque)
if self.reasoning_text is not None:
@@ -931,6 +1127,7 @@ class SessionEventType(Enum):
PENDING_MESSAGES_MODIFIED = "pending_messages.modified"
SESSION_COMPACTION_COMPLETE = "session.compaction_complete"
SESSION_COMPACTION_START = "session.compaction_start"
+ SESSION_CONTEXT_CHANGED = "session.context_changed"
SESSION_ERROR = "session.error"
SESSION_HANDOFF = "session.handoff"
SESSION_IDLE = "session.idle"
@@ -940,8 +1137,10 @@ class SessionEventType(Enum):
SESSION_SHUTDOWN = "session.shutdown"
SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind"
SESSION_START = "session.start"
+ SESSION_TITLE_CHANGED = "session.title_changed"
SESSION_TRUNCATION = "session.truncation"
SESSION_USAGE_INFO = "session.usage_info"
+ SESSION_WARNING = "session.warning"
SKILL_INVOKED = "skill.invoked"
SUBAGENT_COMPLETED = "subagent.completed"
SUBAGENT_FAILED = "subagent.failed"
diff --git a/python/copilot/types.py b/python/copilot/types.py
index 3cecbe64..b77e36be 100644
--- a/python/copilot/types.py
+++ b/python/copilot/types.py
@@ -918,6 +918,61 @@ def to_dict(self) -> dict:
return result
+@dataclass
+class SessionContext:
+ """Working directory context for a session"""
+
+ cwd: str # Working directory where the session was created
+ gitRoot: str | None = None # Git repository root (if in a git repo)
+ repository: str | None = None # GitHub repository in "owner/repo" format
+ branch: str | None = None # Current git branch
+
+ @staticmethod
+ def from_dict(obj: Any) -> SessionContext:
+ assert isinstance(obj, dict)
+ cwd = obj.get("cwd")
+ if cwd is None:
+ raise ValueError("Missing required field 'cwd' in SessionContext")
+ return SessionContext(
+ cwd=str(cwd),
+ gitRoot=obj.get("gitRoot"),
+ repository=obj.get("repository"),
+ branch=obj.get("branch"),
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {"cwd": self.cwd}
+ if self.gitRoot is not None:
+ result["gitRoot"] = self.gitRoot
+ if self.repository is not None:
+ result["repository"] = self.repository
+ if self.branch is not None:
+ result["branch"] = self.branch
+ return result
+
+
+@dataclass
+class SessionListFilter:
+ """Filter options for listing sessions"""
+
+ cwd: str | None = None # Filter by exact cwd match
+ gitRoot: str | None = None # Filter by git root
+ repository: str | None = None # Filter by repository (owner/repo format)
+ branch: str | None = None # Filter by branch
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.cwd is not None:
+ result["cwd"] = self.cwd
+ if self.gitRoot is not None:
+ result["gitRoot"] = self.gitRoot
+ if self.repository is not None:
+ result["repository"] = self.repository
+ if self.branch is not None:
+ result["branch"] = self.branch
+ return result
+
+
@dataclass
class SessionMetadata:
"""Metadata about a session"""
@@ -927,6 +982,7 @@ class SessionMetadata:
modifiedTime: str # ISO 8601 timestamp when session was last modified
isRemote: bool # Whether the session is remote
summary: str | None = None # Optional summary of the session
+ context: SessionContext | None = None # Working directory context
@staticmethod
def from_dict(obj: Any) -> SessionMetadata:
@@ -941,12 +997,15 @@ def from_dict(obj: Any) -> SessionMetadata:
f"startTime={startTime}, modifiedTime={modifiedTime}, isRemote={isRemote}"
)
summary = obj.get("summary")
+ context_dict = obj.get("context")
+ context = SessionContext.from_dict(context_dict) if context_dict else None
return SessionMetadata(
sessionId=str(sessionId),
startTime=str(startTime),
modifiedTime=str(modifiedTime),
isRemote=bool(isRemote),
summary=summary,
+ context=context,
)
def to_dict(self) -> dict:
@@ -957,6 +1016,8 @@ def to_dict(self) -> dict:
result["isRemote"] = self.isRemote
if self.summary is not None:
result["summary"] = self.summary
+ if self.context is not None:
+ result["context"] = self.context.to_dict()
return result
diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py
index f2e545ed..58da274b 100644
--- a/python/e2e/test_session.py
+++ b/python/e2e/test_session.py
@@ -220,6 +220,13 @@ async def test_should_list_sessions(self, ctx: E2ETestContext):
assert isinstance(session_data.modifiedTime, str)
assert isinstance(session_data.isRemote, bool)
+ # Verify context field is present
+ for session_data in sessions:
+ assert hasattr(session_data, "context")
+ if session_data.context is not None:
+ assert hasattr(session_data.context, "cwd")
+ assert isinstance(session_data.context.cwd, str)
+
async def test_should_delete_session(self, ctx: E2ETestContext):
import asyncio
diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json
index d1725f03..1262e9d6 100644
--- a/test/harness/package-lock.json
+++ b/test/harness/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
- "@github/copilot": "^0.0.403",
+ "@github/copilot": "^0.0.409",
"@types/node": "^25.2.0",
"openai": "^6.17.0",
"tsx": "^4.21.0",
@@ -461,27 +461,27 @@
}
},
"node_modules/@github/copilot": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.403.tgz",
- "integrity": "sha512-v5jUdtGJReLmE1rmff/LZf+50nzmYQYAaSRNtVNr9g0j0GkCd/noQExe31i1+PudvWU0ZJjltR0B8pUfDRdA9Q==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.409.tgz",
+ "integrity": "sha512-rkYWOKjTSuGg99KsgmA0QAP4X2cpJzAYk6lZDlVxKPhuLP03wC5E+jLctrSLjpxhX32p9n13rm1+7Jun80a1hw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "0.0.403",
- "@github/copilot-darwin-x64": "0.0.403",
- "@github/copilot-linux-arm64": "0.0.403",
- "@github/copilot-linux-x64": "0.0.403",
- "@github/copilot-win32-arm64": "0.0.403",
- "@github/copilot-win32-x64": "0.0.403"
+ "@github/copilot-darwin-arm64": "0.0.409",
+ "@github/copilot-darwin-x64": "0.0.409",
+ "@github/copilot-linux-arm64": "0.0.409",
+ "@github/copilot-linux-x64": "0.0.409",
+ "@github/copilot-win32-arm64": "0.0.409",
+ "@github/copilot-win32-x64": "0.0.409"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.403.tgz",
- "integrity": "sha512-dOw8IleA0d1soHnbr/6wc6vZiYWNTKMgfTe/NET1nCfMzyKDt/0F0I7PT5y+DLujJknTla/ZeEmmBUmliTW4Cg==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.409.tgz",
+ "integrity": "sha512-yjrrp++UNNvRoWsZ1+UioBqb3DEVxL5M5ePnMO5/Sf1sngxh0y5P9P6ePFZU4PVlM5BgC38DtrcauZaKf/oArQ==",
"cpu": [
"arm64"
],
@@ -496,9 +496,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.403.tgz",
- "integrity": "sha512-aK2jSNWgY8eiZ+TmrvGhssMCPDTKArc0ip6Ul5OaslpytKks8hyXoRbxGD0N9sKioSUSbvKUf+1AqavbDpJO+w==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.409.tgz",
+ "integrity": "sha512-EhLfY5DGU/BZmwjVcfnwKuJA7BxS9zdNCGeynUq7z/SI93ziastFqOddUX4D+ySz6yMrrXieN8cUKgzAlRCOJg==",
"cpu": [
"x64"
],
@@ -513,9 +513,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.403.tgz",
- "integrity": "sha512-KhoR2iR70O6vCkzf0h8/K+p82qAgOvMTgAPm9bVEHvbdGFR7Py9qL5v03bMbPxsA45oNaZAkzDhfTAqWhIAZsQ==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.409.tgz",
+ "integrity": "sha512-O7b/9LmBO8ljPqNngonx+v5d3cOs6HKvj2E9f5/Flb9Uw2lut7g6KGerfDYCMZUpvFCMDfbZSBJD3SDuJj1uPg==",
"cpu": [
"arm64"
],
@@ -530,9 +530,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.403.tgz",
- "integrity": "sha512-eoswUc9vo4TB+/9PgFJLVtzI4dPjkpJXdCsAioVuoqPdNxHxlIHFe9HaVcqMRZxUNY1YHEBZozy+IpUEGjgdfQ==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.409.tgz",
+ "integrity": "sha512-zSfFqyPxNaBE5/ClrSjsKxhhTpJaVOqSJY0q87iV9fw6xwdzcJ1/FlZGKjE7W8YVb4tdJx+OBMjQCU8WYewF1A==",
"cpu": [
"x64"
],
@@ -547,9 +547,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.403.tgz",
- "integrity": "sha512-djWjzCsp2xPNafMyOZ/ivU328/WvWhdroGie/DugiJBTgQL2SP0quWW1fhTlDwE81a3g9CxfJonaRgOpFTJTcg==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.409.tgz",
+ "integrity": "sha512-VizZsdK7L3ym/OR4wahiFx+6hFtaOYN9qvsHmNSo8pb65AZ6ORdRnCPE7w9ZejMpdNEa6x6WqHfxDKJlF85zyA==",
"cpu": [
"arm64"
],
@@ -564,9 +564,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "0.0.403",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.403.tgz",
- "integrity": "sha512-lju8cHy2E6Ux7R7tWyLZeksYC2MVZu9i9ocjiBX/qfG2/pNJs7S5OlkwKJ0BSXSbZEHQYq7iHfEWp201bVfk9A==",
+ "version": "0.0.409",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.409.tgz",
+ "integrity": "sha512-c6dP3XRFk550PmH1Vxe7n/bStNSLnVGH5B+ErUKXk/SPqmZ59pyoa7H2USNdoC6Nav5tkwYYR1vwNZRy+iKvrA==",
"cpu": [
"x64"
],
diff --git a/test/harness/package.json b/test/harness/package.json
index 7a1a37ad..9c75747b 100644
--- a/test/harness/package.json
+++ b/test/harness/package.json
@@ -11,7 +11,7 @@
"test": "vitest run"
},
"devDependencies": {
- "@github/copilot": "^0.0.403",
+ "@github/copilot": "^0.0.409",
"@types/node": "^25.2.0",
"openai": "^6.17.0",
"tsx": "^4.21.0",