@agent-compose/sdk 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -39
- package/dist/agent/__tests__/runtime-json-schema.test.d.ts +10 -0
- package/dist/agent/agent-context.d.ts +21 -1
- package/dist/agent/agent-loop.d.ts +24 -1
- package/dist/client.d.ts +338 -534
- package/dist/directives.d.ts +112 -0
- package/dist/display.d.ts +242 -0
- package/dist/errors.d.ts +24 -1
- package/dist/index.d.ts +34 -13
- package/dist/index.js +2984 -861
- package/dist/pause/wrappers.d.ts +31 -9
- package/dist/processors/ask-human.d.ts +30 -0
- package/dist/processors/ask-human.test.d.ts +1 -0
- package/dist/processors/index.d.ts +1 -0
- package/dist/runtimes/_acp-client.d.ts +46 -1
- package/dist/runtimes/_cli-agent.d.ts +58 -4
- package/dist/runtimes/_jsonl-guard.d.ts +103 -0
- package/dist/runtimes/amp.d.ts +2 -2
- package/dist/runtimes/claude-code.d.ts +59 -0
- package/dist/runtimes/claude-code.test.d.ts +14 -0
- package/dist/runtimes/claude.d.ts +16 -0
- package/dist/runtimes/claude.test.d.ts +8 -0
- package/dist/runtimes/codex.d.ts +9 -3
- package/dist/runtimes/cursor.d.ts +9 -0
- package/dist/runtimes/droid.d.ts +9 -0
- package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
- package/dist/runtimes/openai-desktop.js +2922 -861
- package/dist/runtimes/opencode.d.ts +25 -0
- package/dist/runtimes/vercel.js +22 -1
- package/dist/sandbox/devbox.d.ts +42 -0
- package/dist/sandbox/exec-stream.d.ts +14 -0
- package/dist/sandbox/network-policy.d.ts +100 -0
- package/dist/sandbox/provider-def.d.ts +79 -0
- package/dist/sandbox/providers/desktop.d.ts +10 -0
- package/dist/sandbox/providers/e2b.d.ts +17 -0
- package/dist/sandbox/providers/local.d.ts +11 -0
- package/dist/sandbox/providers/vercel.d.ts +18 -0
- package/dist/sandbox/registry.d.ts +45 -0
- package/dist/sandbox/sizes.d.ts +68 -0
- package/dist/sandbox.d.ts +24 -299
- package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
- package/dist/step-invocation/invoker.d.ts +24 -1
- package/dist/step-invocation/protocol.d.ts +13 -0
- package/dist/types/api-compliance.d.ts +71 -0
- package/dist/types/api-conversations.d.ts +492 -0
- package/dist/types/api-factory.d.ts +309 -0
- package/dist/types/api-projects.d.ts +131 -0
- package/dist/types/api-runs.d.ts +377 -0
- package/dist/types/api-scopes.d.ts +102 -0
- package/dist/types/conversation-stream.d.ts +191 -0
- package/dist/types/execution-context.d.ts +12 -2
- package/dist/types/protocol.d.ts +30 -1
- package/dist/types/sandbox-environment.d.ts +8 -5
- package/dist/types/sandbox.d.ts +79 -0
- package/dist/types/workflow-metadata.d.ts +33 -8
- package/dist/types/workflow-plan.d.ts +10 -0
- package/dist/types/workflow.d.ts +18 -193
- package/dist/utils/bundler.d.ts +12 -1
- package/dist/utils/errors.d.ts +9 -1
- package/dist/workflow-steps/index.d.ts +1 -1
- package/dist/workflow-steps/observability.d.ts +8 -1
- package/dist/workflow-steps/runner.d.ts +3 -3
- package/dist/workflow-steps/step.d.ts +15 -1
- package/dist/workflow-steps/types.d.ts +19 -5
- package/dist/workflow-steps/workflow.d.ts +22 -1
- package/dist/workflows/engine.d.ts +3 -2
- package/dist/workflows/invoke-child.d.ts +2 -2
- package/package.json +1 -1
- package/src/agent/agent-context.ts +206 -16
- package/src/agent/agent-loop.ts +40 -4
- package/src/agent/run-agent.ts +9 -1
- package/src/client.ts +909 -621
- package/src/directives.ts +184 -0
- package/src/display.ts +788 -0
- package/src/errors.ts +39 -0
- package/src/index.ts +117 -10
- package/src/pause/wrappers.ts +44 -9
- package/src/processors/ask-human.ts +136 -0
- package/src/processors/index.ts +5 -0
- package/src/runtimes/_acp-client.ts +72 -3
- package/src/runtimes/_cli-agent.ts +171 -38
- package/src/runtimes/_jsonl-guard.ts +219 -0
- package/src/runtimes/claude-code.ts +246 -0
- package/src/runtimes/claude.ts +32 -2
- package/src/runtimes/codex.ts +55 -3
- package/src/runtimes/cursor.ts +59 -0
- package/src/runtimes/droid.ts +63 -0
- package/src/runtimes/openai-desktop.ts +59 -14
- package/src/runtimes/opencode.ts +61 -0
- package/src/sandbox/devbox.ts +48 -0
- package/src/sandbox/exec-stream.ts +48 -0
- package/src/sandbox/network-policy.ts +181 -0
- package/src/sandbox/provider-def.ts +94 -0
- package/src/sandbox/providers/desktop.ts +57 -0
- package/src/sandbox/providers/e2b.ts +354 -0
- package/src/sandbox/providers/local.ts +106 -0
- package/src/sandbox/providers/vercel.ts +331 -0
- package/src/sandbox/registry.ts +198 -0
- package/src/sandbox/sizes.ts +95 -0
- package/src/sandbox.ts +59 -1263
- package/src/step-invocation/invoker.ts +319 -34
- package/src/step-invocation/protocol.ts +19 -0
- package/src/types/api-compliance.ts +79 -0
- package/src/types/api-conversations.ts +522 -0
- package/src/types/api-factory.ts +336 -0
- package/src/types/api-projects.ts +140 -0
- package/src/types/api-runs.ts +412 -0
- package/src/types/api-scopes.ts +102 -0
- package/src/types/conversation-stream.ts +231 -0
- package/src/types/execution-context.ts +10 -2
- package/src/types/protocol.ts +33 -0
- package/src/types/sandbox-environment.ts +28 -9
- package/src/types/sandbox.ts +78 -0
- package/src/types/workflow-metadata.ts +35 -8
- package/src/types/workflow-plan.ts +11 -0
- package/src/types/workflow.ts +25 -280
- package/src/utils/bundler.ts +32 -5
- package/src/utils/errors.ts +34 -2
- package/src/workflow-steps/index.ts +1 -0
- package/src/workflow-steps/observability.ts +19 -8
- package/src/workflow-steps/runner.ts +4 -4
- package/src/workflow-steps/step.ts +49 -1
- package/src/workflow-steps/types.ts +20 -5
- package/src/workflow-steps/workflow.ts +22 -1
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/invoke-child.ts +2 -2
package/src/errors.ts
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
|
+
/** Stable machine-readable denial codes on membership/capability-gated
|
|
2
|
+
* mutations (ADR-0045). Reads never carry a code — a visibility denial is
|
|
3
|
+
* a plain 404, indistinguishable from a missing id. */
|
|
4
|
+
export type AuthzErrorCode =
|
|
5
|
+
| "role_read_only"
|
|
6
|
+
| "owner_required"
|
|
7
|
+
| "capability_required"
|
|
8
|
+
| "membership_required"
|
|
9
|
+
| "last_owner"
|
|
10
|
+
| "approver_only";
|
|
11
|
+
|
|
1
12
|
/** Thrown by AgentComposeClient when the server returns a non-2xx response. */
|
|
2
13
|
export class AgentComposeError extends Error {
|
|
14
|
+
/** Parsed `Retry-After` response header, in milliseconds, when the server
|
|
15
|
+
* sent one (rate-limit 429s do). Pollers use it to honor the server's own
|
|
16
|
+
* estimate of when the limit window clears instead of guessing. */
|
|
17
|
+
readonly retryAfterMs?: number;
|
|
18
|
+
/** Stable error code on gated-mutation 403s (ADR-0045) — one of
|
|
19
|
+
* `AuthzErrorCode` when the denial is a membership/capability gate;
|
|
20
|
+
* undefined otherwise. */
|
|
21
|
+
readonly code?: string;
|
|
22
|
+
/** The missing capability on a `capability_required` denial
|
|
23
|
+
* (e.g. "write", "invoke", "see_runs"). */
|
|
24
|
+
readonly capability?: string;
|
|
25
|
+
|
|
3
26
|
constructor(
|
|
4
27
|
public readonly status: number,
|
|
5
28
|
message: string,
|
|
29
|
+
opts?: { retryAfterMs?: number; code?: string; capability?: string },
|
|
6
30
|
) {
|
|
7
31
|
super(message);
|
|
8
32
|
this.name = "AgentComposeError";
|
|
33
|
+
if (opts?.retryAfterMs !== undefined) this.retryAfterMs = opts.retryAfterMs;
|
|
34
|
+
if (opts?.code !== undefined) this.code = opts.code;
|
|
35
|
+
if (opts?.capability !== undefined) this.capability = opts.capability;
|
|
9
36
|
}
|
|
10
37
|
}
|
|
38
|
+
|
|
39
|
+
/** Parse a `Retry-After` header into milliseconds. Accepts both RFC 9110
|
|
40
|
+
* forms — delta-seconds (`"120"`) and HTTP-date — and returns `undefined`
|
|
41
|
+
* for anything unparseable so callers fall back to their own backoff. */
|
|
42
|
+
export function parseRetryAfterMs(header: string | null | undefined): number | undefined {
|
|
43
|
+
if (!header) return undefined;
|
|
44
|
+
const seconds = Number(header);
|
|
45
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
|
|
46
|
+
const date = Date.parse(header);
|
|
47
|
+
if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
// Factory functions
|
|
17
17
|
export { defineRuntime } from "./types/runtime.js";
|
|
18
18
|
export { defineWorkflow } from "./types/workflow.js";
|
|
19
|
-
export type { WorkflowDefinition } from "./types/workflow.js";
|
|
20
19
|
export { defineSandboxEnvironment } from "./types/sandbox-environment.js";
|
|
21
20
|
export type { SandboxEnvironmentDefinition } from "./types/sandbox-environment.js";
|
|
22
21
|
|
|
@@ -31,8 +30,6 @@ export type {
|
|
|
31
30
|
|
|
32
31
|
// Workflow types and runtime utilities
|
|
33
32
|
export type {
|
|
34
|
-
WorkflowFn,
|
|
35
|
-
WorkflowCtx,
|
|
36
33
|
WorkflowRun,
|
|
37
34
|
AgentBudget,
|
|
38
35
|
WorkflowHooks,
|
|
@@ -46,7 +43,7 @@ export type { ConnectorRequestRules } from "./types/workflow-metadata.js";
|
|
|
46
43
|
|
|
47
44
|
// Snapshot entry type re-exported for consumers (dashboard, CLI).
|
|
48
45
|
export type { RunSnapshotEntry } from "./client.js";
|
|
49
|
-
export type { WorkflowPlan, WorkflowStepPlan } from "./types/workflow-plan.js";
|
|
46
|
+
export type { WorkflowPlan, WorkflowStepPlan, WorkflowStepDeliverable } from "./types/workflow-plan.js";
|
|
50
47
|
export type { BaseExecutionContext, InvokeChild } from "./types/execution-context.js";
|
|
51
48
|
|
|
52
49
|
// Request context — per-run typed bag (tenant identity + freeform).
|
|
@@ -77,6 +74,8 @@ export {
|
|
|
77
74
|
requireScope,
|
|
78
75
|
redactPattern,
|
|
79
76
|
createGatePauseProcessor,
|
|
77
|
+
createAskHumanProcessor,
|
|
78
|
+
ASK_USER_QUESTION_TOOL,
|
|
80
79
|
} from "./processors/index.js";
|
|
81
80
|
export type {
|
|
82
81
|
Processor,
|
|
@@ -106,6 +105,8 @@ export type {
|
|
|
106
105
|
export type {
|
|
107
106
|
SandboxProvider,
|
|
108
107
|
DesktopSandboxProvider,
|
|
108
|
+
SandboxPtyOpts,
|
|
109
|
+
SandboxPtyHandle,
|
|
109
110
|
} from "./types/sandbox.js";
|
|
110
111
|
|
|
111
112
|
// HTTP client
|
|
@@ -120,6 +121,8 @@ export type {
|
|
|
120
121
|
TeamMember, Mention, CreateMentionsInput,
|
|
121
122
|
EventSubjectType, EventRow, ReportEventInput, ListEventsOptions, ListEventsResult,
|
|
122
123
|
RunLogLine, ListRunLogsOptions,
|
|
124
|
+
RunListEntry, ListRunsOptions, RunDetail,
|
|
125
|
+
FundingLane, RunFundingStamp, RunFundingUsageRow, RunFundingResponse,
|
|
123
126
|
RegisteredRuntime, RunState, RunStatus, FactoryRow, SnapshotListEntry, SnapshotListResponse,
|
|
124
127
|
ApiKey, ApiKeyCreated,
|
|
125
128
|
UsageRollupRow, UsageResponse,
|
|
@@ -128,13 +131,73 @@ export type {
|
|
|
128
131
|
SendAgentMessageOptions, SendAgentMessageResponse,
|
|
129
132
|
AnswerSteerOptions,
|
|
130
133
|
ResumePauseOptions, ResumePauseResponse, ResumePauseSuccess, ResumePausePending, ResumePauseActor,
|
|
134
|
+
ConversationMessagePart, ConversationRow, ConversationMessageRow,
|
|
135
|
+
ConversationsPage, ConversationDetail, ConversationThread,
|
|
136
|
+
ConversationPageContext, SendConversationMessageInput, SendConversationMessageResult,
|
|
137
|
+
ConversationTurnState, ConversationPresenceSnapshot, StreamConversationOptions, AgentListRow,
|
|
138
|
+
CreateCloudSessionInput, CloudSessionCreated,
|
|
139
|
+
// Cloud-session developer surface (ADR-0052)
|
|
140
|
+
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked,
|
|
141
|
+
// Session branch proposals (ADR-0053)
|
|
142
|
+
SessionFileChange, SessionChangeSet,
|
|
143
|
+
SessionMergeReportDetail, SessionMergeReport, SessionDiscardReport,
|
|
144
|
+
// Channel-attached sessions (ADR-0057)
|
|
145
|
+
ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
|
|
146
|
+
FactoryFileSearchRow, FactoryFolderSearchRow, SearchFactoryFilesOptions, FactoryFileSearchResult, PublicFileLinkState,
|
|
147
|
+
// Membership-scoped authorization (ADR-0045)
|
|
148
|
+
ConversationMemberRole, ConversationMember,
|
|
149
|
+
DocumentCapability, TemplateCapability, ScopeGrant, ArtifactScope,
|
|
150
|
+
RunContext, SetScopeGrantsInput, SetTemplateScopeInput, TemplateDetail,
|
|
151
|
+
// Projects (ADR-0045 projects extension)
|
|
152
|
+
Project, ProjectsPage, ProjectRole, ProjectMember,
|
|
153
|
+
ProjectObject, ProjectObjectsPage, ProjectSkippedFile, ProjectPreviewFile,
|
|
154
|
+
ProjectAddPreview, AddProjectObjectInput, AddProjectObjectResult,
|
|
155
|
+
RefreshProjectObjectResult,
|
|
156
|
+
// Break-glass compliance sessions (ADR-0051)
|
|
157
|
+
ComplianceScopeKind, ComplianceStatus, ComplianceSession, ComplianceAccess,
|
|
158
|
+
RequestComplianceSessionInput, ListComplianceSessionsOptions, ComplianceSessionsPage,
|
|
159
|
+
ListComplianceAccessesOptions, ComplianceAccessesPage,
|
|
131
160
|
} from "./client.js";
|
|
132
161
|
|
|
133
162
|
// SSE parser — exposed so tests and downstream callers can reuse it.
|
|
134
163
|
export { parseSseStream } from "./sse.js";
|
|
135
164
|
|
|
165
|
+
// Cloud-session UI directives — the marker contract between the in-sandbox
|
|
166
|
+
// `agentc` CLI and the server's cloud executor.
|
|
167
|
+
export {
|
|
168
|
+
renderDirectiveMarker, parseDirectiveMarkers,
|
|
169
|
+
DIRECTIVE_MARKER_PREFIX, DIRECTIVE_MARKER_SUFFIX, MAX_DIRECTIVES_PER_OUTPUT,
|
|
170
|
+
REVISION_SELECTOR_RE,
|
|
171
|
+
ASK_DIRECTIVE_PROMPT_MAX, ASK_DIRECTIVE_MAX_OPTIONS,
|
|
172
|
+
ASK_DIRECTIVE_OPTION_ID_MAX, ASK_DIRECTIVE_OPTION_LABEL_MAX,
|
|
173
|
+
ASK_APPROVER_MIN, ASK_APPROVER_MAX,
|
|
174
|
+
} from "./directives.js";
|
|
175
|
+
export type { CloudDirective } from "./directives.js";
|
|
176
|
+
|
|
177
|
+
// Conversation stream — typed wire events + the frame normalizer, shared by
|
|
178
|
+
// `streamConversation` and clients that run their own SSE loop (the CLI's
|
|
179
|
+
// `agentc session` terminal driver reuses its bridge parser + normalizes
|
|
180
|
+
// through this, so every surface holds the same durable-vs-live invariant).
|
|
181
|
+
export { normalizeConversationStreamEvent } from "./types/conversation-stream.js";
|
|
182
|
+
export type {
|
|
183
|
+
ConversationStreamEvent, ConversationStreamAuthor,
|
|
184
|
+
ConversationPartEvent, ConversationPartPartialEvent, ConversationMessageDoneEvent,
|
|
185
|
+
ConversationErrorEvent, ConversationReactionEvent, ConversationMessageDeletedEvent,
|
|
186
|
+
ConversationReplayContinueEvent, ConversationTurnStateEvent, ConversationPresenceEvent,
|
|
187
|
+
ConversationSessionStatusEvent,
|
|
188
|
+
ConversationPresenceClient, ConversationAgentPresence,
|
|
189
|
+
} from "./types/conversation-stream.js";
|
|
190
|
+
|
|
191
|
+
// ACP client peer — the JSON-RPC client half of ADR-0020. Re-exported for the
|
|
192
|
+
// CLI's local-agent bridge daemon (`agentc bridge`), which is an ACP client
|
|
193
|
+
// over a locally spawned agent process and reuses the exact same peer +
|
|
194
|
+
// session/update normaliser as the sandbox CLI-agent runtimes.
|
|
195
|
+
export { AcpClientPeer, normalizeSessionUpdate, ACP_PROTOCOL_VERSION } from "./runtimes/_acp-client.js";
|
|
196
|
+
export type { AcpAvailableCommand, AcpClientPeerDeps, AcpSessionCaps } from "./runtimes/_acp-client.js";
|
|
197
|
+
|
|
136
198
|
// Errors and utilities
|
|
137
199
|
export { AgentComposeError } from "./errors.js";
|
|
200
|
+
export type { AuthzErrorCode } from "./errors.js";
|
|
138
201
|
export { formatError } from "./utils/errors.js";
|
|
139
202
|
|
|
140
203
|
// Bundling utilities
|
|
@@ -168,12 +231,33 @@ export type { GatewayModelId } from "ai";
|
|
|
168
231
|
// CLI-agent runtimes — drive an external agentic CLI (codex / amp) inside the
|
|
169
232
|
// sandbox and stream-parse its JSONL. No heavy npm deps (the CLI lives in the
|
|
170
233
|
// sandbox image), so these are root-exported like claudeRuntime.
|
|
171
|
-
export { createCodexRuntime } from "./runtimes/codex.js";
|
|
234
|
+
export { createCodexRuntime, codexSpec } from "./runtimes/codex.js";
|
|
172
235
|
export type { CodexRuntimeConfig } from "./runtimes/codex.js";
|
|
173
236
|
export { default as codexRuntime } from "./runtimes/codex.js";
|
|
237
|
+
// Reasoning-effort level a CLI runtime turn may carry (claude-code / codex).
|
|
238
|
+
export type { CliReasoningEffort } from "./runtimes/_cli-agent.js";
|
|
174
239
|
export { createAmpRuntime } from "./runtimes/amp.js";
|
|
175
240
|
export type { AmpRuntimeConfig } from "./runtimes/amp.js";
|
|
176
241
|
export { default as ampRuntime } from "./runtimes/amp.js";
|
|
242
|
+
export { createOpencodeRuntime, opencodeSpec } from "./runtimes/opencode.js";
|
|
243
|
+
export type { OpencodeRuntimeConfig } from "./runtimes/opencode.js";
|
|
244
|
+
export { default as opencodeRuntime } from "./runtimes/opencode.js";
|
|
245
|
+
|
|
246
|
+
export { createCursorRuntime, cursorSpec } from "./runtimes/cursor.js";
|
|
247
|
+
export type { CursorRuntimeConfig } from "./runtimes/cursor.js";
|
|
248
|
+
export { default as cursorRuntime } from "./runtimes/cursor.js";
|
|
249
|
+
|
|
250
|
+
export { createDroidRuntime, droidSpec } from "./runtimes/droid.js";
|
|
251
|
+
export type { DroidRuntimeConfig } from "./runtimes/droid.js";
|
|
252
|
+
export { default as droidRuntime } from "./runtimes/droid.js";
|
|
253
|
+
|
|
254
|
+
// Claude Code as a CLI-agent runtime (in-sandbox `claude -p` / the Zed ACP
|
|
255
|
+
// adapter) — the cloud-hostable counterpart of `claudeRuntime` (claude.ts,
|
|
256
|
+
// which drives the Agent SDK in the calling process and so can never run a
|
|
257
|
+
// server-driven cloud-session turn).
|
|
258
|
+
export { createClaudeCodeRuntime, claudeCodeSpec, CLAUDE_CODE_ACP_ADAPTER, CLAUDE_CODE_THINKING_TOKENS } from "./runtimes/claude-code.js";
|
|
259
|
+
export type { ClaudeCodeRuntimeConfig } from "./runtimes/claude-code.js";
|
|
260
|
+
export { default as claudeCodeRuntime } from "./runtimes/claude-code.js";
|
|
177
261
|
|
|
178
262
|
// Built-in coding tools for Vercel AI SDK runtime.
|
|
179
263
|
export { bashTool, codingTools, editTool, readTool, writeTool } from "./tools/index.js";
|
|
@@ -189,7 +273,10 @@ export { createSandbox, reconnectSandbox, killAllSandboxes, killSandboxById,
|
|
|
189
273
|
parseSseExecStream, AGENT_COMPOSE_TAG,
|
|
190
274
|
SANDBOX_VCPUS, DEFAULT_SANDBOX_SIZE, E2B_TEMPLATE_SIZES,
|
|
191
275
|
isE2bSupportedSize, e2bMachineSpec, e2bBaseTemplate, e2bAgentEnvTemplate,
|
|
192
|
-
isPlatformE2bTemplateAlias
|
|
276
|
+
isPlatformE2bTemplateAlias,
|
|
277
|
+
// ADR-0038 "Computer" — the per-member persistent desktop machine template.
|
|
278
|
+
E2B_DEVBOX_TEMPLATE, E2B_DEVBOX_SPEC, E2B_DEVBOX_RECIPE_VERSION,
|
|
279
|
+
e2bDevboxTemplateRef } from "./sandbox.js";
|
|
193
280
|
export { SandboxUnavailableError, SANDBOX_UNAVAILABLE_PREFIX } from "./sandbox-errors.js";
|
|
194
281
|
export type {
|
|
195
282
|
SandboxCreateOpts, SandboxNetworkPolicy, SandboxNetworkHeaderTransform,
|
|
@@ -197,7 +284,7 @@ export type {
|
|
|
197
284
|
SandboxQuotaResult, OwnedSandboxResult, OwnedSandbox, SandboxSize,
|
|
198
285
|
ParseSseExecStreamOptions, SandboxCommandRunOptions, SandboxCommandResult,
|
|
199
286
|
} from "./sandbox.js";
|
|
200
|
-
export type { SandboxResources } from "./types/workflow-metadata.js";
|
|
287
|
+
export type { SandboxResources, DriveMergePolicy } from "./types/workflow-metadata.js";
|
|
201
288
|
|
|
202
289
|
// Workflow engine
|
|
203
290
|
export { runWorkflow, WorkflowError, EngineError, classifyError, parseNameVersion } from "./workflows/engine.js";
|
|
@@ -218,6 +305,7 @@ export {
|
|
|
218
305
|
export type {
|
|
219
306
|
Step,
|
|
220
307
|
StepContext,
|
|
308
|
+
StepDeliverable,
|
|
221
309
|
StepRunResult,
|
|
222
310
|
Workflow,
|
|
223
311
|
DefineStepOpts,
|
|
@@ -261,7 +349,7 @@ export {
|
|
|
261
349
|
} from "./pause/errors.js";
|
|
262
350
|
export type { PauseErrorCode } from "./pause/errors.js";
|
|
263
351
|
export type { PauseRequest } from "./pause/pause-core.js";
|
|
264
|
-
export type { WaitForEventRequest } from "./pause/wrappers.js";
|
|
352
|
+
export type { WaitForEventRequest, DecisionRequest } from "./pause/wrappers.js";
|
|
265
353
|
// ADR-0028 — the in-sandbox pause client. `agentc pause` and the gate-pause
|
|
266
354
|
// processor both pause their OWN run through the server pause API and block
|
|
267
355
|
// until a human answers (create-then-poll; the VM suspends in place while
|
|
@@ -285,7 +373,26 @@ export { agentLoop, parseAgentStatus, DEFAULT_CLAUDE_MODEL } from "./agent/agent
|
|
|
285
373
|
export type { AgentLifecycleEvent, AgentLoopOpts, AgentLoopResult } from "./agent/agent-loop.js";
|
|
286
374
|
export { agent } from "./agent/run-agent.js";
|
|
287
375
|
export type { AgentOpts } from "./agent/run-agent.js";
|
|
288
|
-
export { AGENT_COMPOSE_MANUAL, buildAgentContextDoc, writeAgentContext } from "./agent/agent-context.js";
|
|
289
|
-
export type { AgentConnectorInfo } from "./agent/agent-context.js";
|
|
376
|
+
export { AGENT_COMPOSE_MANUAL, buildAgentContextDoc, writeAgentContext, buildAddedSessionBrief } from "./agent/agent-context.js";
|
|
377
|
+
export type { AgentConnectorInfo, AddedSessionBriefParams } from "./agent/agent-context.js";
|
|
290
378
|
export { AgentMessageSchema, parseAgentResponse } from "./agent/protocol.js";
|
|
291
379
|
export { importSourceModule, TMP_DIR, LATEST_VERSION } from "./utils/source-loader.js";
|
|
380
|
+
|
|
381
|
+
// The display palette (ADR-0040 §7) — the typed marker contract between
|
|
382
|
+
// `agentc display` and the session executors, plus the promoter that maps
|
|
383
|
+
// native-harness tool traffic onto the platform's rich-card part shapes.
|
|
384
|
+
export {
|
|
385
|
+
DISPLAY_MARKER_KEY, DISPLAY_MARKER_VERSION, PLAN_MAX_ENTRIES, PLAN_ENTRY_MAX_CHARS,
|
|
386
|
+
TABLE_MAX_COLUMNS, TABLE_MAX_ROWS, TABLE_CELL_MAX_CHARS,
|
|
387
|
+
CHART_MAX_SERIES, CHART_MAX_POINTS_PER_SERIES, CHART_LABEL_MAX_CHARS,
|
|
388
|
+
ASK_PROMPT_MAX_CHARS, ASK_MAX_OPTIONS, ASK_OPTION_ID_MAX_CHARS, ASK_OPTION_LABEL_MAX_CHARS,
|
|
389
|
+
serializeDisplayMarker, parseDisplayMarker, findDisplayMarker, clampPlanEntries,
|
|
390
|
+
clampTableData, clampChartSeries, clampChartAxisLabel, clampAskOptions,
|
|
391
|
+
detectAgentcInvocation, shellWords, createDisplayPromoter,
|
|
392
|
+
} from "./display.js";
|
|
393
|
+
export type {
|
|
394
|
+
DisplayMarker, DisplayPlanEntry, DisplayPlanStatus,
|
|
395
|
+
DisplayTableCell, DisplayTableColumn, DisplayTableData,
|
|
396
|
+
DisplayChartKind, DisplayChartPoint, DisplayChartSeries, DisplayAskOption,
|
|
397
|
+
AgentcInvocation, PromotedPart, DisplayPromoter, DisplayPromoterContext,
|
|
398
|
+
} from "./display.js";
|
package/src/pause/wrappers.ts
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
* closure over `ctx.pause`:
|
|
2
|
+
* The three opinionated pause wrappers (ADR-0006 §"SDK surface", ADR-0042),
|
|
3
|
+
* each a thin closure over `ctx.pause`:
|
|
4
4
|
*
|
|
5
5
|
* - `sleep` — a lightweight timed pause; resolves on its own TTL,
|
|
6
6
|
* skips the snapshot, returns void.
|
|
7
7
|
* - `waitForEvent` — pause until an event resumes by correlation key.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - `requestDecision` — a typed human/agent decision (wire `kind:
|
|
9
|
+
* "decision"`): the prompt/options/approvers ride
|
|
10
|
+
* `payload` for the dashboard's answer UI, and the
|
|
11
|
+
* resume value is the frozen `{ decision: string }`
|
|
12
|
+
* convention. `approvers` travel as RAW member refs
|
|
13
|
+
* (user ids or emails) — the SERVER resolves them
|
|
14
|
+
* against the team roster at pause-insert time and
|
|
15
|
+
* stamps the gate-bearing snapshots (ADR-0042 §2).
|
|
13
16
|
*
|
|
14
17
|
* Built from a `PauseFn` so the wrapper logic lives in one place and the step
|
|
15
18
|
* runner just spreads them onto the context next to `pause`.
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
import
|
|
21
|
+
import { z } from "zod";
|
|
19
22
|
import type { StepPauseRequest } from "../step-invocation/types.js";
|
|
20
23
|
import type { PauseRequest } from "./pause-core.js";
|
|
21
24
|
|
|
@@ -23,7 +26,7 @@ import type { PauseRequest } from "./pause-core.js";
|
|
|
23
26
|
export type PauseFn = <T = unknown>(req: PauseRequest<T>) => Promise<T>;
|
|
24
27
|
|
|
25
28
|
/** Internal: pause with an explicit wire `kind`. The wrappers stamp
|
|
26
|
-
* `sleep`/`event` through this; the public `ctx.pause` is always
|
|
29
|
+
* `decision`/`sleep`/`event` through this; the public `ctx.pause` is always
|
|
27
30
|
* `custom` and never exposes it. */
|
|
28
31
|
export type KindedPauseFn = <T = unknown>(req: PauseRequest<T>, kind: StepPauseRequest["kind"]) => Promise<T>;
|
|
29
32
|
|
|
@@ -35,9 +38,26 @@ export interface WaitForEventRequest<T> {
|
|
|
35
38
|
ttlMs?: number;
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
export interface DecisionRequest {
|
|
42
|
+
prompt: string;
|
|
43
|
+
/** Choices the decision UI offers as buttons; free text is always open. */
|
|
44
|
+
options?: Array<string | { label: string; value: string }>;
|
|
45
|
+
/** Team member refs (user ids or emails, ≤ 10) — with any named, ONLY a
|
|
46
|
+
* named approver may resolve the decision, and each gets an Activity
|
|
47
|
+
* ping. Resolution is server-side at pause creation. */
|
|
48
|
+
approvers?: string[];
|
|
49
|
+
/** DecisionDraft artifact rendered alongside the prompt. */
|
|
50
|
+
draft?: Record<string, unknown>;
|
|
51
|
+
ttlMs?: number;
|
|
52
|
+
correlationKey?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DecisionResultSchema = z.object({ decision: z.string() });
|
|
56
|
+
|
|
38
57
|
export interface PauseWrappers {
|
|
39
58
|
sleep(durationMs: number): Promise<void>;
|
|
40
59
|
waitForEvent<T = unknown>(req: WaitForEventRequest<T>): Promise<T>;
|
|
60
|
+
requestDecision(req: DecisionRequest): Promise<{ decision: string }>;
|
|
41
61
|
}
|
|
42
62
|
|
|
43
63
|
export function buildPauseWrappers(pause: KindedPauseFn): PauseWrappers {
|
|
@@ -61,5 +81,20 @@ export function buildPauseWrappers(pause: KindedPauseFn): PauseWrappers {
|
|
|
61
81
|
...(req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}),
|
|
62
82
|
}, "event");
|
|
63
83
|
},
|
|
84
|
+
|
|
85
|
+
requestDecision(req: DecisionRequest): Promise<{ decision: string }> {
|
|
86
|
+
return pause<{ decision: string }>({
|
|
87
|
+
reason: req.prompt,
|
|
88
|
+
payload: {
|
|
89
|
+
prompt: req.prompt,
|
|
90
|
+
...(req.options !== undefined ? { options: req.options } : {}),
|
|
91
|
+
...(req.approvers !== undefined ? { approvers: req.approvers } : {}),
|
|
92
|
+
...(req.draft !== undefined ? { draft: req.draft } : {}),
|
|
93
|
+
},
|
|
94
|
+
schema: DecisionResultSchema,
|
|
95
|
+
...(req.ttlMs !== undefined ? { ttlMs: req.ttlMs } : {}),
|
|
96
|
+
...(req.correlationKey !== undefined ? { correlationKey: req.correlationKey } : {}),
|
|
97
|
+
}, "decision");
|
|
98
|
+
},
|
|
64
99
|
};
|
|
65
100
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ask-human processor (ADR-0028).
|
|
3
|
+
*
|
|
4
|
+
* Makes "ask a human" a FIRST-CLASS agent affordance: when the agent calls the
|
|
5
|
+
* built-in `AskUserQuestion` tool, this short-circuits it into a SERVER pause
|
|
6
|
+
* (`requestPauseAndAwait`) — the run freezes (compute stops), the question +
|
|
7
|
+
* options land on the human's pause feed, and the human's answer comes back as
|
|
8
|
+
* the tool result. No `agentc pause` CLI for the model to remember, and no
|
|
9
|
+
* dependency on prompt discipline: the moment the agent asks, the run pauses.
|
|
10
|
+
*
|
|
11
|
+
* Loud by construction — the danger this fixes is a pause that SILENTLY doesn't
|
|
12
|
+
* happen (a stale in-sandbox CLI, a non-E2B substrate, an auth error) letting
|
|
13
|
+
* the agent proceed as if it had an answer:
|
|
14
|
+
* - pause cannot be created (server reject) → `Verdict.abort` ENDS the agent
|
|
15
|
+
* loop with a WorkflowError. The run fails loud; it never guesses an answer.
|
|
16
|
+
* - pause expires / is cancelled → the tool result says NO answer came and to
|
|
17
|
+
* not assume one.
|
|
18
|
+
*
|
|
19
|
+
* Lives in the shared `gateToolCall` chain, so one implementation covers every
|
|
20
|
+
* runtime (the Claude Agent SDK `PreToolUse` hook and the ACP permission path).
|
|
21
|
+
* No run credential in the env (local / non-sandbox) → no-op: `AskUserQuestion`
|
|
22
|
+
* passes through untouched so a dev invocation isn't hard-failed.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { Processor, ProcessorContext, ToolCall } from "./processor.js";
|
|
26
|
+
import { Verdict } from "./processor.js";
|
|
27
|
+
import { requestPauseAndAwait } from "../agent/pause-client.js";
|
|
28
|
+
import type { GatePauseConnection } from "./gate-pause.js";
|
|
29
|
+
|
|
30
|
+
/** The Claude built-in tool an agent uses to ask the user a question. */
|
|
31
|
+
export const ASK_USER_QUESTION_TOOL = "AskUserQuestion";
|
|
32
|
+
|
|
33
|
+
/** One question in an `AskUserQuestion` call (only the fields we read). */
|
|
34
|
+
interface AskQuestion { question?: unknown; header?: unknown; options?: unknown }
|
|
35
|
+
|
|
36
|
+
/** Pull the human-facing question + its option labels out of an
|
|
37
|
+
* `AskUserQuestion` tool input. We pause on the FIRST question (the common
|
|
38
|
+
* case); any others are folded into the reason so nothing is lost. */
|
|
39
|
+
function parseAsk(input: Record<string, unknown>): { reason: string; options: Array<{ id: string; label: string }> } {
|
|
40
|
+
const questions = Array.isArray(input.questions) ? (input.questions as AskQuestion[]) : [];
|
|
41
|
+
const first = questions[0] ?? {};
|
|
42
|
+
const head = typeof first.question === "string" && first.question.trim()
|
|
43
|
+
? first.question.trim()
|
|
44
|
+
: "The agent needs your input to continue.";
|
|
45
|
+
const extra = questions.length > 1
|
|
46
|
+
? ` (+${questions.length - 1} more question${questions.length > 2 ? "s" : ""})`
|
|
47
|
+
: "";
|
|
48
|
+
const options = Array.isArray(first.options)
|
|
49
|
+
? (first.options as Array<{ label?: unknown }>)
|
|
50
|
+
.map((o) => String(o?.label ?? "").trim())
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.map((label) => ({ id: label, label }))
|
|
53
|
+
: [];
|
|
54
|
+
return { reason: head + extra, options };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Unwrap the dashboard's `{ decision }` resume payload to the raw answer text. */
|
|
58
|
+
function answerText(decision: unknown): string {
|
|
59
|
+
const raw = decision !== null && typeof decision === "object" && "decision" in decision
|
|
60
|
+
? (decision as { decision: unknown }).decision
|
|
61
|
+
: decision;
|
|
62
|
+
return typeof raw === "string" ? raw.trim() : raw == null ? "" : JSON.stringify(raw);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Recognise the agent shelling out to `agentc pause` and extract the same
|
|
66
|
+
* {reason, options} we'd get from AskUserQuestion. We INTERCEPT it here — in the
|
|
67
|
+
* tool gate, BEFORE the command runs — so the pause takes the DESIGNED path (the
|
|
68
|
+
* answer returns as the tool result and the agent loop continues) instead of the
|
|
69
|
+
* command actually blocking inside the sandbox shell, which froze the runner
|
|
70
|
+
* mid-tool-exec and never resumed the loop. */
|
|
71
|
+
function parseAgentcPause(command: string): { reason: string; options: Array<{ id: string; label: string }> } | null {
|
|
72
|
+
if (!/(^|\s|&&|;|\|)\s*agentc\s+pause(\s|$)/.test(command)) return null;
|
|
73
|
+
const r = command.match(/--reason(?:=|\s+)(?:"([^"]*)"|'([^']*)'|(\S+))/);
|
|
74
|
+
const reason = (r?.[1] ?? r?.[2] ?? r?.[3] ?? "The agent needs your input to continue.").trim();
|
|
75
|
+
const options = [...command.matchAll(/--option(?:=|\s+)(?:"([^"]*)"|'([^']*)'|(\S+))/g)]
|
|
76
|
+
.map((m) => (m[1] ?? m[2] ?? m[3] ?? "").trim())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.map((label) => ({ id: label, label }));
|
|
79
|
+
return { reason, options };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Pull the ask (reason + options) from either the `AskUserQuestion` tool OR a
|
|
83
|
+
* `Bash` call running `agentc pause`. Null for anything else. */
|
|
84
|
+
function extractAsk(call: ToolCall): { reason: string; options: Array<{ id: string; label: string }> } | null {
|
|
85
|
+
if (call.toolName === ASK_USER_QUESTION_TOOL) return parseAsk(call.toolInput);
|
|
86
|
+
if (call.toolName === "Bash") {
|
|
87
|
+
const cmd = (call.toolInput as { command?: unknown })?.command;
|
|
88
|
+
return typeof cmd === "string" ? parseAgentcPause(cmd) : null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createAskHumanProcessor(opts: { connection?: GatePauseConnection } = {}): Processor {
|
|
94
|
+
return {
|
|
95
|
+
name: "ask-human",
|
|
96
|
+
async processToolCall(call: ToolCall, ctx: ProcessorContext) {
|
|
97
|
+
// Fires for AskUserQuestion OR a `Bash` call running `agentc pause` — both
|
|
98
|
+
// ask a human and must take the SAME processor path so the answer returns
|
|
99
|
+
// as the tool result and the agent loop continues.
|
|
100
|
+
const ask = extractAsk(call);
|
|
101
|
+
if (!ask) return Verdict.continue(call);
|
|
102
|
+
|
|
103
|
+
const conn = opts.connection ?? {
|
|
104
|
+
baseUrl: process.env.AGENT_COMPOSE_URL ?? "",
|
|
105
|
+
token: process.env.AGENT_COMPOSE_RUN_TOKEN ?? "",
|
|
106
|
+
runId: process.env.RUN_ID ?? "",
|
|
107
|
+
};
|
|
108
|
+
// No run credential (local / non-sandbox) — can't pause; let the tool
|
|
109
|
+
// through rather than hard-failing a dev invocation.
|
|
110
|
+
if (!conn.baseUrl || !conn.token || !conn.runId) return Verdict.continue(call);
|
|
111
|
+
|
|
112
|
+
let decision;
|
|
113
|
+
try {
|
|
114
|
+
decision = await requestPauseAndAwait({
|
|
115
|
+
baseUrl: conn.baseUrl, token: conn.token, runId: conn.runId,
|
|
116
|
+
reason: ask.reason,
|
|
117
|
+
...(ask.options.length ? { options: ask.options } : {}),
|
|
118
|
+
action: { tool: call.toolName, input: call.toolInput },
|
|
119
|
+
signal: ctx.abortSignal,
|
|
120
|
+
});
|
|
121
|
+
} catch (err) {
|
|
122
|
+
// The pause could NOT be honored (server reject, non-E2B substrate, auth).
|
|
123
|
+
// Abort the loop — never let the agent proceed as if it had an answer.
|
|
124
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
125
|
+
return Verdict.abort(`Could not ask the human — the run could not be paused (${msg}). Stopping rather than guessing an answer.`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (decision.status === "resolved") {
|
|
129
|
+
const answer = answerText(decision.decision);
|
|
130
|
+
return Verdict.deny(`The human answered: ${answer || "(no text returned)"}. Continue using this answer.`);
|
|
131
|
+
}
|
|
132
|
+
// Expired / cancelled — no answer. Do NOT let the agent assume one.
|
|
133
|
+
return Verdict.deny(`No answer came back (${decision.status}). Do NOT assume an answer — ask again, or stop and report exactly what you need from a human.`);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
package/src/processors/index.ts
CHANGED
|
@@ -20,3 +20,8 @@ export {
|
|
|
20
20
|
// through the shared gateToolCall chain.
|
|
21
21
|
export { createGatePauseProcessor } from "./gate-pause.js";
|
|
22
22
|
export type { GatePausePolicy, GatePauseApproval, GatePauseConnection } from "./gate-pause.js";
|
|
23
|
+
|
|
24
|
+
// ADR-0028 — first-class "ask a human": maps the agent's `AskUserQuestion` tool
|
|
25
|
+
// to a server pause and returns the human's answer as the tool result. Added by
|
|
26
|
+
// default to every agent (no-op unless the tool is granted + called).
|
|
27
|
+
export { createAskHumanProcessor, ASK_USER_QUESTION_TOOL } from "./ask-human.js";
|
|
@@ -60,6 +60,28 @@ const OPTION_KIND_FOR_VERDICT = {
|
|
|
60
60
|
deny: ["reject_once", "reject_always"],
|
|
61
61
|
} as const;
|
|
62
62
|
|
|
63
|
+
/** Session-lifecycle capabilities the agent advertised at `initialize`,
|
|
64
|
+
* surfaced (not discarded) so callers can pick a resume strategy
|
|
65
|
+
* (ADR-0037 §3). The in-sandbox CLI-agent runtimes ignore both — they
|
|
66
|
+
* resume via the CLI's own on-disk rollout (ADR-0020 Q4); the bridge
|
|
67
|
+
* daemon attempts `session/resume` only when `resume` is true. */
|
|
68
|
+
export interface AcpSessionCaps {
|
|
69
|
+
/** Agent supports `session/load` — full-history replay as
|
|
70
|
+
* `agent_message_chunk`s. Deliberately NOT used for resume: replaying
|
|
71
|
+
* history would re-emit the transcript as new parts and duplicate the
|
|
72
|
+
* conversation (ADR-0037 alternative D). */
|
|
73
|
+
loadSession: boolean;
|
|
74
|
+
/** Agent supports `session/resume` — context restore with NO replay. */
|
|
75
|
+
resume: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One entry of the agent's slash-command palette, as announced by the ACP
|
|
79
|
+
* `available_commands_update` session notification. */
|
|
80
|
+
export interface AcpAvailableCommand {
|
|
81
|
+
name: string;
|
|
82
|
+
description: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
63
85
|
/** Collaborators `AcpClientPeer` needs from `CliAgentRunner`, kept as a small
|
|
64
86
|
* injected surface so the peer stays transport-agnostic and unit-testable. */
|
|
65
87
|
export interface AcpClientPeerDeps {
|
|
@@ -80,6 +102,14 @@ export interface AcpClientPeerDeps {
|
|
|
80
102
|
/** Turn-scoped abort signal from `sendMessage`. On abort the peer sends
|
|
81
103
|
* `session/cancel` for the live session. */
|
|
82
104
|
signal?: AbortSignal;
|
|
105
|
+
/** The agent announced (or changed) its slash-command palette — an ACP
|
|
106
|
+
* `available_commands_update` session NOTIFICATION, which can arrive at
|
|
107
|
+
* any time (typically right after session/new, outside any prompt), so
|
|
108
|
+
* it rides a callback rather than the per-turn message stream. Each call
|
|
109
|
+
* carries the ENTIRE palette (replace semantics). Optional: the
|
|
110
|
+
* in-sandbox runtimes ignore it; the bridge daemon reports it to the
|
|
111
|
+
* server for composer autocomplete. */
|
|
112
|
+
onAvailableCommands?: (commands: AcpAvailableCommand[]) => void;
|
|
83
113
|
}
|
|
84
114
|
|
|
85
115
|
// ── The single session/update → AgentMessage normaliser ───────────────────────
|
|
@@ -270,6 +300,17 @@ export class AcpClientPeer {
|
|
|
270
300
|
// answers method-not-found.
|
|
271
301
|
const handler: Client = {
|
|
272
302
|
sessionUpdate: (params: acp.SessionNotification): void => {
|
|
303
|
+
// The slash-command palette is session state, not turn content —
|
|
304
|
+
// it can land OUTSIDE any prompt (right after session/new), when
|
|
305
|
+
// no turn queue is being drained, so it goes to its own callback
|
|
306
|
+
// instead of the AgentMessage stream (which drops it).
|
|
307
|
+
if (params.update.sessionUpdate === "available_commands_update") {
|
|
308
|
+
this.deps.onAvailableCommands?.(params.update.availableCommands.map((c) => ({
|
|
309
|
+
name: c.name,
|
|
310
|
+
description: c.description,
|
|
311
|
+
})));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
273
314
|
// Push mapped messages onto the queue. Tolerate updates that race in
|
|
274
315
|
// after cancel (the agent may flush a few before unwinding) — the
|
|
275
316
|
// normaliser handles them the same way.
|
|
@@ -290,9 +331,12 @@ export class AcpClientPeer {
|
|
|
290
331
|
* Negotiate the wire protocol at `initialize`. Pins `protocolVersion` to
|
|
291
332
|
* integer 1 and advertises NO fs / terminal capabilities. Returns the
|
|
292
333
|
* version the agent negotiated — `CliAgentRunner` compares it to
|
|
293
|
-
* `ACP_PROTOCOL_VERSION` to decide ACP-path vs. legacy-JSONL fallback
|
|
334
|
+
* `ACP_PROTOCOL_VERSION` to decide ACP-path vs. legacy-JSONL fallback —
|
|
335
|
+
* plus the agent's advertised session-lifecycle capabilities (`caps`),
|
|
336
|
+
* which the bridge daemon caches as `agent_sessions.resume_supported`
|
|
337
|
+
* (ADR-0037 §3). Absent capability blocks mean "not supported".
|
|
294
338
|
*/
|
|
295
|
-
async initialize(): Promise<{ protocolVersion: number }> {
|
|
339
|
+
async initialize(): Promise<{ protocolVersion: number; caps: AcpSessionCaps }> {
|
|
296
340
|
const res = await this.conn.initialize({
|
|
297
341
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
298
342
|
clientCapabilities: {
|
|
@@ -300,7 +344,15 @@ export class AcpClientPeer {
|
|
|
300
344
|
terminal: false,
|
|
301
345
|
},
|
|
302
346
|
});
|
|
303
|
-
return {
|
|
347
|
+
return {
|
|
348
|
+
protocolVersion: res.protocolVersion,
|
|
349
|
+
caps: {
|
|
350
|
+
loadSession: res.agentCapabilities?.loadSession ?? false,
|
|
351
|
+
// Per the ACP schema, `resume` is a capability OBJECT (`{}` =
|
|
352
|
+
// supported), not a boolean — presence is the signal.
|
|
353
|
+
resume: res.agentCapabilities?.sessionCapabilities?.resume != null,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
304
356
|
}
|
|
305
357
|
|
|
306
358
|
/**
|
|
@@ -316,6 +368,23 @@ export class AcpClientPeer {
|
|
|
316
368
|
return res.sessionId;
|
|
317
369
|
}
|
|
318
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Resume an existing session by id — ACP `session/resume` (ADR-0037 §3).
|
|
373
|
+
* ADDITIVE and opt-in: the in-sandbox CLI-agent runtimes never call this
|
|
374
|
+
* (their resume path is the CLI's own persisted rollout, see
|
|
375
|
+
* `startSession`); the bridge daemon calls it only when `initialize`
|
|
376
|
+
* advertised `caps.resume`. Unlike `session/load` there is NO history
|
|
377
|
+
* replay — the agent restores its internal context and returns nothing,
|
|
378
|
+
* so nothing is re-emitted through the caller's part sink. Throws on an
|
|
379
|
+
* unknown/expired session id (JSON-RPC error from the agent) — the caller
|
|
380
|
+
* marks the durable row stale and falls back to `startSession` + full
|
|
381
|
+
* re-render. `this.sessionId` is only adopted on success.
|
|
382
|
+
*/
|
|
383
|
+
async resumeSession(cwd: string, acpSessionId: string): Promise<void> {
|
|
384
|
+
await this.conn.resumeSession({ sessionId: acpSessionId, cwd, mcpServers: [] });
|
|
385
|
+
this.sessionId = acpSessionId;
|
|
386
|
+
}
|
|
387
|
+
|
|
319
388
|
/**
|
|
320
389
|
* Drive one `session/prompt` turn, yielding `AgentMessage`s as the agent
|
|
321
390
|
* streams `session/update` notifications. Resolves the generator when the
|