From e9a0b2afa6bb892c2b0f7b38d54e62d3e0ee7f50 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 28 Apr 2026 22:31:52 +0000
Subject: [PATCH 1/4] Update @github/copilot to 1.0.39-0
- Updated nodejs and test harness dependencies
- Re-ran code generators
- Formatted generated code
---
dotnet/src/Generated/Rpc.cs | 437 +++++++++++++-
dotnet/src/Generated/SessionEvents.cs | 81 ++-
go/generated_session_events.go | 42 +-
go/rpc/generated_rpc.go | 348 ++++++++++-
nodejs/package-lock.json | 56 +-
nodejs/package.json | 2 +-
nodejs/samples/package-lock.json | 2 +-
nodejs/src/generated/rpc.ts | 258 +++++++-
nodejs/src/generated/session-events.ts | 66 ++-
python/copilot/generated/rpc.py | 648 ++++++++++++++++++++-
python/copilot/generated/session_events.py | 70 ++-
test/harness/package-lock.json | 56 +-
test/harness/package.json | 2 +-
13 files changed, 1997 insertions(+), 71 deletions(-)
diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index 9b6f60c45..fce4b4708 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -686,7 +686,7 @@ internal sealed class ModeSetRequest
/// RPC data type for NameGet operations.
public sealed class NameGetResult
{
- /// The session name, falling back to the auto-generated summary, or null if neither exists.
+ /// The session name (user-set or auto-generated), or null if not yet set.
[JsonPropertyName("name")]
public string? Name { get; set; }
}
@@ -829,6 +829,10 @@ public sealed class WorkspacesGetWorkspaceResultWorkspace
/// Gets or sets the updated_at value.
[JsonPropertyName("updated_at")]
public DateTimeOffset? UpdatedAt { get; set; }
+
+ /// Gets or sets the user_named value.
+ [JsonPropertyName("user_named")]
+ public bool? UserNamed { get; set; }
}
/// RPC data type for WorkspacesGetWorkspace operations.
@@ -987,6 +991,10 @@ public sealed class AgentInfo
/// Unique identifier of the custom agent.
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
+
+ /// Absolute local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path.
+ [JsonPropertyName("path")]
+ public string? Path { get; set; }
}
/// RPC data type for AgentList operations.
@@ -1074,6 +1082,286 @@ internal sealed class SessionAgentReloadRequest
public string SessionId { get; set; } = string.Empty;
}
+/// RPC data type for TasksStartAgent operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksStartAgentResult
+{
+ /// Generated agent ID for the background task.
+ [JsonPropertyName("agentId")]
+ public string AgentId { get; set; } = string.Empty;
+}
+
+/// RPC data type for TasksStartAgent operations.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksStartAgentRequest
+{
+ /// Type of agent to start (e.g., 'explore', 'task', 'general-purpose').
+ [JsonPropertyName("agentType")]
+ public string AgentType { get; set; } = string.Empty;
+
+ /// Short description of the task.
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ /// Optional model override.
+ [JsonPropertyName("model")]
+ public string? Model { get; set; }
+
+ /// Short name for the agent, used to generate a human-readable ID.
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+
+ /// Task prompt for the agent.
+ [JsonPropertyName("prompt")]
+ public string Prompt { get; set; } = string.Empty;
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// Polymorphic base type discriminated by type.
+[JsonPolymorphic(
+ TypeDiscriminatorPropertyName = "type",
+ UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
+[JsonDerivedType(typeof(TaskInfoAgent), "agent")]
+[JsonDerivedType(typeof(TaskInfoShell), "shell")]
+public partial class TaskInfo
+{
+ /// The type discriminator.
+ [JsonPropertyName("type")]
+ public virtual string Type { get; set; } = string.Empty;
+}
+
+
+/// The agent variant of .
+public partial class TaskInfoAgent : TaskInfo
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "agent";
+
+ /// ISO 8601 timestamp when the current active period began.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("activeStartedAt")]
+ public DateTimeOffset? ActiveStartedAt { get; set; }
+
+ /// Accumulated active execution time in milliseconds.
+ [JsonConverter(typeof(MillisecondsTimeSpanConverter))]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("activeTimeMs")]
+ public TimeSpan? ActiveTimeMs { get; set; }
+
+ /// Type of agent running this task.
+ [JsonPropertyName("agentType")]
+ public required string AgentType { get; set; }
+
+ /// Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("canPromoteToBackground")]
+ public bool? CanPromoteToBackground { get; set; }
+
+ /// ISO 8601 timestamp when the task finished.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("completedAt")]
+ public DateTimeOffset? CompletedAt { get; set; }
+
+ /// Short description of the task.
+ [JsonPropertyName("description")]
+ public required string Description { get; set; }
+
+ /// Error message when the task failed.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("error")]
+ public string? Error { get; set; }
+
+ /// How the agent is currently being managed by the runtime.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("executionMode")]
+ public TaskAgentInfoExecutionMode? ExecutionMode { get; set; }
+
+ /// Unique task identifier.
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
+
+ /// ISO 8601 timestamp when the agent entered idle state.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("idleSince")]
+ public DateTimeOffset? IdleSince { get; set; }
+
+ /// Most recent response text from the agent.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("latestResponse")]
+ public string? LatestResponse { get; set; }
+
+ /// Model used for the task when specified.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("model")]
+ public string? Model { get; set; }
+
+ /// Prompt passed to the agent.
+ [JsonPropertyName("prompt")]
+ public required string Prompt { get; set; }
+
+ /// Result text from the task when available.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("result")]
+ public string? Result { get; set; }
+
+ /// ISO 8601 timestamp when the task was started.
+ [JsonPropertyName("startedAt")]
+ public required DateTimeOffset StartedAt { get; set; }
+
+ /// Current lifecycle status of the task.
+ [JsonPropertyName("status")]
+ public required TaskAgentInfoStatus Status { get; set; }
+
+ /// Tool call ID associated with this agent task.
+ [JsonPropertyName("toolCallId")]
+ public required string ToolCallId { get; set; }
+}
+
+/// The shell variant of .
+public partial class TaskInfoShell : TaskInfo
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "shell";
+
+ /// Whether the shell runs inside a managed PTY session or as an independent background process.
+ [JsonPropertyName("attachmentMode")]
+ public required TaskShellInfoAttachmentMode AttachmentMode { get; set; }
+
+ /// Whether this shell task can be promoted to background mode.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("canPromoteToBackground")]
+ public bool? CanPromoteToBackground { get; set; }
+
+ /// Command being executed.
+ [JsonPropertyName("command")]
+ public required string Command { get; set; }
+
+ /// ISO 8601 timestamp when the task finished.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("completedAt")]
+ public DateTimeOffset? CompletedAt { get; set; }
+
+ /// Short description of the task.
+ [JsonPropertyName("description")]
+ public required string Description { get; set; }
+
+ /// Whether the shell command is currently sync-waited or background-managed.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("executionMode")]
+ public TaskShellInfoExecutionMode? ExecutionMode { get; set; }
+
+ /// Unique task identifier.
+ [JsonPropertyName("id")]
+ public required string Id { get; set; }
+
+ /// Path to the detached shell log, when available.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("logPath")]
+ public string? LogPath { get; set; }
+
+ /// Process ID when available.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("pid")]
+ public long? Pid { get; set; }
+
+ /// ISO 8601 timestamp when the task was started.
+ [JsonPropertyName("startedAt")]
+ public required DateTimeOffset StartedAt { get; set; }
+
+ /// Current lifecycle status of the task.
+ [JsonPropertyName("status")]
+ public required TaskShellInfoStatus Status { get; set; }
+}
+
+/// RPC data type for TaskList operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TaskList
+{
+ /// Currently tracked tasks.
+ [JsonPropertyName("tasks")]
+ public IList Tasks { get => field ??= []; set; }
+}
+
+/// RPC data type for SessionTasksList operations.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class SessionTasksListRequest
+{
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// RPC data type for TasksPromoteToBackground operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksPromoteToBackgroundResult
+{
+ /// Whether the task was successfully promoted to background mode.
+ [JsonPropertyName("promoted")]
+ public bool Promoted { get; set; }
+}
+
+/// RPC data type for TasksPromoteToBackground operations.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksPromoteToBackgroundRequest
+{
+ /// Task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// RPC data type for TasksCancel operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksCancelResult
+{
+ /// Whether the task was successfully cancelled.
+ [JsonPropertyName("cancelled")]
+ public bool Cancelled { get; set; }
+}
+
+/// RPC data type for TasksCancel operations.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksCancelRequest
+{
+ /// Task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// RPC data type for TasksRemove operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksRemoveResult
+{
+ /// Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first).
+ [JsonPropertyName("removed")]
+ public bool Removed { get; set; }
+}
+
+/// RPC data type for TasksRemove operations.
+[Experimental(Diagnostics.Experimental)]
+internal sealed class TasksRemoveRequest
+{
+ /// Task identifier.
+ [JsonPropertyName("id")]
+ public string Id { get; set; } = string.Empty;
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// RPC data type for Skill operations.
public sealed class Skill
{
@@ -2539,6 +2827,89 @@ public enum InstructionsSourcesType
}
+/// How the agent is currently being managed by the runtime.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum TaskAgentInfoExecutionMode
+{
+ /// The sync variant.
+ [JsonStringEnumMemberName("sync")]
+ Sync,
+ /// The background variant.
+ [JsonStringEnumMemberName("background")]
+ Background,
+}
+
+
+/// Current lifecycle status of the task.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum TaskAgentInfoStatus
+{
+ /// The running variant.
+ [JsonStringEnumMemberName("running")]
+ Running,
+ /// The idle variant.
+ [JsonStringEnumMemberName("idle")]
+ Idle,
+ /// The completed variant.
+ [JsonStringEnumMemberName("completed")]
+ Completed,
+ /// The failed variant.
+ [JsonStringEnumMemberName("failed")]
+ Failed,
+ /// The cancelled variant.
+ [JsonStringEnumMemberName("cancelled")]
+ Cancelled,
+}
+
+
+/// Whether the shell runs inside a managed PTY session or as an independent background process.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum TaskShellInfoAttachmentMode
+{
+ /// The attached variant.
+ [JsonStringEnumMemberName("attached")]
+ Attached,
+ /// The detached variant.
+ [JsonStringEnumMemberName("detached")]
+ Detached,
+}
+
+
+/// Whether the shell command is currently sync-waited or background-managed.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum TaskShellInfoExecutionMode
+{
+ /// The sync variant.
+ [JsonStringEnumMemberName("sync")]
+ Sync,
+ /// The background variant.
+ [JsonStringEnumMemberName("background")]
+ Background,
+}
+
+
+/// Current lifecycle status of the task.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum TaskShellInfoStatus
+{
+ /// The running variant.
+ [JsonStringEnumMemberName("running")]
+ Running,
+ /// The idle variant.
+ [JsonStringEnumMemberName("idle")]
+ Idle,
+ /// The completed variant.
+ [JsonStringEnumMemberName("completed")]
+ Completed,
+ /// The failed variant.
+ [JsonStringEnumMemberName("failed")]
+ Failed,
+ /// The cancelled variant.
+ [JsonStringEnumMemberName("cancelled")]
+ Cancelled,
+}
+
+
/// Configuration source: user, workspace, plugin, or builtin.
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum McpServerSource
@@ -2943,6 +3314,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId)
Instructions = new InstructionsApi(rpc, sessionId);
Fleet = new FleetApi(rpc, sessionId);
Agent = new AgentApi(rpc, sessionId);
+ Tasks = new TasksApi(rpc, sessionId);
Skills = new SkillsApi(rpc, sessionId);
Mcp = new McpApi(rpc, sessionId);
Plugins = new PluginsApi(rpc, sessionId);
@@ -2983,6 +3355,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId)
/// Agent APIs.
public AgentApi Agent { get; }
+ /// Tasks APIs.
+ public TasksApi Tasks { get; }
+
/// Skills APIs.
public SkillsApi Skills { get; }
@@ -3290,6 +3665,55 @@ public async Task ReloadAsync(CancellationToken cancellationT
}
}
+/// Provides session-scoped Tasks APIs.
+[Experimental(Diagnostics.Experimental)]
+public sealed class TasksApi
+{
+ private readonly JsonRpc _rpc;
+ private readonly string _sessionId;
+
+ internal TasksApi(JsonRpc rpc, string sessionId)
+ {
+ _rpc = rpc;
+ _sessionId = sessionId;
+ }
+
+ /// Calls "session.tasks.startAgent".
+ public async Task StartAgentAsync(string agentType, string prompt, string name, string? description = null, string? model = null, CancellationToken cancellationToken = default)
+ {
+ var request = new TasksStartAgentRequest { SessionId = _sessionId, AgentType = agentType, Prompt = prompt, Name = name, Description = description, Model = model };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.tasks.startAgent", [request], cancellationToken);
+ }
+
+ /// Calls "session.tasks.list".
+ public async Task ListAsync(CancellationToken cancellationToken = default)
+ {
+ var request = new SessionTasksListRequest { SessionId = _sessionId };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.tasks.list", [request], cancellationToken);
+ }
+
+ /// Calls "session.tasks.promoteToBackground".
+ public async Task PromoteToBackgroundAsync(string id, CancellationToken cancellationToken = default)
+ {
+ var request = new TasksPromoteToBackgroundRequest { SessionId = _sessionId, Id = id };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.tasks.promoteToBackground", [request], cancellationToken);
+ }
+
+ /// Calls "session.tasks.cancel".
+ public async Task CancelAsync(string id, CancellationToken cancellationToken = default)
+ {
+ var request = new TasksCancelRequest { SessionId = _sessionId, Id = id };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.tasks.cancel", [request], cancellationToken);
+ }
+
+ /// Calls "session.tasks.remove".
+ public async Task RemoveAsync(string id, CancellationToken cancellationToken = default)
+ {
+ var request = new TasksRemoveRequest { SessionId = _sessionId, Id = id };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.tasks.remove", [request], cancellationToken);
+ }
+}
+
/// Provides session-scoped Skills APIs.
[Experimental(Diagnostics.Experimental)]
public sealed class SkillsApi
@@ -3905,6 +4329,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncFailed LLM API call metadata for telemetry.
+/// Represents the model.call_failure event.
+public partial class ModelCallFailureEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "model.call_failure";
+
+ /// The model.call_failure event payload.
+ [JsonPropertyName("data")]
+ public required ModelCallFailureData Data { get; set; }
+}
+
/// Turn abort information including the reason for termination.
/// Represents the abort event.
public partial class AbortEvent : SessionEvent
@@ -1272,6 +1286,11 @@ public partial class SessionInfoData
[JsonPropertyName("message")]
public required string Message { get; set; }
+ /// Optional actionable tip displayed with this message.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("tip")]
+ public string? Tip { get; set; }
+
/// Optional URL associated with this message that the user can open in a browser.
[Url]
[StringSyntax(StringSyntaxAttribute.Uri)]
@@ -1961,6 +1980,49 @@ public partial class AssistantUsageData
public double? TtftMs { get; set; }
}
+/// Failed LLM API call metadata for telemetry.
+public partial class ModelCallFailureData
+{
+ /// Completion ID from the model provider (e.g., chatcmpl-abc123).
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("apiCallId")]
+ public string? ApiCallId { get; set; }
+
+ /// Duration of the failed API call in milliseconds.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("durationMs")]
+ public double? DurationMs { get; set; }
+
+ /// Raw provider/runtime error message for restricted telemetry.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("errorMessage")]
+ public string? ErrorMessage { get; set; }
+
+ /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("initiator")]
+ public string? Initiator { get; set; }
+
+ /// Model identifier used for the failed API call.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("model")]
+ public string? Model { get; set; }
+
+ /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("providerCallId")]
+ public string? ProviderCallId { get; set; }
+
+ /// Where the failed model call originated.
+ [JsonPropertyName("source")]
+ public required ModelCallFailureSource Source { get; set; }
+
+ /// HTTP status code from the failed request.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("statusCode")]
+ public long? StatusCode { get; set; }
+}
+
/// Turn abort information including the reason for termination.
public partial class AbortData
{
@@ -3552,7 +3614,7 @@ public partial class SystemNotificationNewInboxMessage : SystemNotification
[JsonPropertyName("senderName")]
public required string SenderName { get; set; }
- /// Category of the sender (e.g., ambient-agent, plugin, hook).
+ /// Category of the sender (e.g., sidekick-agent, plugin, hook).
[JsonPropertyName("senderType")]
public required string SenderType { get; set; }
@@ -4485,6 +4547,21 @@ public enum AssistantMessageToolRequestType
Custom,
}
+/// Where the failed model call originated.
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum ModelCallFailureSource
+{
+ /// The top_level variant.
+ [JsonStringEnumMemberName("top_level")]
+ TopLevel,
+ /// The subagent variant.
+ [JsonStringEnumMemberName("subagent")]
+ Subagent,
+ /// The mcp_sampling variant.
+ [JsonStringEnumMemberName("mcp_sampling")]
+ McpSampling,
+}
+
/// Theme variant this icon is intended for.
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum ToolExecutionCompleteContentResourceLinkIconTheme
@@ -4794,6 +4871,8 @@ public enum ExtensionsLoadedExtensionStatus
[JsonSerializable(typeof(McpOauthRequiredEvent))]
[JsonSerializable(typeof(McpOauthRequiredStaticClientConfig))]
[JsonSerializable(typeof(McpServersLoadedServer))]
+[JsonSerializable(typeof(ModelCallFailureData))]
+[JsonSerializable(typeof(ModelCallFailureEvent))]
[JsonSerializable(typeof(PendingMessagesModifiedData))]
[JsonSerializable(typeof(PendingMessagesModifiedEvent))]
[JsonSerializable(typeof(PermissionCompletedData))]
diff --git a/go/generated_session_events.go b/go/generated_session_events.go
index 8b393bec9..19a376e7a 100644
--- a/go/generated_session_events.go
+++ b/go/generated_session_events.go
@@ -263,6 +263,12 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error {
return err
}
e.Data = &d
+ case SessionEventTypeModelCallFailure:
+ var d ModelCallFailureData
+ if err := json.Unmarshal(raw.Data, &d); err != nil {
+ return err
+ }
+ e.Data = &d
case SessionEventTypeAbort:
var d AbortData
if err := json.Unmarshal(raw.Data, &d); err != nil {
@@ -588,6 +594,7 @@ const (
SessionEventTypeAssistantMessageDelta SessionEventType = "assistant.message_delta"
SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end"
SessionEventTypeAssistantUsage SessionEventType = "assistant.usage"
+ SessionEventTypeModelCallFailure SessionEventType = "model.call_failure"
SessionEventTypeAbort SessionEventType = "abort"
SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested"
SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start"
@@ -903,6 +910,28 @@ type ExternalToolRequestedData struct {
func (*ExternalToolRequestedData) sessionEventData() {}
+// Failed LLM API call metadata for telemetry
+type ModelCallFailureData struct {
+ // Completion ID from the model provider (e.g., chatcmpl-abc123)
+ APICallID *string `json:"apiCallId,omitempty"`
+ // Duration of the failed API call in milliseconds
+ DurationMs *float64 `json:"durationMs,omitempty"`
+ // Raw provider/runtime error message for restricted telemetry
+ ErrorMessage *string `json:"errorMessage,omitempty"`
+ // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls
+ Initiator *string `json:"initiator,omitempty"`
+ // Model identifier used for the failed API call
+ Model *string `json:"model,omitempty"`
+ // GitHub request tracing ID (x-github-request-id header) for server-side log correlation
+ ProviderCallID *string `json:"providerCallId,omitempty"`
+ // Where the failed model call originated
+ Source ModelCallFailureSource `json:"source"`
+ // HTTP status code from the failed request
+ StatusCode *int64 `json:"statusCode,omitempty"`
+}
+
+func (*ModelCallFailureData) sessionEventData() {}
+
// Hook invocation completion details including output, success status, and error information
type HookEndData struct {
// Error details when the hook failed
@@ -937,6 +966,8 @@ type SessionInfoData struct {
InfoType string `json:"infoType"`
// Human-readable informational message for display in the timeline
Message string `json:"message"`
+ // Optional actionable tip displayed with this message
+ Tip *string `json:"tip,omitempty"`
// Optional URL associated with this message that the user can open in a browser
URL *string `json:"url,omitempty"`
}
@@ -2028,7 +2059,7 @@ type SystemNotification struct {
Prompt *string `json:"prompt,omitempty"`
// Human-readable name of the sender
SenderName *string `json:"senderName,omitempty"`
- // Category of the sender (e.g., ambient-agent, plugin, hook)
+ // Category of the sender (e.g., sidekick-agent, plugin, hook)
SenderType *string `json:"senderType,omitempty"`
// Unique identifier of the shell session
ShellID *string `json:"shellId,omitempty"`
@@ -2464,6 +2495,15 @@ const (
PermissionRequestMemoryDirectionDownvote PermissionRequestMemoryDirection = "downvote"
)
+// Where the failed model call originated
+type ModelCallFailureSource string
+
+const (
+ ModelCallFailureSourceTopLevel ModelCallFailureSource = "top_level"
+ ModelCallFailureSourceSubagent ModelCallFailureSource = "subagent"
+ ModelCallFailureSourceMcpSampling ModelCallFailureSource = "mcp_sampling"
+)
+
// Whether the agent completed successfully or failed
type SystemNotificationAgentCompletedStatus string
diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go
index 2efcb494f..311081247 100644
--- a/go/rpc/generated_rpc.go
+++ b/go/rpc/generated_rpc.go
@@ -184,6 +184,23 @@ type RPCTypes struct {
SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"`
SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"`
SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"`
+ TaskAgentInfo TaskAgentInfo `json:"TaskAgentInfo"`
+ TaskAgentInfoExecutionMode TaskInfoExecutionMode `json:"TaskAgentInfoExecutionMode"`
+ TaskAgentInfoStatus TaskInfoStatus `json:"TaskAgentInfoStatus"`
+ TaskInfo TaskInfo `json:"TaskInfo"`
+ TaskList TaskList `json:"TaskList"`
+ TasksCancelRequest TasksCancelRequest `json:"TasksCancelRequest"`
+ TasksCancelResult TasksCancelResult `json:"TasksCancelResult"`
+ TaskShellInfo TaskShellInfo `json:"TaskShellInfo"`
+ TaskShellInfoAttachmentMode TaskShellInfoAttachmentMode `json:"TaskShellInfoAttachmentMode"`
+ TaskShellInfoExecutionMode TaskInfoExecutionMode `json:"TaskShellInfoExecutionMode"`
+ TaskShellInfoStatus TaskInfoStatus `json:"TaskShellInfoStatus"`
+ TasksPromoteToBackgroundRequest TasksPromoteToBackgroundRequest `json:"TasksPromoteToBackgroundRequest"`
+ TasksPromoteToBackgroundResult TasksPromoteToBackgroundResult `json:"TasksPromoteToBackgroundResult"`
+ TasksRemoveRequest TasksRemoveRequest `json:"TasksRemoveRequest"`
+ TasksRemoveResult TasksRemoveResult `json:"TasksRemoveResult"`
+ TasksStartAgentRequest TasksStartAgentRequest `json:"TasksStartAgentRequest"`
+ TasksStartAgentResult TasksStartAgentResult `json:"TasksStartAgentResult"`
Tool Tool `json:"Tool"`
ToolCallResult ToolCallResult `json:"ToolCallResult"`
ToolList ToolList `json:"ToolList"`
@@ -273,6 +290,9 @@ type AgentInfo struct {
DisplayName string `json:"displayName"`
// Unique identifier of the custom agent
Name string `json:"name"`
+ // Absolute local file path of the agent definition. Only set for file-based agents loaded
+ // from disk; remote agents do not have a path.
+ Path *string `json:"path,omitempty"`
}
// Experimental: AgentList is part of an experimental API and may change or be removed.
@@ -777,7 +797,7 @@ type ModelsListRequest struct {
}
type NameGetResult struct {
- // The session name, falling back to the auto-generated summary, or null if neither exists
+ // The session name (user-set or auto-generated), or null if not yet set
Name *string `json:"name"`
}
@@ -1301,6 +1321,192 @@ type SkillsEnableResult struct {
type SkillsReloadResult struct {
}
+type TaskAgentInfo struct {
+ // ISO 8601 timestamp when the current active period began
+ ActiveStartedAt *time.Time `json:"activeStartedAt,omitempty"`
+ // Accumulated active execution time in milliseconds
+ ActiveTimeMS *int64 `json:"activeTimeMs,omitempty"`
+ // Type of agent running this task
+ AgentType string `json:"agentType"`
+ // Whether the task is currently in the original sync wait and can be moved to background
+ // mode. False once it is already backgrounded, idle, finished, or no longer has a
+ // promotable sync waiter.
+ CanPromoteToBackground *bool `json:"canPromoteToBackground,omitempty"`
+ // ISO 8601 timestamp when the task finished
+ CompletedAt *time.Time `json:"completedAt,omitempty"`
+ // Short description of the task
+ Description string `json:"description"`
+ // Error message when the task failed
+ Error *string `json:"error,omitempty"`
+ // How the agent is currently being managed by the runtime
+ ExecutionMode *TaskInfoExecutionMode `json:"executionMode,omitempty"`
+ // Unique task identifier
+ ID string `json:"id"`
+ // ISO 8601 timestamp when the agent entered idle state
+ IdleSince *time.Time `json:"idleSince,omitempty"`
+ // Most recent response text from the agent
+ LatestResponse *string `json:"latestResponse,omitempty"`
+ // Model used for the task when specified
+ Model *string `json:"model,omitempty"`
+ // Prompt passed to the agent
+ Prompt string `json:"prompt"`
+ // Result text from the task when available
+ Result *string `json:"result,omitempty"`
+ // ISO 8601 timestamp when the task was started
+ StartedAt time.Time `json:"startedAt"`
+ // Current lifecycle status of the task
+ Status TaskInfoStatus `json:"status"`
+ // Tool call ID associated with this agent task
+ ToolCallID string `json:"toolCallId"`
+ // Task kind
+ Type TaskAgentInfoType `json:"type"`
+}
+
+type TaskInfo struct {
+ // ISO 8601 timestamp when the current active period began
+ ActiveStartedAt *time.Time `json:"activeStartedAt,omitempty"`
+ // Accumulated active execution time in milliseconds
+ ActiveTimeMS *int64 `json:"activeTimeMs,omitempty"`
+ // Type of agent running this task
+ AgentType *string `json:"agentType,omitempty"`
+ // Whether the task is currently in the original sync wait and can be moved to background
+ // mode. False once it is already backgrounded, idle, finished, or no longer has a
+ // promotable sync waiter.
+ //
+ // Whether this shell task can be promoted to background mode
+ CanPromoteToBackground *bool `json:"canPromoteToBackground,omitempty"`
+ // ISO 8601 timestamp when the task finished
+ CompletedAt *time.Time `json:"completedAt,omitempty"`
+ // Short description of the task
+ Description string `json:"description"`
+ // Error message when the task failed
+ Error *string `json:"error,omitempty"`
+ // How the agent is currently being managed by the runtime
+ //
+ // Whether the shell command is currently sync-waited or background-managed
+ ExecutionMode *TaskInfoExecutionMode `json:"executionMode,omitempty"`
+ // Unique task identifier
+ ID string `json:"id"`
+ // ISO 8601 timestamp when the agent entered idle state
+ IdleSince *time.Time `json:"idleSince,omitempty"`
+ // Most recent response text from the agent
+ LatestResponse *string `json:"latestResponse,omitempty"`
+ // Model used for the task when specified
+ Model *string `json:"model,omitempty"`
+ // Prompt passed to the agent
+ Prompt *string `json:"prompt,omitempty"`
+ // Result text from the task when available
+ Result *string `json:"result,omitempty"`
+ // ISO 8601 timestamp when the task was started
+ StartedAt time.Time `json:"startedAt"`
+ // Current lifecycle status of the task
+ Status TaskInfoStatus `json:"status"`
+ // Tool call ID associated with this agent task
+ ToolCallID *string `json:"toolCallId,omitempty"`
+ // Task kind
+ Type TaskInfoType `json:"type"`
+ // Whether the shell runs inside a managed PTY session or as an independent background
+ // process
+ AttachmentMode *TaskShellInfoAttachmentMode `json:"attachmentMode,omitempty"`
+ // Command being executed
+ Command *string `json:"command,omitempty"`
+ // Path to the detached shell log, when available
+ LogPath *string `json:"logPath,omitempty"`
+ // Process ID when available
+ PID *int64 `json:"pid,omitempty"`
+}
+
+// Experimental: TaskList is part of an experimental API and may change or be removed.
+type TaskList struct {
+ // Currently tracked tasks
+ Tasks []TaskInfo `json:"tasks"`
+}
+
+type TaskShellInfo struct {
+ // Whether the shell runs inside a managed PTY session or as an independent background
+ // process
+ AttachmentMode TaskShellInfoAttachmentMode `json:"attachmentMode"`
+ // Whether this shell task can be promoted to background mode
+ CanPromoteToBackground *bool `json:"canPromoteToBackground,omitempty"`
+ // Command being executed
+ Command string `json:"command"`
+ // ISO 8601 timestamp when the task finished
+ CompletedAt *time.Time `json:"completedAt,omitempty"`
+ // Short description of the task
+ Description string `json:"description"`
+ // Whether the shell command is currently sync-waited or background-managed
+ ExecutionMode *TaskInfoExecutionMode `json:"executionMode,omitempty"`
+ // Unique task identifier
+ ID string `json:"id"`
+ // Path to the detached shell log, when available
+ LogPath *string `json:"logPath,omitempty"`
+ // Process ID when available
+ PID *int64 `json:"pid,omitempty"`
+ // ISO 8601 timestamp when the task was started
+ StartedAt time.Time `json:"startedAt"`
+ // Current lifecycle status of the task
+ Status TaskInfoStatus `json:"status"`
+ // Task kind
+ Type TaskShellInfoType `json:"type"`
+}
+
+// Experimental: TasksCancelRequest is part of an experimental API and may change or be removed.
+type TasksCancelRequest struct {
+ // Task identifier
+ ID string `json:"id"`
+}
+
+// Experimental: TasksCancelResult is part of an experimental API and may change or be removed.
+type TasksCancelResult struct {
+ // Whether the task was successfully cancelled
+ Cancelled bool `json:"cancelled"`
+}
+
+// Experimental: TasksPromoteToBackgroundRequest is part of an experimental API and may change or be removed.
+type TasksPromoteToBackgroundRequest struct {
+ // Task identifier
+ ID string `json:"id"`
+}
+
+// Experimental: TasksPromoteToBackgroundResult is part of an experimental API and may change or be removed.
+type TasksPromoteToBackgroundResult struct {
+ // Whether the task was successfully promoted to background mode
+ Promoted bool `json:"promoted"`
+}
+
+// Experimental: TasksRemoveRequest is part of an experimental API and may change or be removed.
+type TasksRemoveRequest struct {
+ // Task identifier
+ ID string `json:"id"`
+}
+
+// Experimental: TasksRemoveResult is part of an experimental API and may change or be removed.
+type TasksRemoveResult struct {
+ // Whether the task was removed. Returns false if the task does not exist or is still
+ // running/idle (cancel it first).
+ Removed bool `json:"removed"`
+}
+
+// Experimental: TasksStartAgentRequest is part of an experimental API and may change or be removed.
+type TasksStartAgentRequest struct {
+ // Type of agent to start (e.g., 'explore', 'task', 'general-purpose')
+ AgentType string `json:"agentType"`
+ // Short description of the task
+ Description *string `json:"description,omitempty"`
+ // Optional model override
+ Model *string `json:"model,omitempty"`
+ // Short name for the agent, used to generate a human-readable ID
+ Name string `json:"name"`
+ // Task prompt for the agent
+ Prompt string `json:"prompt"`
+}
+
+// Experimental: TasksStartAgentResult is part of an experimental API and may change or be removed.
+type TasksStartAgentResult struct {
+ // Generated agent ID for the background task
+ AgentID string `json:"agentId"`
+}
+
type Tool struct {
// Description of what the tool does
Description string `json:"description"`
@@ -1585,6 +1791,7 @@ type WorkspaceClass struct {
Summary *string `json:"summary,omitempty"`
SummaryCount *int64 `json:"summary_count,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
+ UserNamed *bool `json:"user_named,omitempty"`
}
type WorkspacesListFilesResult struct {
@@ -1867,6 +2074,55 @@ const (
ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM"
)
+// How the agent is currently being managed by the runtime
+//
+// Whether the shell command is currently sync-waited or background-managed
+type TaskInfoExecutionMode string
+
+const (
+ TaskInfoExecutionModeBackground TaskInfoExecutionMode = "background"
+ TaskInfoExecutionModeSync TaskInfoExecutionMode = "sync"
+)
+
+// Current lifecycle status of the task
+type TaskInfoStatus string
+
+const (
+ TaskInfoStatusCancelled TaskInfoStatus = "cancelled"
+ TaskInfoStatusCompleted TaskInfoStatus = "completed"
+ TaskInfoStatusIdle TaskInfoStatus = "idle"
+ TaskInfoStatusFailed TaskInfoStatus = "failed"
+ TaskInfoStatusRunning TaskInfoStatus = "running"
+)
+
+type TaskAgentInfoType string
+
+const (
+ TaskAgentInfoTypeAgent TaskAgentInfoType = "agent"
+)
+
+// Whether the shell runs inside a managed PTY session or as an independent background
+// process
+type TaskShellInfoAttachmentMode string
+
+const (
+ TaskShellInfoAttachmentModeAttached TaskShellInfoAttachmentMode = "attached"
+ TaskShellInfoAttachmentModeDetached TaskShellInfoAttachmentMode = "detached"
+)
+
+type TaskInfoType string
+
+const (
+ TaskInfoTypeAgent TaskInfoType = "agent"
+ TaskInfoTypeShell TaskInfoType = "shell"
+)
+
+type TaskShellInfoType string
+
+const (
+ TaskShellInfoTypeShell TaskShellInfoType = "shell"
+)
+
type UIElicitationArrayAnyOfFieldType string
const (
@@ -2527,6 +2783,94 @@ func (a *AgentApi) Reload(ctx context.Context) (*AgentReloadResult, error) {
return &result, nil
}
+// Experimental: TasksApi contains experimental APIs that may change or be removed.
+type TasksApi sessionApi
+
+func (a *TasksApi) StartAgent(ctx context.Context, params *TasksStartAgentRequest) (*TasksStartAgentResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["agentType"] = params.AgentType
+ req["prompt"] = params.Prompt
+ req["name"] = params.Name
+ if params.Description != nil {
+ req["description"] = *params.Description
+ }
+ if params.Model != nil {
+ req["model"] = *params.Model
+ }
+ }
+ raw, err := a.client.Request("session.tasks.startAgent", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksStartAgentResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (a *TasksApi) List(ctx context.Context) (*TaskList, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ raw, err := a.client.Request("session.tasks.list", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TaskList
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (a *TasksApi) PromoteToBackground(ctx context.Context, params *TasksPromoteToBackgroundRequest) (*TasksPromoteToBackgroundResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["id"] = params.ID
+ }
+ raw, err := a.client.Request("session.tasks.promoteToBackground", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksPromoteToBackgroundResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (a *TasksApi) Cancel(ctx context.Context, params *TasksCancelRequest) (*TasksCancelResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["id"] = params.ID
+ }
+ raw, err := a.client.Request("session.tasks.cancel", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksCancelResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
+func (a *TasksApi) Remove(ctx context.Context, params *TasksRemoveRequest) (*TasksRemoveResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["id"] = params.ID
+ }
+ raw, err := a.client.Request("session.tasks.remove", req)
+ if err != nil {
+ return nil, err
+ }
+ var result TasksRemoveResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
// Experimental: SkillsApi contains experimental APIs that may change or be removed.
type SkillsApi sessionApi
@@ -2992,6 +3336,7 @@ type SessionRpc struct {
Instructions *InstructionsApi
Fleet *FleetApi
Agent *AgentApi
+ Tasks *TasksApi
Skills *SkillsApi
Mcp *McpApi
Plugins *PluginsApi
@@ -3042,6 +3387,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc {
r.Instructions = (*InstructionsApi)(&r.common)
r.Fleet = (*FleetApi)(&r.common)
r.Agent = (*AgentApi)(&r.common)
+ r.Tasks = (*TasksApi)(&r.common)
r.Skills = (*SkillsApi)(&r.common)
r.Mcp = (*McpApi)(&r.common)
r.Plugins = (*PluginsApi)(&r.common)
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index ac03d9a99..f2a929d73 100644
--- a/nodejs/package-lock.json
+++ b/nodejs/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.1.8",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.36-0",
+ "@github/copilot": "^1.0.39-0",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
@@ -663,26 +663,26 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.36-0.tgz",
- "integrity": "sha512-M1mxNbdRkiQv4qApKgV33jK6AsA3TqlMAtKyaDv9sJzE/kZa4IRUAUrmO+3d3C+ojZa/Yffjy0/6dC6kllhI4g==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.39-0.tgz",
+ "integrity": "sha512-OuN6wGgUv0WQydOCUuhYRFwUwTkfktI9fGdSih+SKUE+nTZze8JBz8Sg68K0ZLlqdD0OcF0ac9wMAfunlutvsw==",
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.36-0",
- "@github/copilot-darwin-x64": "1.0.36-0",
- "@github/copilot-linux-arm64": "1.0.36-0",
- "@github/copilot-linux-x64": "1.0.36-0",
- "@github/copilot-win32-arm64": "1.0.36-0",
- "@github/copilot-win32-x64": "1.0.36-0"
+ "@github/copilot-darwin-arm64": "1.0.39-0",
+ "@github/copilot-darwin-x64": "1.0.39-0",
+ "@github/copilot-linux-arm64": "1.0.39-0",
+ "@github/copilot-linux-x64": "1.0.39-0",
+ "@github/copilot-win32-arm64": "1.0.39-0",
+ "@github/copilot-win32-x64": "1.0.39-0"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.36-0.tgz",
- "integrity": "sha512-S0/oT9eo2WvjteWjtjougfh6tokq1Upye6tWeTHWq001E2UvrBN69+cQJNcNQUkO2C2AVvoqiI5RJT/E+HDrww==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-DquiC7DZo+OmP2AtQUW27FCBsMGLshX9MEedWczjDgQ5YK2iMwACQLMeULdURssXJWXjvQQZMTTo0wsow59lnA==",
"cpu": [
"arm64"
],
@@ -696,9 +696,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.36-0.tgz",
- "integrity": "sha512-msY1h6J2j005HMHxYqXO6Q5rJdqAjkUnnBwne5p3s71EHGekOl5U8GJs1Q2Y287+e9ZKSY68ANt/JB0Gq2ivmA==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.39-0.tgz",
+ "integrity": "sha512-NPjVkyl6QqYLGWlkqSiegcSUuI59RE3Qt4cOTALGG9TZmGYa0Z60o26LYrANkUyyerLl8MDI14oIgtl52nuBrQ==",
"cpu": [
"x64"
],
@@ -712,9 +712,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.36-0.tgz",
- "integrity": "sha512-2yIKaU5XdC0xkFXt80pU+Uqr7pU2lHHzcBehzhDHfDeZVNq8jQj61Ka9r9NBjU3W8c3f99ctPMN8gErFnc5L/A==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-Rv2EsthoR40FPn+afObJ+Jef0Lbpb3S6TAKNz+1MHv71hlVVxNKBVCGXVCKIehVgwE8rQGKz+pTy2+Gbprim9A==",
"cpu": [
"arm64"
],
@@ -728,9 +728,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.36-0.tgz",
- "integrity": "sha512-tiEnl40MmEc4uybJ8at9TagmEcMsNHDiqzER72PYAD4mKwWklZ3RAClaVgzQlTxn1tMdNM7gbajyqSivLmo+rA==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.39-0.tgz",
+ "integrity": "sha512-7z8lmFLAVWRgZ7WoSEQsF2XAMeenWU5kgjljhbupDGV1yhW9Ycrx7RhB3cBtmyvmal+OzFjOpYlTiLi0Ul3kwA==",
"cpu": [
"x64"
],
@@ -744,9 +744,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.36-0.tgz",
- "integrity": "sha512-c6hT1lnl7B44tLJGmyugvqPQ51bIMXtTeCb7Z5riPd4Sv17gsU+gLWewJLddAnu+0XhjzlBsIHv9GUtxGPCgWQ==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-HtPnEV+Mt1H1RF54NHQa4qagj7llYkCcnHmc8jzkj810DE8iU4aI2u5K2fmU9/z/hvF1+223bEXRnSKAinyjmw==",
"cpu": [
"arm64"
],
@@ -760,9 +760,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.36-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.36-0.tgz",
- "integrity": "sha512-RJy3RtlX+34denR3+ttBZsbyeaGoA2nlYoEtnijsQquK37on4gTwVavRbvjfVa2pL+aMuKhRD+xdvsUd+Fu4Lg==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.39-0.tgz",
+ "integrity": "sha512-N3Q5G6hDLKeiU+40mgdZk3Sk3b6/+pvNE3Tp5B8LK/Z3CvE2fQKYRXJx8iSDNtP48QwRqwHdrCGQVwDtEtSDAQ==",
"cpu": [
"x64"
],
diff --git a/nodejs/package.json b/nodejs/package.json
index a7caa354a..2c7b12e1f 100644
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -56,7 +56,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.36-0",
+ "@github/copilot": "^1.0.39-0",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json
index 32128f5e7..0e97a2e36 100644
--- a/nodejs/samples/package-lock.json
+++ b/nodejs/samples/package-lock.json
@@ -18,7 +18,7 @@
"version": "0.1.8",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.32",
+ "@github/copilot": "^1.0.39-0",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts
index 6ee14deed..d792e2aca 100644
--- a/nodejs/src/generated/rpc.ts
+++ b/nodejs/src/generated/rpc.ts
@@ -171,6 +171,43 @@ export type SessionFsSetProviderConventions = "windows" | "posix";
* via the `definition` "ShellKillSignal".
*/
export type ShellKillSignal = "SIGTERM" | "SIGKILL" | "SIGINT";
+/**
+ * Current lifecycle status of the task
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "TaskAgentInfoStatus".
+ */
+export type TaskAgentInfoStatus = "running" | "idle" | "completed" | "failed" | "cancelled";
+/**
+ * How the agent is currently being managed by the runtime
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "TaskAgentInfoExecutionMode".
+ */
+export type TaskAgentInfoExecutionMode = "sync" | "background";
+
+export type TaskInfo = TaskAgentInfo | TaskShellInfo;
+/**
+ * Current lifecycle status of the task
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "TaskShellInfoStatus".
+ */
+export type TaskShellInfoStatus = "running" | "idle" | "completed" | "failed" | "cancelled";
+/**
+ * Whether the shell runs inside a managed PTY session or as an independent background process
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "TaskShellInfoAttachmentMode".
+ */
+export type TaskShellInfoAttachmentMode = "attached" | "detached";
+/**
+ * Whether the shell command is currently sync-waited or background-managed
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "TaskShellInfoExecutionMode".
+ */
+export type TaskShellInfoExecutionMode = "sync" | "background";
/**
* Tool call result (string or expanded result object)
*
@@ -273,6 +310,10 @@ export interface AgentInfo {
* Description of the agent's purpose
*/
description: string;
+ /**
+ * Absolute local file path of the agent definition. Only set for file-based agents loaded from disk; remote agents do not have a path.
+ */
+ path?: string;
}
/** @experimental */
@@ -901,7 +942,7 @@ export interface ModeSetRequest {
export interface NameGetResult {
/**
- * The session name, falling back to the auto-generated summary, or null if neither exists
+ * The session name (user-set or auto-generated), or null if not yet set
*/
name: string | null;
}
@@ -1562,6 +1603,205 @@ export interface SkillsEnableRequest {
name: string;
}
+export interface TaskAgentInfo {
+ /**
+ * Task kind
+ */
+ type: "agent";
+ /**
+ * Unique task identifier
+ */
+ id: string;
+ /**
+ * Tool call ID associated with this agent task
+ */
+ toolCallId: string;
+ /**
+ * Short description of the task
+ */
+ description: string;
+ status: TaskAgentInfoStatus;
+ /**
+ * ISO 8601 timestamp when the task was started
+ */
+ startedAt: string;
+ /**
+ * ISO 8601 timestamp when the task finished
+ */
+ completedAt?: string;
+ /**
+ * Accumulated active execution time in milliseconds
+ */
+ activeTimeMs?: number;
+ /**
+ * ISO 8601 timestamp when the current active period began
+ */
+ activeStartedAt?: string;
+ /**
+ * Error message when the task failed
+ */
+ error?: string;
+ /**
+ * Type of agent running this task
+ */
+ agentType: string;
+ /**
+ * Prompt passed to the agent
+ */
+ prompt: string;
+ /**
+ * Result text from the task when available
+ */
+ result?: string;
+ /**
+ * Model used for the task when specified
+ */
+ model?: string;
+ executionMode?: TaskAgentInfoExecutionMode;
+ /**
+ * Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter.
+ */
+ canPromoteToBackground?: boolean;
+ /**
+ * Most recent response text from the agent
+ */
+ latestResponse?: string;
+ /**
+ * ISO 8601 timestamp when the agent entered idle state
+ */
+ idleSince?: string;
+}
+
+export interface TaskShellInfo {
+ /**
+ * Task kind
+ */
+ type: "shell";
+ /**
+ * Unique task identifier
+ */
+ id: string;
+ /**
+ * Short description of the task
+ */
+ description: string;
+ status: TaskShellInfoStatus;
+ /**
+ * ISO 8601 timestamp when the task was started
+ */
+ startedAt: string;
+ /**
+ * ISO 8601 timestamp when the task finished
+ */
+ completedAt?: string;
+ /**
+ * Command being executed
+ */
+ command: string;
+ attachmentMode: TaskShellInfoAttachmentMode;
+ executionMode?: TaskShellInfoExecutionMode;
+ /**
+ * Whether this shell task can be promoted to background mode
+ */
+ canPromoteToBackground?: boolean;
+ /**
+ * Path to the detached shell log, when available
+ */
+ logPath?: string;
+ /**
+ * Process ID when available
+ */
+ pid?: number;
+}
+
+/** @experimental */
+export interface TaskList {
+ /**
+ * Currently tracked tasks
+ */
+ tasks: TaskInfo[];
+}
+
+/** @experimental */
+export interface TasksCancelRequest {
+ /**
+ * Task identifier
+ */
+ id: string;
+}
+
+/** @experimental */
+export interface TasksCancelResult {
+ /**
+ * Whether the task was successfully cancelled
+ */
+ cancelled: boolean;
+}
+
+/** @experimental */
+export interface TasksPromoteToBackgroundRequest {
+ /**
+ * Task identifier
+ */
+ id: string;
+}
+
+/** @experimental */
+export interface TasksPromoteToBackgroundResult {
+ /**
+ * Whether the task was successfully promoted to background mode
+ */
+ promoted: boolean;
+}
+
+/** @experimental */
+export interface TasksRemoveRequest {
+ /**
+ * Task identifier
+ */
+ id: string;
+}
+
+/** @experimental */
+export interface TasksRemoveResult {
+ /**
+ * Whether the task was removed. Returns false if the task does not exist or is still running/idle (cancel it first).
+ */
+ removed: boolean;
+}
+
+/** @experimental */
+export interface TasksStartAgentRequest {
+ /**
+ * Type of agent to start (e.g., 'explore', 'task', 'general-purpose')
+ */
+ agentType: string;
+ /**
+ * Task prompt for the agent
+ */
+ prompt: string;
+ /**
+ * Short name for the agent, used to generate a human-readable ID
+ */
+ name: string;
+ /**
+ * Short description of the task
+ */
+ description?: string;
+ /**
+ * Optional model override
+ */
+ model?: string;
+}
+
+/** @experimental */
+export interface TasksStartAgentResult {
+ /**
+ * Generated agent ID for the background task
+ */
+ agentId: string;
+}
+
export interface Tool {
/**
* Tool identifier (e.g., "bash", "grep", "str_replace_editor")
@@ -1910,8 +2150,9 @@ export interface WorkspacesGetWorkspaceResult {
repository?: string;
host_type?: "github" | "ado";
branch?: string;
- summary?: string;
name?: string;
+ user_named?: boolean;
+ summary?: string;
summary_count?: number;
created_at?: string;
updated_at?: string;
@@ -2066,6 +2307,19 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
connection.sendRequest("session.agent.reload", { sessionId }),
},
/** @experimental */
+ tasks: {
+ startAgent: async (params: TasksStartAgentRequest): Promise =>
+ connection.sendRequest("session.tasks.startAgent", { sessionId, ...params }),
+ list: async (): Promise =>
+ connection.sendRequest("session.tasks.list", { sessionId }),
+ promoteToBackground: async (params: TasksPromoteToBackgroundRequest): Promise =>
+ connection.sendRequest("session.tasks.promoteToBackground", { sessionId, ...params }),
+ cancel: async (params: TasksCancelRequest): Promise =>
+ connection.sendRequest("session.tasks.cancel", { sessionId, ...params }),
+ remove: async (params: TasksRemoveRequest): Promise =>
+ connection.sendRequest("session.tasks.remove", { sessionId, ...params }),
+ },
+ /** @experimental */
skills: {
list: async (): Promise =>
connection.sendRequest("session.skills.list", { sessionId }),
diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts
index ebaf68a01..ad541ea88 100644
--- a/nodejs/src/generated/session-events.ts
+++ b/nodejs/src/generated/session-events.ts
@@ -36,6 +36,7 @@ export type SessionEvent =
| AssistantMessageDeltaEvent
| AssistantTurnEndEvent
| AssistantUsageEvent
+ | ModelCallFailureEvent
| AbortEvent
| ToolUserRequestedEvent
| ToolExecutionStartEvent
@@ -121,6 +122,10 @@ export type UserMessageAttachmentGithubReferenceType = "issue" | "pr" | "discuss
* Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent.
*/
export type AssistantMessageToolRequestType = "function" | "custom";
+/**
+ * Where the failed model call originated
+ */
+export type ModelCallFailureSource = "top_level" | "subagent" | "mcp_sampling";
/**
* A content block within a tool result, which may be text, terminal output, image, audio, or a resource
*/
@@ -587,6 +592,10 @@ export interface InfoData {
* Human-readable informational message for display in the timeline
*/
message: string;
+ /**
+ * Optional actionable tip displayed with this message
+ */
+ tip?: string;
/**
* Optional URL associated with this message that the user can open in a browser
*/
@@ -2118,6 +2127,61 @@ export interface AssistantUsageQuotaSnapshot {
*/
usedRequests: number;
}
+export interface ModelCallFailureEvent {
+ /**
+ * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events.
+ */
+ agentId?: string;
+ data: ModelCallFailureData;
+ ephemeral: true;
+ /**
+ * Unique event identifier (UUID v4), generated when the event is emitted
+ */
+ id: string;
+ /**
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
+ */
+ parentId: string | null;
+ /**
+ * ISO 8601 timestamp when the event was created
+ */
+ timestamp: string;
+ type: "model.call_failure";
+}
+/**
+ * Failed LLM API call metadata for telemetry
+ */
+export interface ModelCallFailureData {
+ /**
+ * Completion ID from the model provider (e.g., chatcmpl-abc123)
+ */
+ apiCallId?: string;
+ /**
+ * Duration of the failed API call in milliseconds
+ */
+ durationMs?: number;
+ /**
+ * Raw provider/runtime error message for restricted telemetry
+ */
+ errorMessage?: string;
+ /**
+ * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls
+ */
+ initiator?: string;
+ /**
+ * Model identifier used for the failed API call
+ */
+ model?: string;
+ /**
+ * GitHub request tracing ID (x-github-request-id header) for server-side log correlation
+ */
+ providerCallId?: string;
+ source: ModelCallFailureSource;
+ /**
+ * HTTP status code from the failed request
+ */
+ statusCode?: number;
+}
export interface AbortEvent {
/**
* Sub-agent instance identifier. Absent for events from the root/main agent and session-level events.
@@ -3097,7 +3161,7 @@ export interface SystemNotificationNewInboxMessage {
*/
senderName: string;
/**
- * Category of the sender (e.g., ambient-agent, plugin, hook)
+ * Category of the sender (e.g., sidekick-agent, plugin, hook)
*/
senderType: string;
/**
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index 00eaae928..397e3166a 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -156,19 +156,27 @@ class AgentInfo:
name: str
"""Unique identifier of the custom agent"""
+ path: str | None = None
+ """Absolute local file path of the agent definition. Only set for file-based agents loaded
+ from disk; remote agents do not have a path.
+ """
+
@staticmethod
def from_dict(obj: Any) -> 'AgentInfo':
assert isinstance(obj, dict)
description = from_str(obj.get("description"))
display_name = from_str(obj.get("displayName"))
name = from_str(obj.get("name"))
- return AgentInfo(description, display_name, name)
+ path = from_union([from_str, from_none], obj.get("path"))
+ return AgentInfo(description, display_name, name, path)
def to_dict(self) -> dict:
result: dict = {}
result["description"] = from_str(self.description)
result["displayName"] = from_str(self.display_name)
result["name"] = from_str(self.name)
+ if self.path is not None:
+ result["path"] = from_union([from_str, from_none], self.path)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -884,7 +892,7 @@ def to_dict(self) -> dict:
@dataclass
class NameGetResult:
name: str | None = None
- """The session name, falling back to the auto-generated summary, or null if neither exists"""
+ """The session name (user-set or auto-generated), or null if not yet set"""
@staticmethod
def from_dict(obj: Any) -> 'NameGetResult':
@@ -1740,6 +1748,200 @@ def to_dict(self) -> dict:
result["name"] = from_str(self.name)
return result
+class TaskInfoExecutionMode(Enum):
+ """How the agent is currently being managed by the runtime
+
+ Whether the shell command is currently sync-waited or background-managed
+ """
+ BACKGROUND = "background"
+ SYNC = "sync"
+
+class TaskInfoStatus(Enum):
+ """Current lifecycle status of the task"""
+
+ CANCELLED = "cancelled"
+ COMPLETED = "completed"
+ FAILED = "failed"
+ IDLE = "idle"
+ RUNNING = "running"
+
+class TaskAgentInfoType(Enum):
+ AGENT = "agent"
+
+class TaskShellInfoAttachmentMode(Enum):
+ """Whether the shell runs inside a managed PTY session or as an independent background
+ process
+ """
+ ATTACHED = "attached"
+ DETACHED = "detached"
+
+class TaskInfoType(Enum):
+ AGENT = "agent"
+ SHELL = "shell"
+
+class TaskShellInfoType(Enum):
+ SHELL = "shell"
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksCancelRequest:
+ id: str
+ """Task identifier"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksCancelRequest':
+ assert isinstance(obj, dict)
+ id = from_str(obj.get("id"))
+ return TasksCancelRequest(id)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["id"] = from_str(self.id)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksCancelResult:
+ cancelled: bool
+ """Whether the task was successfully cancelled"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksCancelResult':
+ assert isinstance(obj, dict)
+ cancelled = from_bool(obj.get("cancelled"))
+ return TasksCancelResult(cancelled)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["cancelled"] = from_bool(self.cancelled)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksPromoteToBackgroundRequest:
+ id: str
+ """Task identifier"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksPromoteToBackgroundRequest':
+ assert isinstance(obj, dict)
+ id = from_str(obj.get("id"))
+ return TasksPromoteToBackgroundRequest(id)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["id"] = from_str(self.id)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksPromoteToBackgroundResult:
+ promoted: bool
+ """Whether the task was successfully promoted to background mode"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksPromoteToBackgroundResult':
+ assert isinstance(obj, dict)
+ promoted = from_bool(obj.get("promoted"))
+ return TasksPromoteToBackgroundResult(promoted)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["promoted"] = from_bool(self.promoted)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksRemoveRequest:
+ id: str
+ """Task identifier"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksRemoveRequest':
+ assert isinstance(obj, dict)
+ id = from_str(obj.get("id"))
+ return TasksRemoveRequest(id)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["id"] = from_str(self.id)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksRemoveResult:
+ removed: bool
+ """Whether the task was removed. Returns false if the task does not exist or is still
+ running/idle (cancel it first).
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksRemoveResult':
+ assert isinstance(obj, dict)
+ removed = from_bool(obj.get("removed"))
+ return TasksRemoveResult(removed)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["removed"] = from_bool(self.removed)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksStartAgentRequest:
+ agent_type: str
+ """Type of agent to start (e.g., 'explore', 'task', 'general-purpose')"""
+
+ name: str
+ """Short name for the agent, used to generate a human-readable ID"""
+
+ prompt: str
+ """Task prompt for the agent"""
+
+ description: str | None = None
+ """Short description of the task"""
+
+ model: str | None = None
+ """Optional model override"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksStartAgentRequest':
+ assert isinstance(obj, dict)
+ agent_type = from_str(obj.get("agentType"))
+ name = from_str(obj.get("name"))
+ prompt = from_str(obj.get("prompt"))
+ description = from_union([from_str, from_none], obj.get("description"))
+ model = from_union([from_str, from_none], obj.get("model"))
+ return TasksStartAgentRequest(agent_type, name, prompt, description, model)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["agentType"] = from_str(self.agent_type)
+ result["name"] = from_str(self.name)
+ result["prompt"] = from_str(self.prompt)
+ if self.description is not None:
+ result["description"] = from_union([from_str, from_none], self.description)
+ if self.model is not None:
+ result["model"] = from_union([from_str, from_none], self.model)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TasksStartAgentResult:
+ agent_id: str
+ """Generated agent ID for the background task"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TasksStartAgentResult':
+ assert isinstance(obj, dict)
+ agent_id = from_str(obj.get("agentId"))
+ return TasksStartAgentResult(agent_id)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["agentId"] = from_str(self.agent_id)
+ return result
+
@dataclass
class Tool:
description: str
@@ -3230,6 +3432,83 @@ def to_dict(self) -> dict:
result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills)
return result
+@dataclass
+class TaskShellInfo:
+ attachment_mode: TaskShellInfoAttachmentMode
+ """Whether the shell runs inside a managed PTY session or as an independent background
+ process
+ """
+ command: str
+ """Command being executed"""
+
+ description: str
+ """Short description of the task"""
+
+ id: str
+ """Unique task identifier"""
+
+ started_at: datetime
+ """ISO 8601 timestamp when the task was started"""
+
+ status: TaskInfoStatus
+ """Current lifecycle status of the task"""
+
+ type: TaskShellInfoType
+ """Task kind"""
+
+ can_promote_to_background: bool | None = None
+ """Whether this shell task can be promoted to background mode"""
+
+ completed_at: datetime | None = None
+ """ISO 8601 timestamp when the task finished"""
+
+ execution_mode: TaskInfoExecutionMode | None = None
+ """Whether the shell command is currently sync-waited or background-managed"""
+
+ log_path: str | None = None
+ """Path to the detached shell log, when available"""
+
+ pid: int | None = None
+ """Process ID when available"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TaskShellInfo':
+ assert isinstance(obj, dict)
+ attachment_mode = TaskShellInfoAttachmentMode(obj.get("attachmentMode"))
+ command = from_str(obj.get("command"))
+ description = from_str(obj.get("description"))
+ id = from_str(obj.get("id"))
+ started_at = from_datetime(obj.get("startedAt"))
+ status = TaskInfoStatus(obj.get("status"))
+ type = TaskShellInfoType(obj.get("type"))
+ can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
+ completed_at = from_union([from_datetime, from_none], obj.get("completedAt"))
+ execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode"))
+ log_path = from_union([from_str, from_none], obj.get("logPath"))
+ pid = from_union([from_int, from_none], obj.get("pid"))
+ return TaskShellInfo(attachment_mode, command, description, id, started_at, status, type, can_promote_to_background, completed_at, execution_mode, log_path, pid)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["attachmentMode"] = to_enum(TaskShellInfoAttachmentMode, self.attachment_mode)
+ result["command"] = from_str(self.command)
+ result["description"] = from_str(self.description)
+ result["id"] = from_str(self.id)
+ result["startedAt"] = self.started_at.isoformat()
+ result["status"] = to_enum(TaskInfoStatus, self.status)
+ result["type"] = to_enum(TaskShellInfoType, self.type)
+ if self.can_promote_to_background is not None:
+ result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
+ if self.completed_at is not None:
+ result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at)
+ if self.execution_mode is not None:
+ result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode)
+ if self.log_path is not None:
+ result["logPath"] = from_union([from_str, from_none], self.log_path)
+ if self.pid is not None:
+ result["pid"] = from_union([from_int, from_none], self.pid)
+ return result
+
@dataclass
class ToolList:
tools: list[Tool]
@@ -3560,6 +3839,7 @@ class Workspace:
summary: str | None = None
summary_count: int | None = None
updated_at: datetime | None = None
+ user_named: bool | None = None
@staticmethod
def from_dict(obj: Any) -> 'Workspace':
@@ -3581,7 +3861,8 @@ def from_dict(obj: Any) -> 'Workspace':
summary = from_union([from_str, from_none], obj.get("summary"))
summary_count = from_union([from_int, from_none], obj.get("summary_count"))
updated_at = from_union([from_datetime, from_none], obj.get("updated_at"))
- return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at)
+ user_named = from_union([from_bool, from_none], obj.get("user_named"))
+ return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at, user_named)
def to_dict(self) -> dict:
result: dict = {}
@@ -3618,6 +3899,8 @@ def to_dict(self) -> dict:
result["summary_count"] = from_union([from_int, from_none], self.summary_count)
if self.updated_at is not None:
result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at)
+ if self.user_named is not None:
+ result["user_named"] = from_union([from_bool, from_none], self.user_named)
return result
@dataclass
@@ -4399,6 +4682,281 @@ def to_dict(self) -> dict:
result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort)
return result
+@dataclass
+class TaskAgentInfo:
+ agent_type: str
+ """Type of agent running this task"""
+
+ description: str
+ """Short description of the task"""
+
+ id: str
+ """Unique task identifier"""
+
+ prompt: str
+ """Prompt passed to the agent"""
+
+ started_at: datetime
+ """ISO 8601 timestamp when the task was started"""
+
+ status: TaskInfoStatus
+ """Current lifecycle status of the task"""
+
+ tool_call_id: str
+ """Tool call ID associated with this agent task"""
+
+ type: TaskAgentInfoType
+ """Task kind"""
+
+ active_started_at: datetime | None = None
+ """ISO 8601 timestamp when the current active period began"""
+
+ active_time_ms: int | None = None
+ """Accumulated active execution time in milliseconds"""
+
+ can_promote_to_background: bool | None = None
+ """Whether the task is currently in the original sync wait and can be moved to background
+ mode. False once it is already backgrounded, idle, finished, or no longer has a
+ promotable sync waiter.
+ """
+ completed_at: datetime | None = None
+ """ISO 8601 timestamp when the task finished"""
+
+ error: str | None = None
+ """Error message when the task failed"""
+
+ execution_mode: TaskInfoExecutionMode | None = None
+ """How the agent is currently being managed by the runtime"""
+
+ idle_since: datetime | None = None
+ """ISO 8601 timestamp when the agent entered idle state"""
+
+ latest_response: str | None = None
+ """Most recent response text from the agent"""
+
+ model: str | None = None
+ """Model used for the task when specified"""
+
+ result: str | None = None
+ """Result text from the task when available"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TaskAgentInfo':
+ assert isinstance(obj, dict)
+ agent_type = from_str(obj.get("agentType"))
+ description = from_str(obj.get("description"))
+ id = from_str(obj.get("id"))
+ prompt = from_str(obj.get("prompt"))
+ started_at = from_datetime(obj.get("startedAt"))
+ status = TaskInfoStatus(obj.get("status"))
+ tool_call_id = from_str(obj.get("toolCallId"))
+ type = TaskAgentInfoType(obj.get("type"))
+ active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt"))
+ active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs"))
+ can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
+ completed_at = from_union([from_datetime, from_none], obj.get("completedAt"))
+ error = from_union([from_str, from_none], obj.get("error"))
+ execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode"))
+ idle_since = from_union([from_datetime, from_none], obj.get("idleSince"))
+ latest_response = from_union([from_str, from_none], obj.get("latestResponse"))
+ model = from_union([from_str, from_none], obj.get("model"))
+ result = from_union([from_str, from_none], obj.get("result"))
+ return TaskAgentInfo(agent_type, description, id, prompt, started_at, status, tool_call_id, type, active_started_at, active_time_ms, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, result)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["agentType"] = from_str(self.agent_type)
+ result["description"] = from_str(self.description)
+ result["id"] = from_str(self.id)
+ result["prompt"] = from_str(self.prompt)
+ result["startedAt"] = self.started_at.isoformat()
+ result["status"] = to_enum(TaskInfoStatus, self.status)
+ result["toolCallId"] = from_str(self.tool_call_id)
+ result["type"] = to_enum(TaskAgentInfoType, self.type)
+ if self.active_started_at is not None:
+ result["activeStartedAt"] = from_union([lambda x: x.isoformat(), from_none], self.active_started_at)
+ if self.active_time_ms is not None:
+ result["activeTimeMs"] = from_union([from_int, from_none], self.active_time_ms)
+ if self.can_promote_to_background is not None:
+ result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
+ if self.completed_at is not None:
+ result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at)
+ if self.error is not None:
+ result["error"] = from_union([from_str, from_none], self.error)
+ if self.execution_mode is not None:
+ result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode)
+ if self.idle_since is not None:
+ result["idleSince"] = from_union([lambda x: x.isoformat(), from_none], self.idle_since)
+ if self.latest_response is not None:
+ result["latestResponse"] = from_union([from_str, from_none], self.latest_response)
+ if self.model is not None:
+ result["model"] = from_union([from_str, from_none], self.model)
+ if self.result is not None:
+ result["result"] = from_union([from_str, from_none], self.result)
+ return result
+
+@dataclass
+class TaskInfo:
+ description: str
+ """Short description of the task"""
+
+ id: str
+ """Unique task identifier"""
+
+ started_at: datetime
+ """ISO 8601 timestamp when the task was started"""
+
+ status: TaskInfoStatus
+ """Current lifecycle status of the task"""
+
+ type: TaskInfoType
+ """Task kind"""
+
+ active_started_at: datetime | None = None
+ """ISO 8601 timestamp when the current active period began"""
+
+ active_time_ms: int | None = None
+ """Accumulated active execution time in milliseconds"""
+
+ agent_type: str | None = None
+ """Type of agent running this task"""
+
+ can_promote_to_background: bool | None = None
+ """Whether the task is currently in the original sync wait and can be moved to background
+ mode. False once it is already backgrounded, idle, finished, or no longer has a
+ promotable sync waiter.
+
+ Whether this shell task can be promoted to background mode
+ """
+ completed_at: datetime | None = None
+ """ISO 8601 timestamp when the task finished"""
+
+ error: str | None = None
+ """Error message when the task failed"""
+
+ execution_mode: TaskInfoExecutionMode | None = None
+ """How the agent is currently being managed by the runtime
+
+ Whether the shell command is currently sync-waited or background-managed
+ """
+ idle_since: datetime | None = None
+ """ISO 8601 timestamp when the agent entered idle state"""
+
+ latest_response: str | None = None
+ """Most recent response text from the agent"""
+
+ model: str | None = None
+ """Model used for the task when specified"""
+
+ prompt: str | None = None
+ """Prompt passed to the agent"""
+
+ result: str | None = None
+ """Result text from the task when available"""
+
+ tool_call_id: str | None = None
+ """Tool call ID associated with this agent task"""
+
+ attachment_mode: TaskShellInfoAttachmentMode | None = None
+ """Whether the shell runs inside a managed PTY session or as an independent background
+ process
+ """
+ command: str | None = None
+ """Command being executed"""
+
+ log_path: str | None = None
+ """Path to the detached shell log, when available"""
+
+ pid: int | None = None
+ """Process ID when available"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TaskInfo':
+ assert isinstance(obj, dict)
+ description = from_str(obj.get("description"))
+ id = from_str(obj.get("id"))
+ started_at = from_datetime(obj.get("startedAt"))
+ status = TaskInfoStatus(obj.get("status"))
+ type = TaskInfoType(obj.get("type"))
+ active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt"))
+ active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs"))
+ agent_type = from_union([from_str, from_none], obj.get("agentType"))
+ can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground"))
+ completed_at = from_union([from_datetime, from_none], obj.get("completedAt"))
+ error = from_union([from_str, from_none], obj.get("error"))
+ execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode"))
+ idle_since = from_union([from_datetime, from_none], obj.get("idleSince"))
+ latest_response = from_union([from_str, from_none], obj.get("latestResponse"))
+ model = from_union([from_str, from_none], obj.get("model"))
+ prompt = from_union([from_str, from_none], obj.get("prompt"))
+ result = from_union([from_str, from_none], obj.get("result"))
+ tool_call_id = from_union([from_str, from_none], obj.get("toolCallId"))
+ attachment_mode = from_union([TaskShellInfoAttachmentMode, from_none], obj.get("attachmentMode"))
+ command = from_union([from_str, from_none], obj.get("command"))
+ log_path = from_union([from_str, from_none], obj.get("logPath"))
+ pid = from_union([from_int, from_none], obj.get("pid"))
+ return TaskInfo(description, id, started_at, status, type, active_started_at, active_time_ms, agent_type, can_promote_to_background, completed_at, error, execution_mode, idle_since, latest_response, model, prompt, result, tool_call_id, attachment_mode, command, log_path, pid)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["description"] = from_str(self.description)
+ result["id"] = from_str(self.id)
+ result["startedAt"] = self.started_at.isoformat()
+ result["status"] = to_enum(TaskInfoStatus, self.status)
+ result["type"] = to_enum(TaskInfoType, self.type)
+ if self.active_started_at is not None:
+ result["activeStartedAt"] = from_union([lambda x: x.isoformat(), from_none], self.active_started_at)
+ if self.active_time_ms is not None:
+ result["activeTimeMs"] = from_union([from_int, from_none], self.active_time_ms)
+ if self.agent_type is not None:
+ result["agentType"] = from_union([from_str, from_none], self.agent_type)
+ if self.can_promote_to_background is not None:
+ result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background)
+ if self.completed_at is not None:
+ result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at)
+ if self.error is not None:
+ result["error"] = from_union([from_str, from_none], self.error)
+ if self.execution_mode is not None:
+ result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode)
+ if self.idle_since is not None:
+ result["idleSince"] = from_union([lambda x: x.isoformat(), from_none], self.idle_since)
+ if self.latest_response is not None:
+ result["latestResponse"] = from_union([from_str, from_none], self.latest_response)
+ if self.model is not None:
+ result["model"] = from_union([from_str, from_none], self.model)
+ if self.prompt is not None:
+ result["prompt"] = from_union([from_str, from_none], self.prompt)
+ if self.result is not None:
+ result["result"] = from_union([from_str, from_none], self.result)
+ if self.tool_call_id is not None:
+ result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id)
+ if self.attachment_mode is not None:
+ result["attachmentMode"] = from_union([lambda x: to_enum(TaskShellInfoAttachmentMode, x), from_none], self.attachment_mode)
+ if self.command is not None:
+ result["command"] = from_union([from_str, from_none], self.command)
+ if self.log_path is not None:
+ result["logPath"] = from_union([from_str, from_none], self.log_path)
+ if self.pid is not None:
+ result["pid"] = from_union([from_int, from_none], self.pid)
+ return result
+
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class TaskList:
+ tasks: list[TaskInfo]
+ """Currently tracked tasks"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'TaskList':
+ assert isinstance(obj, dict)
+ tasks = from_list(TaskInfo.from_dict, obj.get("tasks"))
+ return TaskList(tasks)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["tasks"] = from_list(lambda x: to_class(TaskInfo, x), self.tasks)
+ return result
+
@dataclass
class RPC:
account_get_quota_request: AccountGetQuotaRequest
@@ -4552,6 +5110,23 @@ class RPC:
skills_disable_request: SkillsDisableRequest
skills_discover_request: SkillsDiscoverRequest
skills_enable_request: SkillsEnableRequest
+ task_agent_info: TaskAgentInfo
+ task_agent_info_execution_mode: TaskInfoExecutionMode
+ task_agent_info_status: TaskInfoStatus
+ task_info: TaskInfo
+ task_list: TaskList
+ tasks_cancel_request: TasksCancelRequest
+ tasks_cancel_result: TasksCancelResult
+ task_shell_info: TaskShellInfo
+ task_shell_info_attachment_mode: TaskShellInfoAttachmentMode
+ task_shell_info_execution_mode: TaskInfoExecutionMode
+ task_shell_info_status: TaskInfoStatus
+ tasks_promote_to_background_request: TasksPromoteToBackgroundRequest
+ tasks_promote_to_background_result: TasksPromoteToBackgroundResult
+ tasks_remove_request: TasksRemoveRequest
+ tasks_remove_result: TasksRemoveResult
+ tasks_start_agent_request: TasksStartAgentRequest
+ tasks_start_agent_result: TasksStartAgentResult
tool: Tool
tool_call_result: ToolCallResult
tool_list: ToolList
@@ -4745,6 +5320,23 @@ def from_dict(obj: Any) -> 'RPC':
skills_disable_request = SkillsDisableRequest.from_dict(obj.get("SkillsDisableRequest"))
skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest"))
skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest"))
+ task_agent_info = TaskAgentInfo.from_dict(obj.get("TaskAgentInfo"))
+ task_agent_info_execution_mode = TaskInfoExecutionMode(obj.get("TaskAgentInfoExecutionMode"))
+ task_agent_info_status = TaskInfoStatus(obj.get("TaskAgentInfoStatus"))
+ task_info = TaskInfo.from_dict(obj.get("TaskInfo"))
+ task_list = TaskList.from_dict(obj.get("TaskList"))
+ tasks_cancel_request = TasksCancelRequest.from_dict(obj.get("TasksCancelRequest"))
+ tasks_cancel_result = TasksCancelResult.from_dict(obj.get("TasksCancelResult"))
+ task_shell_info = TaskShellInfo.from_dict(obj.get("TaskShellInfo"))
+ task_shell_info_attachment_mode = TaskShellInfoAttachmentMode(obj.get("TaskShellInfoAttachmentMode"))
+ task_shell_info_execution_mode = TaskInfoExecutionMode(obj.get("TaskShellInfoExecutionMode"))
+ task_shell_info_status = TaskInfoStatus(obj.get("TaskShellInfoStatus"))
+ tasks_promote_to_background_request = TasksPromoteToBackgroundRequest.from_dict(obj.get("TasksPromoteToBackgroundRequest"))
+ tasks_promote_to_background_result = TasksPromoteToBackgroundResult.from_dict(obj.get("TasksPromoteToBackgroundResult"))
+ tasks_remove_request = TasksRemoveRequest.from_dict(obj.get("TasksRemoveRequest"))
+ tasks_remove_result = TasksRemoveResult.from_dict(obj.get("TasksRemoveResult"))
+ tasks_start_agent_request = TasksStartAgentRequest.from_dict(obj.get("TasksStartAgentRequest"))
+ tasks_start_agent_result = TasksStartAgentResult.from_dict(obj.get("TasksStartAgentResult"))
tool = Tool.from_dict(obj.get("Tool"))
tool_call_result = ToolCallResult.from_dict(obj.get("ToolCallResult"))
tool_list = ToolList.from_dict(obj.get("ToolList"))
@@ -4783,7 +5375,7 @@ def from_dict(obj: Any) -> 'RPC':
workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult"))
workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest"))
workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult"))
- return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result)
+ return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, task_agent_info, task_agent_info_execution_mode, task_agent_info_status, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, task_shell_info_execution_mode, task_shell_info_status, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result)
def to_dict(self) -> dict:
result: dict = {}
@@ -4938,6 +5530,23 @@ def to_dict(self) -> dict:
result["SkillsDisableRequest"] = to_class(SkillsDisableRequest, self.skills_disable_request)
result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request)
result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request)
+ result["TaskAgentInfo"] = to_class(TaskAgentInfo, self.task_agent_info)
+ result["TaskAgentInfoExecutionMode"] = to_enum(TaskInfoExecutionMode, self.task_agent_info_execution_mode)
+ result["TaskAgentInfoStatus"] = to_enum(TaskInfoStatus, self.task_agent_info_status)
+ result["TaskInfo"] = to_class(TaskInfo, self.task_info)
+ result["TaskList"] = to_class(TaskList, self.task_list)
+ result["TasksCancelRequest"] = to_class(TasksCancelRequest, self.tasks_cancel_request)
+ result["TasksCancelResult"] = to_class(TasksCancelResult, self.tasks_cancel_result)
+ result["TaskShellInfo"] = to_class(TaskShellInfo, self.task_shell_info)
+ result["TaskShellInfoAttachmentMode"] = to_enum(TaskShellInfoAttachmentMode, self.task_shell_info_attachment_mode)
+ result["TaskShellInfoExecutionMode"] = to_enum(TaskInfoExecutionMode, self.task_shell_info_execution_mode)
+ result["TaskShellInfoStatus"] = to_enum(TaskInfoStatus, self.task_shell_info_status)
+ result["TasksPromoteToBackgroundRequest"] = to_class(TasksPromoteToBackgroundRequest, self.tasks_promote_to_background_request)
+ result["TasksPromoteToBackgroundResult"] = to_class(TasksPromoteToBackgroundResult, self.tasks_promote_to_background_result)
+ result["TasksRemoveRequest"] = to_class(TasksRemoveRequest, self.tasks_remove_request)
+ result["TasksRemoveResult"] = to_class(TasksRemoveResult, self.tasks_remove_result)
+ result["TasksStartAgentRequest"] = to_class(TasksStartAgentRequest, self.tasks_start_agent_request)
+ result["TasksStartAgentResult"] = to_class(TasksStartAgentResult, self.tasks_start_agent_result)
result["Tool"] = to_class(Tool, self.tool)
result["ToolCallResult"] = to_class(ToolCallResult, self.tool_call_result)
result["ToolList"] = to_class(ToolList, self.tool_list)
@@ -5268,6 +5877,36 @@ async def reload(self, *, timeout: float | None = None) -> AgentReloadResult:
return AgentReloadResult.from_dict(await self._client.request("session.agent.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+# Experimental: this API group is experimental and may change or be removed.
+class TasksApi:
+ def __init__(self, client: "JsonRpcClient", session_id: str):
+ self._client = client
+ self._session_id = session_id
+
+ async def start_agent(self, params: TasksStartAgentRequest, *, timeout: float | None = None) -> TasksStartAgentResult:
+ params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
+ params_dict["sessionId"] = self._session_id
+ return TasksStartAgentResult.from_dict(await self._client.request("session.tasks.startAgent", params_dict, **_timeout_kwargs(timeout)))
+
+ async def list(self, *, timeout: float | None = None) -> TaskList:
+ return TaskList.from_dict(await self._client.request("session.tasks.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+
+ async def promote_to_background(self, params: TasksPromoteToBackgroundRequest, *, timeout: float | None = None) -> TasksPromoteToBackgroundResult:
+ params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
+ params_dict["sessionId"] = self._session_id
+ return TasksPromoteToBackgroundResult.from_dict(await self._client.request("session.tasks.promoteToBackground", params_dict, **_timeout_kwargs(timeout)))
+
+ async def cancel(self, params: TasksCancelRequest, *, timeout: float | None = None) -> TasksCancelResult:
+ params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
+ params_dict["sessionId"] = self._session_id
+ return TasksCancelResult.from_dict(await self._client.request("session.tasks.cancel", params_dict, **_timeout_kwargs(timeout)))
+
+ async def remove(self, params: TasksRemoveRequest, *, timeout: float | None = None) -> TasksRemoveResult:
+ params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
+ params_dict["sessionId"] = self._session_id
+ return TasksRemoveResult.from_dict(await self._client.request("session.tasks.remove", params_dict, **_timeout_kwargs(timeout)))
+
+
# Experimental: this API group is experimental and may change or be removed.
class SkillsApi:
def __init__(self, client: "JsonRpcClient", session_id: str):
@@ -5472,6 +6111,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str):
self.instructions = InstructionsApi(client, session_id)
self.fleet = FleetApi(client, session_id)
self.agent = AgentApi(client, session_id)
+ self.tasks = TasksApi(client, session_id)
self.skills = SkillsApi(client, session_id)
self.mcp = McpApi(client, session_id)
self.plugins = PluginsApi(client, session_id)
diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py
index bebed90d9..32599fdc3 100644
--- a/python/copilot/generated/session_events.py
+++ b/python/copilot/generated/session_events.py
@@ -136,6 +136,7 @@ class SessionEventType(Enum):
ASSISTANT_MESSAGE_DELTA = "assistant.message_delta"
ASSISTANT_TURN_END = "assistant.turn_end"
ASSISTANT_USAGE = "assistant.usage"
+ MODEL_CALL_FAILURE = "model.call_failure"
ABORT = "abort"
TOOL_USER_REQUESTED = "tool.user_requested"
TOOL_EXECUTION_START = "tool.execution_start"
@@ -1597,6 +1598,60 @@ def to_dict(self) -> dict:
return result
+@dataclass
+class ModelCallFailureData:
+ "Failed LLM API call metadata for telemetry"
+ source: ModelCallFailureSource
+ api_call_id: str | None = None
+ duration_ms: float | None = None
+ error_message: str | None = None
+ initiator: str | None = None
+ model: str | None = None
+ provider_call_id: str | None = None
+ status_code: int | None = None
+
+ @staticmethod
+ def from_dict(obj: Any) -> "ModelCallFailureData":
+ assert isinstance(obj, dict)
+ source = parse_enum(ModelCallFailureSource, obj.get("source"))
+ api_call_id = from_union([from_none, from_str], obj.get("apiCallId"))
+ duration_ms = from_union([from_none, from_float], obj.get("durationMs"))
+ error_message = from_union([from_none, from_str], obj.get("errorMessage"))
+ initiator = from_union([from_none, from_str], obj.get("initiator"))
+ model = from_union([from_none, from_str], obj.get("model"))
+ provider_call_id = from_union([from_none, from_str], obj.get("providerCallId"))
+ status_code = from_union([from_none, from_int], obj.get("statusCode"))
+ return ModelCallFailureData(
+ source=source,
+ api_call_id=api_call_id,
+ duration_ms=duration_ms,
+ error_message=error_message,
+ initiator=initiator,
+ model=model,
+ provider_call_id=provider_call_id,
+ status_code=status_code,
+ )
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["source"] = to_enum(ModelCallFailureSource, self.source)
+ if self.api_call_id is not None:
+ result["apiCallId"] = from_union([from_none, from_str], self.api_call_id)
+ if self.duration_ms is not None:
+ result["durationMs"] = from_union([from_none, to_float], self.duration_ms)
+ if self.error_message is not None:
+ result["errorMessage"] = from_union([from_none, from_str], self.error_message)
+ if self.initiator is not None:
+ result["initiator"] = from_union([from_none, from_str], self.initiator)
+ if self.model is not None:
+ result["model"] = from_union([from_none, from_str], self.model)
+ if self.provider_call_id is not None:
+ result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id)
+ if self.status_code is not None:
+ result["statusCode"] = from_union([from_none, to_int], self.status_code)
+ return result
+
+
@dataclass
class PendingMessagesModifiedData:
"Empty payload; the event signals that the pending message queue has changed"
@@ -2427,6 +2482,7 @@ class SessionInfoData:
"Informational message for timeline display with categorization"
info_type: str
message: str
+ tip: str | None = None
url: str | None = None
@staticmethod
@@ -2434,10 +2490,12 @@ def from_dict(obj: Any) -> "SessionInfoData":
assert isinstance(obj, dict)
info_type = from_str(obj.get("infoType"))
message = from_str(obj.get("message"))
+ tip = from_union([from_none, from_str], obj.get("tip"))
url = from_union([from_none, from_str], obj.get("url"))
return SessionInfoData(
info_type=info_type,
message=message,
+ tip=tip,
url=url,
)
@@ -2445,6 +2503,8 @@ def to_dict(self) -> dict:
result: dict = {}
result["infoType"] = from_str(self.info_type)
result["message"] = from_str(self.message)
+ if self.tip is not None:
+ result["tip"] = from_union([from_none, from_str], self.tip)
if self.url is not None:
result["url"] = from_union([from_none, from_str], self.url)
return result
@@ -4279,6 +4339,13 @@ class McpServersLoadedServerStatus(Enum):
NOT_CONFIGURED = "not_configured"
+class ModelCallFailureSource(Enum):
+ "Where the failed model call originated"
+ TOP_LEVEL = "top_level"
+ SUBAGENT = "subagent"
+ MCP_SAMPLING = "mcp_sampling"
+
+
class PermissionCompletedKind(Enum):
"The outcome of the permission request"
APPROVED = "approved"
@@ -4433,7 +4500,7 @@ class WorkspaceFileChangedOperation(Enum):
UPDATE = "update"
-SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data
+SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | ModelCallFailureData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data
@dataclass
@@ -4489,6 +4556,7 @@ def from_dict(obj: Any) -> "SessionEvent":
case SessionEventType.ASSISTANT_MESSAGE_DELTA: data = AssistantMessageDeltaData.from_dict(data_obj)
case SessionEventType.ASSISTANT_TURN_END: data = AssistantTurnEndData.from_dict(data_obj)
case SessionEventType.ASSISTANT_USAGE: data = AssistantUsageData.from_dict(data_obj)
+ case SessionEventType.MODEL_CALL_FAILURE: data = ModelCallFailureData.from_dict(data_obj)
case SessionEventType.ABORT: data = AbortData.from_dict(data_obj)
case SessionEventType.TOOL_USER_REQUESTED: data = ToolUserRequestedData.from_dict(data_obj)
case SessionEventType.TOOL_EXECUTION_START: data = ToolExecutionStartData.from_dict(data_obj)
diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json
index 51538fe1d..ea6f5be9c 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": "^1.0.32",
+ "@github/copilot": "^1.0.39-0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"openai": "^6.17.0",
@@ -462,27 +462,27 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32.tgz",
- "integrity": "sha512-ydEYAztJQa1sLQw+WPmnkkt3Sf/k2Smn/7szzYvt1feUOdNIak1gHpQhKcgPr2w252gjVLRWjOiynoeLVW0Fbw==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.39-0.tgz",
+ "integrity": "sha512-OuN6wGgUv0WQydOCUuhYRFwUwTkfktI9fGdSih+SKUE+nTZze8JBz8Sg68K0ZLlqdD0OcF0ac9wMAfunlutvsw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.32",
- "@github/copilot-darwin-x64": "1.0.32",
- "@github/copilot-linux-arm64": "1.0.32",
- "@github/copilot-linux-x64": "1.0.32",
- "@github/copilot-win32-arm64": "1.0.32",
- "@github/copilot-win32-x64": "1.0.32"
+ "@github/copilot-darwin-arm64": "1.0.39-0",
+ "@github/copilot-darwin-x64": "1.0.39-0",
+ "@github/copilot-linux-arm64": "1.0.39-0",
+ "@github/copilot-linux-x64": "1.0.39-0",
+ "@github/copilot-win32-arm64": "1.0.39-0",
+ "@github/copilot-win32-x64": "1.0.39-0"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32.tgz",
- "integrity": "sha512-RtGHpnrbP1eVtpzitLqC0jkBlo63PJiByv6W/NTtLw4ZAllumb5kMk8JaTtydKl9DCOHA0wfXbG5/JkGXuQ81g==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-DquiC7DZo+OmP2AtQUW27FCBsMGLshX9MEedWczjDgQ5YK2iMwACQLMeULdURssXJWXjvQQZMTTo0wsow59lnA==",
"cpu": [
"arm64"
],
@@ -497,9 +497,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32.tgz",
- "integrity": "sha512-eyF6uy8gcZ4m/0UdM9UoykMDotZ8hZPJ1xIg0iHy4wrNtkYOaAspAoVpOkm50ODOQAHJ5PVV+9LuT6IoeL+wHQ==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.39-0.tgz",
+ "integrity": "sha512-NPjVkyl6QqYLGWlkqSiegcSUuI59RE3Qt4cOTALGG9TZmGYa0Z60o26LYrANkUyyerLl8MDI14oIgtl52nuBrQ==",
"cpu": [
"x64"
],
@@ -514,9 +514,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32.tgz",
- "integrity": "sha512-acRAu5ehFPnw3hQSIxcmi7wzv8PAYd+nqdxZXizOi++en3QWgez7VEXiKLe9Ukf50iiGReg19yvWV4iDOGC0HQ==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-Rv2EsthoR40FPn+afObJ+Jef0Lbpb3S6TAKNz+1MHv71hlVVxNKBVCGXVCKIehVgwE8rQGKz+pTy2+Gbprim9A==",
"cpu": [
"arm64"
],
@@ -531,9 +531,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32.tgz",
- "integrity": "sha512-lw86YDwkTKwmeVpfnPErDe9DhemrOHN+l92xOU9wQSH5/d+HguXwRb3e4cQjlxsGLS+/fWRGtwf+u2fbQ37avw==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.39-0.tgz",
+ "integrity": "sha512-7z8lmFLAVWRgZ7WoSEQsF2XAMeenWU5kgjljhbupDGV1yhW9Ycrx7RhB3cBtmyvmal+OzFjOpYlTiLi0Ul3kwA==",
"cpu": [
"x64"
],
@@ -548,9 +548,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32.tgz",
- "integrity": "sha512-+eZpuzgBbLHMIzltH541wfbbMy0HEdG91ISzRae3qPCssf3Ad85sat6k7FWTRBSZBFrN7z4yMQm5gROqDJYGSA==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.39-0.tgz",
+ "integrity": "sha512-HtPnEV+Mt1H1RF54NHQa4qagj7llYkCcnHmc8jzkj810DE8iU4aI2u5K2fmU9/z/hvF1+223bEXRnSKAinyjmw==",
"cpu": [
"arm64"
],
@@ -565,9 +565,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.32",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32.tgz",
- "integrity": "sha512-R6SW1dsEVmPMhrN/WRTetS4gVxcuYcxi2zfDPOfcjW3W0iD0Vwpt3MlqwBaU2UL36j+rnTnmiOA+g82FIBCYVg==",
+ "version": "1.0.39-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.39-0.tgz",
+ "integrity": "sha512-N3Q5G6hDLKeiU+40mgdZk3Sk3b6/+pvNE3Tp5B8LK/Z3CvE2fQKYRXJx8iSDNtP48QwRqwHdrCGQVwDtEtSDAQ==",
"cpu": [
"x64"
],
diff --git a/test/harness/package.json b/test/harness/package.json
index af521775b..6b0c60b4e 100644
--- a/test/harness/package.json
+++ b/test/harness/package.json
@@ -11,7 +11,7 @@
"test": "vitest run"
},
"devDependencies": {
- "@github/copilot": "^1.0.32",
+ "@github/copilot": "^1.0.39-0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"openai": "^6.17.0",
From d63e4bad4ec56f6c0b217a7a8d288f5cb9efb12b Mon Sep 17 00:00:00 2001
From: Stephen Toub
Date: Tue, 28 Apr 2026 22:29:40 -0400
Subject: [PATCH 2/4] Fix E2E replay normalization for CLI reminders
Normalize CLI system reminders in replay matching and isolate E2E tests from user-level Copilot state by setting COPILOT_HOME in each SDK harness.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
dotnet/test/Harness/E2ETestContext.cs | 1 +
go/internal/e2e/testharness/context.go | 1 +
nodejs/test/e2e/harness/sdkTestContext.ts | 1 +
python/e2e/test_commands.py | 1 +
python/e2e/test_multi_client.py | 1 +
.../e2e/test_ui_elicitation_multi_client.py | 1 +
python/e2e/testharness/context.py | 1 +
test/harness/replayingCapiProxy.test.ts | 22 +++++++++++++++++++
test/harness/replayingCapiProxy.ts | 1 +
9 files changed, 30 insertions(+)
diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs
index 1d5cf8839..48bbcd6ce 100644
--- a/dotnet/test/Harness/E2ETestContext.cs
+++ b/dotnet/test/Harness/E2ETestContext.cs
@@ -95,6 +95,7 @@ public IReadOnlyDictionary GetEnvironment()
.ToDictionary(e => (string)e.Key, e => e.Value?.ToString());
env["COPILOT_API_URL"] = ProxyUrl;
+ env["COPILOT_HOME"] = HomeDir;
env["XDG_CONFIG_HOME"] = HomeDir;
env["XDG_STATE_HOME"] = HomeDir;
diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go
index 9f73c2267..a2d684706 100644
--- a/go/internal/e2e/testharness/context.go
+++ b/go/internal/e2e/testharness/context.go
@@ -156,6 +156,7 @@ func (c *TestContext) Env() []string {
// Add overrides (later values take precedence in most systems)
env = append(env,
"COPILOT_API_URL="+c.ProxyURL,
+ "COPILOT_HOME="+c.HomeDir,
"XDG_CONFIG_HOME="+c.HomeDir,
"XDG_STATE_HOME="+c.HomeDir,
)
diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts
index d9680a9ba..8a6722a12 100644
--- a/nodejs/test/e2e/harness/sdkTestContext.ts
+++ b/nodejs/test/e2e/harness/sdkTestContext.ts
@@ -37,6 +37,7 @@ export async function createSdkTestContext({
const env = {
...process.env,
COPILOT_API_URL: proxyUrl,
+ COPILOT_HOME: homeDir,
// TODO: I'm not convinced the SDK should default to using whatever config you happen to have in your homedir.
// The SDK config should be independent of the regular CLI app. Likewise it shouldn't mix sessions from the
diff --git a/python/e2e/test_commands.py b/python/e2e/test_commands.py
index f2eb7cdf1..6cc68e246 100644
--- a/python/e2e/test_commands.py
+++ b/python/e2e/test_commands.py
@@ -113,6 +113,7 @@ def _get_env(self) -> dict:
env.update(
{
"COPILOT_API_URL": self.proxy_url,
+ "COPILOT_HOME": self.home_dir,
"XDG_CONFIG_HOME": self.home_dir,
"XDG_STATE_HOME": self.home_dir,
}
diff --git a/python/e2e/test_multi_client.py b/python/e2e/test_multi_client.py
index 3b3b75d0a..386f2eeb0 100644
--- a/python/e2e/test_multi_client.py
+++ b/python/e2e/test_multi_client.py
@@ -129,6 +129,7 @@ def get_env(self) -> dict:
env.update(
{
"COPILOT_API_URL": self.proxy_url,
+ "COPILOT_HOME": self.home_dir,
"XDG_CONFIG_HOME": self.home_dir,
"XDG_STATE_HOME": self.home_dir,
}
diff --git a/python/e2e/test_ui_elicitation_multi_client.py b/python/e2e/test_ui_elicitation_multi_client.py
index 4c63fb6b2..e12202b2f 100644
--- a/python/e2e/test_ui_elicitation_multi_client.py
+++ b/python/e2e/test_ui_elicitation_multi_client.py
@@ -120,6 +120,7 @@ def _get_env(self) -> dict:
env.update(
{
"COPILOT_API_URL": self.proxy_url,
+ "COPILOT_HOME": self.home_dir,
"XDG_CONFIG_HOME": self.home_dir,
"XDG_STATE_HOME": self.home_dir,
}
diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py
index c2028c14a..09a279d0d 100644
--- a/python/e2e/testharness/context.py
+++ b/python/e2e/testharness/context.py
@@ -133,6 +133,7 @@ def get_env(self) -> dict:
env.update(
{
"COPILOT_API_URL": self.proxy_url,
+ "COPILOT_HOME": self.home_dir,
"XDG_CONFIG_HOME": self.home_dir,
"XDG_STATE_HOME": self.home_dir,
}
diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts
index f19674052..0a57c5e01 100644
--- a/test/harness/replayingCapiProxy.test.ts
+++ b/test/harness/replayingCapiProxy.test.ts
@@ -302,6 +302,28 @@ describe("ReplayingCapiProxy", () => {
);
});
+ test("strips system_reminder from user messages", async () => {
+ const requestBody = JSON.stringify({
+ messages: [
+ {
+ role: "user",
+ content:
+ "What is 2+2?\n\n\nNo tables currently exist.\n",
+ },
+ ],
+ });
+ const responseBody = JSON.stringify({
+ choices: [{ message: { role: "assistant", content: "4" } }],
+ });
+
+ const outputPath = await createProxy([
+ { url: "/chat/completions", requestBody, responseBody },
+ ]);
+
+ const result = await readYamlOutput(outputPath);
+ expect(result.conversations[0].messages[0].content).toBe("What is 2+2?");
+ });
+
test("strips agent_instructions from user messages", async () => {
const requestBody = JSON.stringify({
messages: [
diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts
index 967c18084..cc000b13b 100644
--- a/test/harness/replayingCapiProxy.ts
+++ b/test/harness/replayingCapiProxy.ts
@@ -854,6 +854,7 @@ function normalizeUserMessage(content: string): string {
return content
.replace(/.*?<\/current_datetime>/g, "")
.replace(/[\s\S]*?<\/reminder>/g, "")
+ .replace(/[\s\S]*?<\/system_reminder>/g, "")
.replace(/[\s\S]*?<\/agent_instructions>/g, "")
.replace(
/Please create a detailed summary of the conversation so far\. The history is being compacted[\s\S]*/,
From f7f13549b5782877b19a9af978e85f434cd9ded9 Mon Sep 17 00:00:00 2001
From: Stephen Toub
Date: Tue, 28 Apr 2026 22:55:00 -0400
Subject: [PATCH 3/4] Fix SessionFs state path on Unix
Use a writable temp-backed session state path for SessionFs E2E tests on non-Windows so the updated runtime can create its internal state without writing to /session-state. Keep the existing /session-state path on Windows, isolate Node COPILOT_HOME from per-test cleanup, and normalize temp-backed large-output paths for stable snapshots.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
dotnet/test/SessionFsTests.cs | 29 ++++++++++++------
go/internal/e2e/session_fs_test.go | 35 +++++++++++++--------
nodejs/test/e2e/harness/sdkTestContext.ts | 4 ++-
nodejs/test/e2e/session_fs.test.ts | 37 +++++++++++++++++------
python/e2e/test_session_fs.py | 27 +++++++++++------
test/harness/replayingCapiProxy.ts | 13 +++++---
6 files changed, 100 insertions(+), 45 deletions(-)
diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs
index 1d0e6d2e5..a007a6c30 100644
--- a/dotnet/test/SessionFsTests.cs
+++ b/dotnet/test/SessionFsTests.cs
@@ -16,7 +16,7 @@ public class SessionFsTests(E2ETestFixture fixture, ITestOutputHelper output)
private static readonly SessionFsConfig SessionFsConfig = new()
{
InitialCwd = "/",
- SessionStatePath = "/session-state",
+ SessionStatePath = CreateSessionStatePath(),
Conventions = SessionFsSetProviderConventions.Posix,
};
@@ -38,7 +38,7 @@ public async Task Should_Route_File_Operations_Through_The_Session_Fs_Provider()
Assert.Contains("300", msg?.Data.Content ?? string.Empty);
await session.DisposeAsync();
- var eventsPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/events.jsonl");
+ var eventsPath = GetStoredPath(providerRoot, session.SessionId, $"{SessionFsConfig.SessionStatePath}/events.jsonl");
await WaitForConditionAsync(() => File.Exists(eventsPath));
var content = await ReadAllTextSharedAsync(eventsPath);
Assert.Contains("300", content);
@@ -69,7 +69,7 @@ public async Task Should_Load_Session_Data_From_Fs_Provider_On_Resume()
Assert.Contains("100", msg?.Data.Content ?? string.Empty);
await session1.DisposeAsync();
- var eventsPath = GetStoredPath(providerRoot, sessionId, "/session-state/events.jsonl");
+ var eventsPath = GetStoredPath(providerRoot, sessionId, $"{SessionFsConfig.SessionStatePath}/events.jsonl");
await WaitForConditionAsync(() => File.Exists(eventsPath));
var session2 = await client.ResumeSessionAsync(sessionId, new ResumeSessionConfig
@@ -165,11 +165,11 @@ await session.SendAndWaitAsync(new MessageOptions
var messages = await session.GetMessagesAsync();
var toolResult = FindToolCallResult(messages, "get_big_string");
Assert.NotNull(toolResult);
- Assert.Contains("/session-state/temp/", toolResult);
+ Assert.Contains($"{SessionFsConfig.SessionStatePath}/temp/", toolResult);
var match = System.Text.RegularExpressions.Regex.Match(
toolResult!,
- @"([/\\]session-state[/\\]temp[/\\][^\s]+)");
+ $"({System.Text.RegularExpressions.Regex.Escape(SessionFsConfig.SessionStatePath)}/temp/[^\\s]+)");
Assert.True(match.Success);
var fileContent = await ReadAllTextSharedAsync(GetStoredPath(providerRoot, session.SessionId, match.Groups[1].Value));
@@ -206,7 +206,7 @@ public async Task Should_Succeed_With_Compaction_While_Using_SessionFs()
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2+2?" });
- var eventsPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/events.jsonl");
+ var eventsPath = GetStoredPath(providerRoot, session.SessionId, $"{SessionFsConfig.SessionStatePath}/events.jsonl");
await WaitForConditionAsync(() => File.Exists(eventsPath), TimeSpan.FromSeconds(30));
var contentBefore = await ReadAllTextSharedAsync(eventsPath);
Assert.DoesNotContain("checkpointNumber", contentBefore);
@@ -244,13 +244,13 @@ public async Task Should_Write_Workspace_Metadata_Via_SessionFs()
Assert.Contains("56", msg?.Data.Content ?? string.Empty);
// WorkspaceManager should have created workspace.yaml via sessionFs
- var workspaceYamlPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/workspace.yaml");
+ var workspaceYamlPath = GetStoredPath(providerRoot, session.SessionId, $"{SessionFsConfig.SessionStatePath}/workspace.yaml");
await WaitForConditionAsync(() => File.Exists(workspaceYamlPath));
var yaml = await ReadAllTextSharedAsync(workspaceYamlPath);
Assert.Contains("id:", yaml);
// Checkpoint index should also exist
- var indexPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/checkpoints/index.md");
+ var indexPath = GetStoredPath(providerRoot, session.SessionId, $"{SessionFsConfig.SessionStatePath}/checkpoints/index.md");
await WaitForConditionAsync(() => File.Exists(indexPath));
await session.DisposeAsync();
@@ -278,7 +278,7 @@ public async Task Should_Persist_Plan_Md_Via_SessionFs()
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 3?" });
await session.Rpc.Plan.UpdateAsync("# Test Plan\n\nThis is a test.");
- var planPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/plan.md");
+ var planPath = GetStoredPath(providerRoot, session.SessionId, $"{SessionFsConfig.SessionStatePath}/plan.md");
await WaitForConditionAsync(() => File.Exists(planPath));
var content = await ReadAllTextSharedAsync(planPath);
Assert.Contains("# Test Plan", content);
@@ -323,6 +323,17 @@ private CopilotClient CreateSessionFsClient(string providerRoot, bool useStdio =
private static string CreateProviderRoot()
=> Path.Join(Path.GetTempPath(), $"copilot-sessionfs-{Guid.NewGuid():N}");
+ private static string CreateSessionStatePath()
+ {
+ if (OperatingSystem.IsWindows())
+ {
+ return "/session-state";
+ }
+
+ return Path.Join(Path.GetTempPath(), $"copilot-sessionfs-state-{Guid.NewGuid():N}", "session-state")
+ .Replace(Path.DirectorySeparatorChar, '/');
+ }
+
private static string GetStoredPath(string providerRoot, string sessionId, string sessionPath)
{
var safeSessionId = NormalizeRelativePathSegment(sessionId, nameof(sessionId));
diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go
index 05cbd23b4..85a6a24b9 100644
--- a/go/internal/e2e/session_fs_test.go
+++ b/go/internal/e2e/session_fs_test.go
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "runtime"
"strings"
"testing"
"time"
@@ -17,6 +18,12 @@ import (
func TestSessionFs(t *testing.T) {
ctx := testharness.NewTestContext(t)
providerRoot := t.TempDir()
+ sessionStatePath := createSessionStatePath(t)
+ sessionFsConfig := &copilot.SessionFsConfig{
+ InitialCwd: "/",
+ SessionStatePath: sessionStatePath,
+ Conventions: rpc.SessionFSSetProviderConventionsPosix,
+ }
createSessionFsHandler := func(session *copilot.Session) copilot.SessionFsProvider {
return &testSessionFsHandler{
root: providerRoot,
@@ -60,7 +67,7 @@ func TestSessionFs(t *testing.T) {
t.Fatalf("Failed to disconnect session: %v", err)
}
- events, err := os.ReadFile(p(session.SessionID, "/session-state/events.jsonl"))
+ events, err := os.ReadFile(p(session.SessionID, sessionStatePath+"/events.jsonl"))
if err != nil {
t.Fatalf("Failed to read events file: %v", err)
}
@@ -98,7 +105,7 @@ func TestSessionFs(t *testing.T) {
t.Fatalf("Failed to disconnect first session: %v", err)
}
- if _, err := os.Stat(p(sessionID, "/session-state/events.jsonl")); err != nil {
+ if _, err := os.Stat(p(sessionID, sessionStatePath+"/events.jsonl")); err != nil {
t.Fatalf("Expected events file to exist before resume: %v", err)
}
@@ -189,10 +196,10 @@ func TestSessionFs(t *testing.T) {
t.Fatalf("Failed to get messages: %v", err)
}
toolResult := findToolCallResult(messages, "get_big_string")
- if !strings.Contains(toolResult, "/session-state/temp/") {
- t.Fatalf("Expected tool result to reference /session-state/temp/, got %q", toolResult)
+ if !strings.Contains(toolResult, sessionStatePath+"/temp/") {
+ t.Fatalf("Expected tool result to reference %s/temp/, got %q", sessionStatePath, toolResult)
}
- match := regexp.MustCompile(`(/session-state/temp/[^\s]+)`).FindStringSubmatch(toolResult)
+ match := regexp.MustCompile(`(` + regexp.QuoteMeta(sessionStatePath) + `/temp/[^\s]+)`).FindStringSubmatch(toolResult)
if len(match) < 2 {
t.Fatalf("Expected temp file path in tool result, got %q", toolResult)
}
@@ -221,7 +228,7 @@ func TestSessionFs(t *testing.T) {
t.Fatalf("Failed to send message: %v", err)
}
- eventsPath := p(session.SessionID, "/session-state/events.jsonl")
+ eventsPath := p(session.SessionID, sessionStatePath+"/events.jsonl")
if err := waitForFile(eventsPath, 5*time.Second); err != nil {
t.Fatalf("Timed out waiting for events file: %v", err)
}
@@ -271,7 +278,7 @@ func TestSessionFs(t *testing.T) {
}
// WorkspaceManager should have created workspace.yaml via sessionFs
- workspaceYamlPath := p(session.SessionID, "/session-state/workspace.yaml")
+ workspaceYamlPath := p(session.SessionID, sessionStatePath+"/workspace.yaml")
if err := waitForFile(workspaceYamlPath, 5*time.Second); err != nil {
t.Fatalf("Timed out waiting for workspace.yaml: %v", err)
}
@@ -284,7 +291,7 @@ func TestSessionFs(t *testing.T) {
}
// Checkpoint index should also exist
- indexPath := p(session.SessionID, "/session-state/checkpoints/index.md")
+ indexPath := p(session.SessionID, sessionStatePath+"/checkpoints/index.md")
if err := waitForFile(indexPath, 5*time.Second); err != nil {
t.Fatalf("Timed out waiting for checkpoints/index.md: %v", err)
}
@@ -313,7 +320,7 @@ func TestSessionFs(t *testing.T) {
t.Fatalf("Failed to update plan: %v", err)
}
- planPath := p(session.SessionID, "/session-state/plan.md")
+ planPath := p(session.SessionID, sessionStatePath+"/plan.md")
if err := waitForFile(planPath, 5*time.Second); err != nil {
t.Fatalf("Timed out waiting for plan.md: %v", err)
}
@@ -331,10 +338,12 @@ func TestSessionFs(t *testing.T) {
})
}
-var sessionFsConfig = &copilot.SessionFsConfig{
- InitialCwd: "/",
- SessionStatePath: "/session-state",
- Conventions: rpc.SessionFSSetProviderConventionsPosix,
+func createSessionStatePath(t *testing.T) string {
+ t.Helper()
+ if runtime.GOOS == "windows" {
+ return "/session-state"
+ }
+ return filepath.ToSlash(filepath.Join(t.TempDir(), "session-state"))
}
type testSessionFsHandler struct {
diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts
index 8a6722a12..f17419295 100644
--- a/nodejs/test/e2e/harness/sdkTestContext.ts
+++ b/nodejs/test/e2e/harness/sdkTestContext.ts
@@ -30,6 +30,7 @@ export async function createSdkTestContext({
copilotClientOptions?: CopilotClientOptions;
} = {}) {
const homeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-config-")));
+ const copilotHomeDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-home-")));
const workDir = realpathSync(fs.mkdtempSync(join(os.tmpdir(), "copilot-test-work-")));
const openAiEndpoint = new CapiProxy();
@@ -37,7 +38,7 @@ export async function createSdkTestContext({
const env = {
...process.env,
COPILOT_API_URL: proxyUrl,
- COPILOT_HOME: homeDir,
+ COPILOT_HOME: copilotHomeDir,
// TODO: I'm not convinced the SDK should default to using whatever config you happen to have in your homedir.
// The SDK config should be independent of the regular CLI app. Likewise it shouldn't mix sessions from the
@@ -87,6 +88,7 @@ export async function createSdkTestContext({
afterAll(async () => {
await copilotClient.stop();
await openAiEndpoint.stop(anyTestFailed);
+ await rmDir("remove e2e test copilotHomeDir", copilotHomeDir);
await rmDir("remove e2e test homeDir", homeDir);
await rmDir("remove e2e test workDir", workDir);
});
diff --git a/nodejs/test/e2e/session_fs.test.ts b/nodejs/test/e2e/session_fs.test.ts
index f455ffcd1..f6af24d34 100644
--- a/nodejs/test/e2e/session_fs.test.ts
+++ b/nodejs/test/e2e/session_fs.test.ts
@@ -4,6 +4,9 @@
import { SessionCompactionCompleteEvent } from "@github/copilot/sdk";
import { MemoryProvider, VirtualProvider } from "@platformatic/vfs";
+import { mkdtempSync, realpathSync } from "fs";
+import { tmpdir } from "os";
+import { join } from "path";
import { describe, expect, it, onTestFinished } from "vitest";
import { CopilotClient } from "../../src/client.js";
import type { SessionFsReaddirWithTypesEntry } from "../../src/generated/rpc.js";
@@ -18,6 +21,14 @@ import {
} from "../../src/index.js";
import { createSdkTestContext } from "./harness/sdkTestContext.js";
+const sessionStatePath =
+ process.platform === "win32"
+ ? "/session-state"
+ : join(
+ realpathSync(mkdtempSync(join(tmpdir(), "copilot-sessionfs-state-"))),
+ "session-state"
+ ).replace(/\\/g, "/");
+
describe("Session Fs", async () => {
// Single provider for the describe block — session IDs are unique per test,
// so no cross-contamination between tests.
@@ -43,7 +54,9 @@ describe("Session Fs", async () => {
expect(msg?.data.content).toContain("300");
await session.disconnect();
- const buf = await provider.readFile(p(session.sessionId, "/session-state/events.jsonl"));
+ const buf = await provider.readFile(
+ p(session.sessionId, `${sessionStatePath}/events.jsonl`)
+ );
const content = buf.toString("utf8");
expect(content).toContain("300");
});
@@ -60,7 +73,7 @@ describe("Session Fs", async () => {
await session1.disconnect();
// The events file should exist before resume
- expect(await provider.exists(p(sessionId, "/session-state/events.jsonl"))).toBe(true);
+ expect(await provider.exists(p(sessionId, `${sessionStatePath}/events.jsonl`))).toBe(true);
const session2 = await client.resumeSession(sessionId, {
onPermissionRequest: approveAll,
@@ -116,8 +129,10 @@ describe("Session Fs", async () => {
// The tool result should reference a temp file under the session state path
const messages = await session.getMessages();
const toolResult = findToolCallResult(messages, "get_big_string");
- expect(toolResult).toContain("/session-state/temp/");
- const filename = toolResult?.match(/(\/session-state\/temp\/[^\s]+)/)?.[1];
+ expect(toolResult).toContain(`${sessionStatePath}/temp/`);
+ const filename = toolResult?.match(
+ new RegExp(`(${escapeRegExp(sessionStatePath)}/temp/[^\\s]+)`)
+ )?.[1];
expect(filename).toBeDefined();
// Verify the file was written with the correct content via the provider
@@ -135,13 +150,13 @@ describe("Session Fs", async () => {
expect(msg?.data.content).toContain("56");
// WorkspaceManager should have created workspace.yaml via sessionFs
- const workspaceYamlPath = p(session.sessionId, "/session-state/workspace.yaml");
+ const workspaceYamlPath = p(session.sessionId, `${sessionStatePath}/workspace.yaml`);
await expect.poll(() => provider.exists(workspaceYamlPath)).toBe(true);
const yaml = await provider.readFile(workspaceYamlPath, "utf8");
expect(yaml).toContain("id:");
// Checkpoint index should also exist
- const indexPath = p(session.sessionId, "/session-state/checkpoints/index.md");
+ const indexPath = p(session.sessionId, `${sessionStatePath}/checkpoints/index.md`);
await expect.poll(() => provider.exists(indexPath)).toBe(true);
await session.disconnect();
@@ -157,7 +172,7 @@ describe("Session Fs", async () => {
await session.sendAndWait({ prompt: "What is 2 + 3?" });
await session.rpc.plan.update({ content: "# Test Plan\n\nThis is a test." });
- const planPath = p(session.sessionId, "/session-state/plan.md");
+ const planPath = p(session.sessionId, `${sessionStatePath}/plan.md`);
await expect.poll(() => provider.exists(planPath)).toBe(true);
const content = await provider.readFile(planPath, "utf8");
expect(content).toContain("# Test Plan");
@@ -176,7 +191,7 @@ describe("Session Fs", async () => {
await session.sendAndWait({ prompt: "What is 2+2?" });
- const eventsPath = p(session.sessionId, "/session-state/events.jsonl");
+ const eventsPath = p(session.sessionId, `${sessionStatePath}/events.jsonl`);
await expect.poll(() => provider.exists(eventsPath)).toBe(true);
const contentBefore = await provider.readFile(eventsPath, "utf8");
expect(contentBefore).not.toContain("checkpointNumber");
@@ -212,10 +227,14 @@ function findToolName(messages: SessionEvent[], toolCallId: string): string | un
const sessionFsConfig: SessionFsConfig = {
initialCwd: "/",
- sessionStatePath: "/session-state",
+ sessionStatePath,
conventions: "posix",
};
+function escapeRegExp(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+}
+
function createTestSessionFsHandler(
session: CopilotSession,
provider: VirtualProvider
diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py
index 18c266c64..1c57b06d9 100644
--- a/python/e2e/test_session_fs.py
+++ b/python/e2e/test_session_fs.py
@@ -6,6 +6,7 @@
import datetime as dt
import os
import re
+import tempfile
from pathlib import Path
import pytest
@@ -26,9 +27,17 @@
pytestmark = pytest.mark.asyncio(loop_scope="module")
+SESSION_STATE_PATH = (
+ "/session-state"
+ if os.name == "nt"
+ else (Path(tempfile.mkdtemp(prefix="copilot-sessionfs-state-")) / "session-state")
+ .resolve()
+ .as_posix()
+)
+
SESSION_FS_CONFIG: SessionFsConfig = {
"initial_cwd": "/",
- "session_state_path": "/session-state",
+ "session_state_path": SESSION_STATE_PATH,
"conventions": "posix",
}
@@ -71,7 +80,7 @@ async def test_should_route_file_operations_through_the_session_fs_provider(
await session.disconnect()
events_path = provider_path(
- provider_root, session.session_id, "/session-state/events.jsonl"
+ provider_root, session.session_id, f"{SESSION_STATE_PATH}/events.jsonl"
)
assert "300" in events_path.read_text(encoding="utf-8")
@@ -93,7 +102,7 @@ async def test_should_load_session_data_from_fs_provider_on_resume(
assert "100" in msg.data.content
await session1.disconnect()
- assert provider_path(provider_root, session_id, "/session-state/events.jsonl").exists()
+ assert provider_path(provider_root, session_id, f"{SESSION_STATE_PATH}/events.jsonl").exists()
session2 = await session_fs_client.resume_session(
session_id,
@@ -169,8 +178,8 @@ def get_big_string() -> str:
messages = await session.get_messages()
tool_result = find_tool_call_result(messages, "get_big_string")
assert tool_result is not None
- assert "/session-state/temp/" in tool_result
- match = re.search(r"(/session-state/temp/[^\s]+)", tool_result)
+ assert f"{SESSION_STATE_PATH}/temp/" in tool_result
+ match = re.search(rf"({re.escape(SESSION_STATE_PATH)}/temp/[^\s]+)", tool_result)
assert match is not None
temp_file = provider_path(provider_root, session.session_id, match.group(1))
@@ -200,7 +209,7 @@ def on_event(event: SessionEvent):
await session.send_and_wait("What is 2+2?")
events_path = provider_path(
- provider_root, session.session_id, "/session-state/events.jsonl"
+ provider_root, session.session_id, f"{SESSION_STATE_PATH}/events.jsonl"
)
await wait_for_path(events_path)
assert "checkpointNumber" not in events_path.read_text(encoding="utf-8")
@@ -228,7 +237,7 @@ async def test_should_write_workspace_metadata_via_sessionfs(
# WorkspaceManager should have created workspace.yaml via sessionFs
workspace_yaml_path = provider_path(
- provider_root, session.session_id, "/session-state/workspace.yaml"
+ provider_root, session.session_id, f"{SESSION_STATE_PATH}/workspace.yaml"
)
await wait_for_path(workspace_yaml_path)
yaml_content = workspace_yaml_path.read_text(encoding="utf-8")
@@ -236,7 +245,7 @@ async def test_should_write_workspace_metadata_via_sessionfs(
# Checkpoint index should also exist
index_path = provider_path(
- provider_root, session.session_id, "/session-state/checkpoints/index.md"
+ provider_root, session.session_id, f"{SESSION_STATE_PATH}/checkpoints/index.md"
)
await wait_for_path(index_path)
@@ -257,7 +266,7 @@ async def test_should_persist_plan_md_via_sessionfs(
await session.send_and_wait("What is 2 + 3?")
await session.rpc.plan.update(PlanUpdateRequest(content="# Test Plan\n\nThis is a test."))
- plan_path = provider_path(provider_root, session.session_id, "/session-state/plan.md")
+ plan_path = provider_path(provider_root, session.session_id, f"{SESSION_STATE_PATH}/plan.md")
await wait_for_path(plan_path)
content = plan_path.read_text(encoding="utf-8")
assert "# Test Plan" in content
diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts
index cc000b13b..f8864cccd 100644
--- a/test/harness/replayingCapiProxy.ts
+++ b/test/harness/replayingCapiProxy.ts
@@ -865,10 +865,15 @@ function normalizeUserMessage(content: string): string {
function normalizeLargeOutputFilepaths(result: string): string {
// Replaces filenames like 1774637043987-copilot-tool-output-tk7puw.txt with PLACEHOLDER-copilot-tool-output-PLACEHOLDER
- return result.replace(
- /\d+-copilot-tool-output-[a-z0-9.]+/g,
- "PLACEHOLDER-copilot-tool-output-PLACEHOLDER",
- );
+ return result
+ .replace(
+ /\d+-copilot-tool-output-[a-z0-9.]+/g,
+ "PLACEHOLDER-copilot-tool-output-PLACEHOLDER",
+ )
+ .replace(
+ /(?:[A-Za-z]:)?[^\s"'`]*[\\/]session-state[\\/]temp[\\/]PLACEHOLDER-copilot-tool-output-PLACEHOLDER/g,
+ "/session-state/temp/PLACEHOLDER-copilot-tool-output-PLACEHOLDER",
+ );
}
// Transforms a single OpenAI-style inbound response message into normalized form
From f6daf4e2c739dc8e97da7a4edb11fa68adbb6924 Mon Sep 17 00:00:00 2001
From: Stephen Toub
Date: Tue, 28 Apr 2026 22:57:21 -0400
Subject: [PATCH 4/4] Fix Python SessionFs test formatting
Wrap long SessionFs assertions so the Python e2e test passes ruff after the Unix session state path update.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
python/e2e/test_session_fs.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py
index 1c57b06d9..f44b91a16 100644
--- a/python/e2e/test_session_fs.py
+++ b/python/e2e/test_session_fs.py
@@ -102,7 +102,9 @@ async def test_should_load_session_data_from_fs_provider_on_resume(
assert "100" in msg.data.content
await session1.disconnect()
- assert provider_path(provider_root, session_id, f"{SESSION_STATE_PATH}/events.jsonl").exists()
+ assert provider_path(
+ provider_root, session_id, f"{SESSION_STATE_PATH}/events.jsonl"
+ ).exists()
session2 = await session_fs_client.resume_session(
session_id,
@@ -266,7 +268,9 @@ async def test_should_persist_plan_md_via_sessionfs(
await session.send_and_wait("What is 2 + 3?")
await session.rpc.plan.update(PlanUpdateRequest(content="# Test Plan\n\nThis is a test."))
- plan_path = provider_path(provider_root, session.session_id, f"{SESSION_STATE_PATH}/plan.md")
+ plan_path = provider_path(
+ provider_root, session.session_id, f"{SESSION_STATE_PATH}/plan.md"
+ )
await wait_for_path(plan_path)
content = plan_path.read_text(encoding="utf-8")
assert "# Test Plan" in content