@gajae-code/coding-agent 0.11.11 → 0.12.1
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/CHANGELOG.md +131 -4
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +83 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +38 -4
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +10 -19
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
- package/dist/types/modes/components/settings-selector.d.ts +11 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +44 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +82 -12
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +48 -19
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +96 -12
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -4
- package/dist/types/skill-state/workflow-hud.d.ts +9 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +44 -3
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -7
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +11 -13
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/async/job-manager.ts +78 -20
- package/src/capability/index.ts +8 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/team.ts +20 -2
- package/src/config/file-lock.ts +11 -2
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +74 -1
- package/src/config/settings.ts +246 -108
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
- package/src/defaults/gjc/skills/team/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/eval/py/executor.ts +7 -2
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +46 -5
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -943
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +42 -5
- package/src/gjc-runtime/team-runtime.ts +1085 -40
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
- package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
- package/src/gjc-runtime/workflow-manifest.ts +38 -247
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +21 -22
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +98 -0
- package/src/modes/acp/acp-agent.ts +339 -191
- package/src/modes/acp/acp-event-mapper.ts +14 -7
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +96 -28
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +457 -83
- package/src/modes/components/settings-selector.ts +116 -23
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +26 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +127 -36
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +22 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -5
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +984 -146
- package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +303 -0
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/control/dispatch.ts +5 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +23 -1
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +101 -10
- package/src/session/agent-session.ts +1286 -220
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2364 -438
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +500 -111
- package/src/setup/credential-import.ts +75 -13
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +32 -10
- package/src/skill-state/workflow-hud.ts +42 -2
- package/src/skill-state/workflow-mutation-guard.ts +560 -8
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +169 -118
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +281 -20
- package/src/tools/path-utils.ts +8 -3
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1560 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
PROTOCOL_VERSION,
|
|
26
26
|
type PromptRequest,
|
|
27
27
|
type PromptResponse,
|
|
28
|
+
RequestError,
|
|
28
29
|
type ResumeSessionRequest,
|
|
29
30
|
type ResumeSessionResponse,
|
|
30
31
|
type SessionInfo,
|
|
@@ -46,6 +47,7 @@ import { resolveAcpFinalText } from "../../sdk/acp/final-text";
|
|
|
46
47
|
import { ACP_MCP_LIFECYCLE_TIMEOUT_MS, type SessionLifecycleMcpServer } from "../../sdk/acp/mcp";
|
|
47
48
|
import { ensureBroker } from "../../sdk/broker/ensure";
|
|
48
49
|
import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../../sdk/client";
|
|
50
|
+
import type { SdkPromptTerminalOutcome } from "../../sdk/prompt-status";
|
|
49
51
|
import {
|
|
50
52
|
buildToolCallStartUpdate,
|
|
51
53
|
mapAgentSessionEventToAcpSessionUpdates,
|
|
@@ -65,26 +67,21 @@ const THINKING_CONFIG_ID = "thinking";
|
|
|
65
67
|
const SESSION_PAGE_SIZE = 50;
|
|
66
68
|
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
67
69
|
const MAX_ACP_REPLAY_PAGES = 10_000;
|
|
70
|
+
/** Bounded retention of settled prompt correlations so late duplicates stay closed. */
|
|
71
|
+
const SETTLED_PROMPT_CORRELATION_RETENTION = 16;
|
|
68
72
|
|
|
69
73
|
type JsonObject = Record<string, unknown>;
|
|
70
|
-
/**
|
|
71
|
-
* ACP prompt completion is tied to lifecycle activity observed after dispatch.
|
|
72
|
-
* A fast host may stream and finish a turn before acknowledging `turn.prompt`,
|
|
73
|
-
* so pre-ack activity is retained but cannot resolve the waiter until the
|
|
74
|
-
* acknowledgement supplies its command/turn correlation.
|
|
75
|
-
*/
|
|
76
74
|
interface PromptWaiter {
|
|
77
|
-
cancelRequested: boolean;
|
|
78
75
|
acknowledged: boolean;
|
|
79
|
-
activityObserved: boolean;
|
|
80
|
-
/** The prompt was accepted while the host was already busy, so its next valid idle ends the steer. */
|
|
81
|
-
steeringAtAcknowledgement: boolean;
|
|
82
76
|
/** Highest inbound frame sequence already observed when the prompt was acknowledged. */
|
|
83
77
|
boundary: number;
|
|
84
78
|
correlation: PromptCorrelation;
|
|
85
79
|
messageProgress?: { textEmitted: boolean; thoughtEmitted: boolean };
|
|
86
80
|
emittedAssistantText: string;
|
|
87
|
-
|
|
81
|
+
settled: boolean;
|
|
82
|
+
terminal?: { outcome: SdkPromptTerminalOutcome; correlation: PromptCorrelation };
|
|
83
|
+
/** Frames for an already-settled correlation held until acknowledgement resolves ownership. */
|
|
84
|
+
deferredFrames: JsonObject[];
|
|
88
85
|
resolve: (response: PromptResponse) => void;
|
|
89
86
|
reject: (error: Error) => void;
|
|
90
87
|
}
|
|
@@ -109,6 +106,9 @@ type SessionRecord = {
|
|
|
109
106
|
/** Start/update args retained because tool_execution_end does not carry them. */
|
|
110
107
|
toolArgs: Map<string, unknown>;
|
|
111
108
|
/** Actionable model-profile authentication failure detected before prompt dispatch. */
|
|
109
|
+
connectionId?: string;
|
|
110
|
+
/** Bounded set of correlations already settled; they stay closed for publication. */
|
|
111
|
+
settledPromptCorrelations: PromptCorrelation[];
|
|
112
112
|
authFailure?: string;
|
|
113
113
|
activePrompt?: PromptWaiter;
|
|
114
114
|
};
|
|
@@ -256,31 +256,97 @@ function correlationFrom(...values: unknown[]): PromptCorrelation {
|
|
|
256
256
|
return correlation;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
function
|
|
259
|
+
function hasCorrelation(correlation: PromptCorrelation): boolean {
|
|
260
|
+
return correlation.commandId !== undefined || correlation.turnId !== undefined;
|
|
261
|
+
}
|
|
262
|
+
function correlationsMatch(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
|
|
260
263
|
return (
|
|
261
|
-
(
|
|
262
|
-
(expected.
|
|
264
|
+
hasCorrelation(actual) &&
|
|
265
|
+
(expected.commandId === undefined || expected.commandId === actual.commandId) &&
|
|
266
|
+
(expected.turnId === undefined || expected.turnId === actual.turnId)
|
|
263
267
|
);
|
|
264
268
|
}
|
|
265
269
|
|
|
266
|
-
function
|
|
270
|
+
function hasCompleteCorrelation(correlation: PromptCorrelation): correlation is { commandId: string; turnId: string } {
|
|
267
271
|
return (
|
|
268
|
-
|
|
269
|
-
(
|
|
272
|
+
typeof correlation.commandId === "string" &&
|
|
273
|
+
correlation.commandId.trim().length > 0 &&
|
|
274
|
+
typeof correlation.turnId === "string" &&
|
|
275
|
+
correlation.turnId.trim().length > 0
|
|
270
276
|
);
|
|
271
277
|
}
|
|
272
278
|
|
|
273
|
-
function
|
|
274
|
-
return
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
279
|
+
function correlationsExactlyMatch(expected: PromptCorrelation, actual: PromptCorrelation): boolean {
|
|
280
|
+
return (
|
|
281
|
+
hasCompleteCorrelation(expected) &&
|
|
282
|
+
hasCompleteCorrelation(actual) &&
|
|
283
|
+
expected.commandId === actual.commandId &&
|
|
284
|
+
expected.turnId === actual.turnId
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function promptAcknowledgement(value: unknown): PromptCorrelation | undefined {
|
|
289
|
+
const candidate = object(value);
|
|
290
|
+
if (
|
|
291
|
+
!candidate ||
|
|
292
|
+
candidate.ok === false ||
|
|
293
|
+
candidate.error !== undefined ||
|
|
294
|
+
(candidate.accepted !== undefined && candidate.accepted !== true)
|
|
295
|
+
)
|
|
296
|
+
return undefined;
|
|
297
|
+
const payload = object(candidate.result) ?? candidate;
|
|
298
|
+
if (payload.accepted !== true) return undefined;
|
|
299
|
+
if (typeof payload.commandId !== "string" || payload.commandId.trim().length === 0) return undefined;
|
|
300
|
+
if (typeof payload.turnId !== "string" || payload.turnId.trim().length === 0) return undefined;
|
|
301
|
+
return { commandId: payload.commandId, turnId: payload.turnId };
|
|
302
|
+
}
|
|
303
|
+
function strictCorrelationFrom(...values: unknown[]): PromptCorrelation | undefined {
|
|
304
|
+
const correlation: PromptCorrelation = {};
|
|
305
|
+
let malformed = false;
|
|
306
|
+
for (const value of values) {
|
|
307
|
+
const candidate = object(value);
|
|
308
|
+
if (!candidate) continue;
|
|
309
|
+
for (const [field, aliases] of [
|
|
310
|
+
["commandId", ["commandId", "command_id"]],
|
|
311
|
+
["turnId", ["turnId", "turn_id"]],
|
|
312
|
+
] as const) {
|
|
313
|
+
for (const alias of aliases) {
|
|
314
|
+
if (!Object.hasOwn(candidate, alias)) continue;
|
|
315
|
+
const identity = candidate[alias];
|
|
316
|
+
if (typeof identity !== "string" || identity.trim().length === 0) {
|
|
317
|
+
malformed = true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const previous = correlation[field];
|
|
321
|
+
if (previous !== undefined && previous !== identity) malformed = true;
|
|
322
|
+
correlation[field] = identity;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return malformed ? undefined : correlation;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function terminalOutcome(event: JsonObject): SdkPromptTerminalOutcome | undefined {
|
|
330
|
+
const outcome = object(event.outcome);
|
|
331
|
+
if (!outcome) return undefined;
|
|
332
|
+
if (
|
|
333
|
+
outcome.kind === "stopped" &&
|
|
334
|
+
(outcome.reason === "end_turn" ||
|
|
335
|
+
outcome.reason === "max_tokens" ||
|
|
336
|
+
outcome.reason === "max_turn_requests" ||
|
|
337
|
+
outcome.reason === "refusal" ||
|
|
338
|
+
outcome.reason === "cancelled") &&
|
|
339
|
+
(outcome.provenance === "agent" || outcome.provenance === "client_cancel")
|
|
340
|
+
)
|
|
341
|
+
return outcome as SdkPromptTerminalOutcome;
|
|
342
|
+
if (
|
|
343
|
+
outcome.kind === "failed" &&
|
|
344
|
+
(outcome.code === "prompt_failed" || outcome.code === "prompt_deadline_exceeded") &&
|
|
345
|
+
typeof outcome.message === "string" &&
|
|
346
|
+
(outcome.provenance === "agent_failed" || outcome.provenance === "deadline")
|
|
347
|
+
)
|
|
348
|
+
return outcome as SdkPromptTerminalOutcome;
|
|
349
|
+
return undefined;
|
|
284
350
|
}
|
|
285
351
|
|
|
286
352
|
export type TranscriptReplayBlock = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
|
@@ -321,11 +387,9 @@ type ReceivedSdkEvent = {
|
|
|
321
387
|
* without treating notification-specific frames as agent lifecycle truth.
|
|
322
388
|
*/
|
|
323
389
|
function receivedSdkEvent(frame: JsonObject): ReceivedSdkEvent | undefined {
|
|
324
|
-
if (frame.type === "activity")
|
|
325
|
-
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
if (frame.type === "agent_start" || frame.type === "agent_end") return { event: frame };
|
|
390
|
+
if (frame.type === "activity") return undefined;
|
|
391
|
+
if (frame.type === "agent_start" || frame.type === "agent_end" || frame.type === "agent_failed")
|
|
392
|
+
return { event: frame };
|
|
329
393
|
if (frame.type !== "event") return undefined;
|
|
330
394
|
const payload = object(frame.payload);
|
|
331
395
|
if (!payload) return undefined;
|
|
@@ -532,23 +596,95 @@ export function acpPromptPayload(blocks: PromptRequest["prompt"]): {
|
|
|
532
596
|
return { text: text.join("\n"), images };
|
|
533
597
|
}
|
|
534
598
|
|
|
599
|
+
/**
|
|
600
|
+
* `AcpSdkAdapterError.code` is an internal string, but the SDK only derives a
|
|
601
|
+
* JSON-RPC code from a `RequestError`. Everything else collapses to an opaque
|
|
602
|
+
* `-32603 Internal error`, which hides the reason and defeats client-side
|
|
603
|
+
* recovery (an ACP client cannot see that it must authenticate). Map the codes
|
|
604
|
+
* that have a defined ACP/JSON-RPC counterpart onto a real `RequestError`.
|
|
605
|
+
*/
|
|
606
|
+
export function acpRequestFailure(error: unknown): unknown {
|
|
607
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
608
|
+
if (typeof code !== "string") return error;
|
|
609
|
+
const message = error instanceof Error ? error.message : code;
|
|
610
|
+
switch (code) {
|
|
611
|
+
case "authentication_failed":
|
|
612
|
+
return RequestError.authRequired({ code, details: message }, message);
|
|
613
|
+
// `not_found` stays -32603 with its discriminator in `data`: ACP's
|
|
614
|
+
// `resourceNotFound` (-32002) is a URI-addressed resource error, and an unknown
|
|
615
|
+
// session id is not a resource URI. Pinned ACP core-v1 conformance also requires
|
|
616
|
+
// -32603/-32000 for a prompt against an unknown session.
|
|
617
|
+
case "invalid_input":
|
|
618
|
+
case "unsupported":
|
|
619
|
+
case "unsupported_content":
|
|
620
|
+
return RequestError.invalidParams({ code, details: message }, message);
|
|
621
|
+
default:
|
|
622
|
+
// The remaining internal codes (conflict, unavailable, busy, …) have no ACP
|
|
623
|
+
// counterpart and stay -32603. Keep the discriminator in `data` so a client can
|
|
624
|
+
// branch on retry/reconnect instead of parsing an English message.
|
|
625
|
+
return RequestError.internalError({ code, details: message }, message);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
535
629
|
/** Registers a permission provider only when the ACP client requires prompts. */
|
|
536
630
|
export function acpProviderRegistrations(
|
|
537
631
|
capabilities: ClientCapabilities | undefined,
|
|
538
632
|
env: NodeJS.ProcessEnv = process.env,
|
|
539
633
|
): AcpProviderRegistration[] {
|
|
540
634
|
return [
|
|
635
|
+
// `fs.readTextFile` and `fs.writeTextFile` are independently optional, so the
|
|
636
|
+
// advertised methods travel with the lease instead of being inferred as both.
|
|
541
637
|
...(capabilities?.fs?.readTextFile || capabilities?.fs?.writeTextFile
|
|
542
|
-
? [
|
|
638
|
+
? [
|
|
639
|
+
{
|
|
640
|
+
capability: "fs",
|
|
641
|
+
definitions: [
|
|
642
|
+
...(capabilities.fs.readTextFile ? [{ name: "fs.readTextFile" }] : []),
|
|
643
|
+
...(capabilities.fs.writeTextFile ? [{ name: "fs.writeTextFile" }] : []),
|
|
644
|
+
],
|
|
645
|
+
},
|
|
646
|
+
]
|
|
543
647
|
: []),
|
|
544
648
|
...(capabilities?.terminal ? [{ capability: "terminal", definitions: [] }] : []),
|
|
545
649
|
...(resolveAcpPermissionMode(capabilities, env) === "prompt"
|
|
546
650
|
? [{ capability: "permission", definitions: [] }]
|
|
547
651
|
: []),
|
|
548
|
-
{ capability: "ui", definitions: [] },
|
|
652
|
+
...(capabilities?.elicitation?.form ? [{ capability: "ui", definitions: [] }] : []),
|
|
549
653
|
];
|
|
550
654
|
}
|
|
551
655
|
|
|
656
|
+
export function createAcpReverseConnection(connection: AgentSideConnection, sessionId: string): AcpReverseConnection {
|
|
657
|
+
const methods: Record<string, string> = {
|
|
658
|
+
request: "session/request_permission",
|
|
659
|
+
"permission.request": "session/request_permission",
|
|
660
|
+
"fs.readTextFile": "fs/read_text_file",
|
|
661
|
+
"fs.writeTextFile": "fs/write_text_file",
|
|
662
|
+
"terminal.create": "terminal/create",
|
|
663
|
+
"ui.elicit": "elicitation/create",
|
|
664
|
+
};
|
|
665
|
+
return {
|
|
666
|
+
request: async (
|
|
667
|
+
method: string,
|
|
668
|
+
params: JsonObject,
|
|
669
|
+
options?: { cancellationSignal?: AbortSignal },
|
|
670
|
+
): Promise<unknown> => {
|
|
671
|
+
const name = methods[method];
|
|
672
|
+
if (!name)
|
|
673
|
+
throw new AcpSdkAdapterError("acp_reverse_unavailable", `ACP reverse method is unavailable: ${method}`);
|
|
674
|
+
const rawRequest = (connection as unknown as Record<string, unknown>).request;
|
|
675
|
+
if (typeof rawRequest !== "function")
|
|
676
|
+
throw new AcpSdkAdapterError("acp_reverse_unavailable", "ACP reverse request surface is unavailable.");
|
|
677
|
+
return await (
|
|
678
|
+
rawRequest as (
|
|
679
|
+
method: string,
|
|
680
|
+
input: JsonObject,
|
|
681
|
+
options?: { cancellationSignal?: AbortSignal },
|
|
682
|
+
) => Promise<unknown>
|
|
683
|
+
).call(connection, name, { ...params, sessionId }, options);
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
552
688
|
/** Maps ACP permission handling to the session's canonical SDK policy. */
|
|
553
689
|
export async function applyAcpPermissionMode(
|
|
554
690
|
adapter: Pick<AcpSdkAdapter, "control">,
|
|
@@ -568,82 +704,6 @@ export async function applyAcpStartupOptions(
|
|
|
568
704
|
if (options?.thinkingLevel) await adapter.control("thinking.set", { level: options.thinkingLevel });
|
|
569
705
|
}
|
|
570
706
|
|
|
571
|
-
/** ACP form elicitation uses the client-facing reverse surface without owning a session runtime. */
|
|
572
|
-
export function createAcpExtensionUiContext(
|
|
573
|
-
connection: AgentSideConnection,
|
|
574
|
-
getSessionId: () => string,
|
|
575
|
-
capabilities: ClientCapabilities | undefined,
|
|
576
|
-
): {
|
|
577
|
-
select: (
|
|
578
|
-
message: string,
|
|
579
|
-
options: string[],
|
|
580
|
-
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
581
|
-
) => Promise<string | undefined>;
|
|
582
|
-
confirm: (
|
|
583
|
-
message: string,
|
|
584
|
-
detail?: string,
|
|
585
|
-
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
586
|
-
) => Promise<boolean>;
|
|
587
|
-
input: (
|
|
588
|
-
message: string,
|
|
589
|
-
placeholder?: string,
|
|
590
|
-
dialog?: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void },
|
|
591
|
-
) => Promise<string | undefined>;
|
|
592
|
-
} {
|
|
593
|
-
const elicit = async (
|
|
594
|
-
kind: "select" | "confirm" | "input",
|
|
595
|
-
message: string,
|
|
596
|
-
options: string[] | undefined,
|
|
597
|
-
dialog: { signal?: AbortSignal; timeout?: number; onTimeout?: () => void } | undefined,
|
|
598
|
-
): Promise<unknown> => {
|
|
599
|
-
if (!capabilities?.elicitation?.form || dialog?.signal?.aborted) return undefined;
|
|
600
|
-
const request = (
|
|
601
|
-
connection as unknown as { unstable_createElicitation(input: JsonObject): Promise<JsonObject> }
|
|
602
|
-
).unstable_createElicitation({
|
|
603
|
-
sessionId: getSessionId(),
|
|
604
|
-
message,
|
|
605
|
-
requestedSchema: {
|
|
606
|
-
type: "object",
|
|
607
|
-
properties: {
|
|
608
|
-
value:
|
|
609
|
-
kind === "confirm" ? { type: "boolean" } : { type: "string", ...(options ? { enum: options } : {}) },
|
|
610
|
-
},
|
|
611
|
-
required: ["value"],
|
|
612
|
-
},
|
|
613
|
-
});
|
|
614
|
-
let timer: NodeJS.Timeout | undefined;
|
|
615
|
-
const timeout =
|
|
616
|
-
dialog?.timeout === undefined
|
|
617
|
-
? undefined
|
|
618
|
-
: new Promise<undefined>(resolve => {
|
|
619
|
-
timer = setTimeout(() => {
|
|
620
|
-
dialog.onTimeout?.();
|
|
621
|
-
resolve(undefined);
|
|
622
|
-
}, dialog.timeout);
|
|
623
|
-
});
|
|
624
|
-
try {
|
|
625
|
-
const response = timeout ? await Promise.race([request, timeout]) : await request;
|
|
626
|
-
return object(object(response)?.content)?.value;
|
|
627
|
-
} catch {
|
|
628
|
-
return undefined;
|
|
629
|
-
} finally {
|
|
630
|
-
if (timer) clearTimeout(timer);
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
return {
|
|
634
|
-
select: async (message, options, dialog) => {
|
|
635
|
-
const value = await elicit("select", message, options, dialog);
|
|
636
|
-
return typeof value === "string" && options.includes(value) ? value : undefined;
|
|
637
|
-
},
|
|
638
|
-
confirm: async (message, detail, dialog) =>
|
|
639
|
-
(await elicit("confirm", detail ? `${message}\n\n${detail}` : message, undefined, dialog)) === true,
|
|
640
|
-
input: async (message, placeholder, dialog) => {
|
|
641
|
-
const value = await elicit("input", placeholder ? `${message}\n\n${placeholder}` : message, undefined, dialog);
|
|
642
|
-
return typeof value === "string" ? value : undefined;
|
|
643
|
-
},
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
|
|
647
707
|
/**
|
|
648
708
|
* ACP is a pure SDK client. Session processes are created and resumed by the
|
|
649
709
|
* broker, while all per-session operations use that session's authenticated SDK
|
|
@@ -933,20 +993,19 @@ export class AcpAgent implements Agent {
|
|
|
933
993
|
|
|
934
994
|
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
935
995
|
const record = this.#sessions.get(params.sessionId);
|
|
936
|
-
if (!record) throw new AcpSdkAdapterError("not_found", `
|
|
996
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${params.sessionId}`);
|
|
937
997
|
if (record.activePrompt) throw new AcpSdkAdapterError("conflict", "ACP session already has an active prompt.");
|
|
938
998
|
if (record.authFailure) throw new AcpSdkAdapterError("authentication_failed", record.authFailure);
|
|
939
999
|
const payload = acpPromptPayload(params.prompt);
|
|
940
1000
|
let waiter!: PromptWaiter;
|
|
941
1001
|
const response = new Promise<PromptResponse>((resolve, reject) => {
|
|
942
1002
|
waiter = {
|
|
943
|
-
cancelRequested: false,
|
|
944
1003
|
acknowledged: false,
|
|
945
|
-
activityObserved: false,
|
|
946
|
-
steeringAtAcknowledgement: record.busy,
|
|
947
1004
|
boundary: record.inboundSequence,
|
|
948
1005
|
correlation: {},
|
|
949
1006
|
emittedAssistantText: "",
|
|
1007
|
+
settled: false,
|
|
1008
|
+
deferredFrames: [],
|
|
950
1009
|
resolve,
|
|
951
1010
|
reject,
|
|
952
1011
|
};
|
|
@@ -957,14 +1016,29 @@ export class AcpAgent implements Agent {
|
|
|
957
1016
|
text: payload.text,
|
|
958
1017
|
...(payload.images.length ? { images: payload.images } : {}),
|
|
959
1018
|
});
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1019
|
+
const acknowledgementCorrelation = promptAcknowledgement(acknowledgement);
|
|
1020
|
+
if (!acknowledgementCorrelation)
|
|
1021
|
+
throw new AcpSdkAdapterError(
|
|
1022
|
+
"invalid_prompt_acknowledgement",
|
|
1023
|
+
"SDK prompt acknowledgement must accept the prompt and include commandId and turnId.",
|
|
1024
|
+
);
|
|
1025
|
+
// Retain the acknowledgement ingress boundary with its complete correlation.
|
|
963
1026
|
waiter.boundary = record.inboundSequence;
|
|
964
|
-
waiter.correlation =
|
|
1027
|
+
waiter.correlation = acknowledgementCorrelation;
|
|
965
1028
|
waiter.acknowledged = true;
|
|
1029
|
+
// Frames held while ownership was unknown belong to this prompt only when the
|
|
1030
|
+
// acknowledgement proves their complete correlation matches exactly.
|
|
1031
|
+
const deferred = waiter.deferredFrames.splice(0);
|
|
1032
|
+
for (const deferredFrame of deferred)
|
|
1033
|
+
if (correlationsExactlyMatch(waiter.correlation, correlationFrom(deferredFrame)))
|
|
1034
|
+
record.frameTail = record.frameTail.then(
|
|
1035
|
+
async () => await this.#handleSdkFrame(params.sessionId, record.adapter, deferredFrame),
|
|
1036
|
+
);
|
|
966
1037
|
this.#settlePrompt(record, waiter);
|
|
967
1038
|
} catch (error) {
|
|
1039
|
+
waiter.deferredFrames.length = 0;
|
|
1040
|
+
waiter.terminal = undefined;
|
|
1041
|
+
waiter.settled = true;
|
|
968
1042
|
if (record.activePrompt === waiter) record.activePrompt = undefined;
|
|
969
1043
|
throw error;
|
|
970
1044
|
}
|
|
@@ -973,8 +1047,7 @@ export class AcpAgent implements Agent {
|
|
|
973
1047
|
|
|
974
1048
|
async cancel(params: { sessionId: string }): Promise<void> {
|
|
975
1049
|
const record = this.#sessions.get(params.sessionId);
|
|
976
|
-
if (!record) throw new AcpSdkAdapterError("not_found", `
|
|
977
|
-
const waiter = record.activePrompt;
|
|
1050
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${params.sessionId}`);
|
|
978
1051
|
const acknowledgement = await record.adapter.cancel();
|
|
979
1052
|
const result = object(object(acknowledgement)?.result) ?? object(acknowledgement);
|
|
980
1053
|
if (result?.aborted !== true)
|
|
@@ -982,12 +1055,19 @@ export class AcpAgent implements Agent {
|
|
|
982
1055
|
"abort_unacknowledged",
|
|
983
1056
|
"SDK did not acknowledge cancellation of the active prompt.",
|
|
984
1057
|
);
|
|
985
|
-
// Do not retroactively mark a waiter that already settled while the abort
|
|
986
|
-
// request was in flight. A cancelled response means the abort itself won.
|
|
987
|
-
if (waiter && record.activePrompt === waiter) waiter.cancelRequested = true;
|
|
988
1058
|
}
|
|
989
1059
|
|
|
990
1060
|
async extMethod(method: string, params: JsonObject): Promise<JsonObject> {
|
|
1061
|
+
// An unrecognized extension method is a protocol failure, not an application
|
|
1062
|
+
// result: it must reach the client as JSON-RPC -32601 rather than a resolved
|
|
1063
|
+
// payload. Recognized `_gjc/*` methods keep their `{ok:false}` result contract.
|
|
1064
|
+
if (
|
|
1065
|
+
method !== "session/set_model" &&
|
|
1066
|
+
method !== "_gjc/sdk/global" &&
|
|
1067
|
+
method !== "_gjc/sdk/control" &&
|
|
1068
|
+
method !== "_gjc/sdk/query"
|
|
1069
|
+
)
|
|
1070
|
+
throw RequestError.methodNotFound(method);
|
|
991
1071
|
try {
|
|
992
1072
|
if (method === "session/set_model") {
|
|
993
1073
|
const sessionId = typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
@@ -1001,13 +1081,10 @@ export class AcpAgent implements Agent {
|
|
|
1001
1081
|
const result = await (await this.#brokerAdapter()).handle(method, params);
|
|
1002
1082
|
return object(result) ?? {};
|
|
1003
1083
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
return object(result) ?? {};
|
|
1009
|
-
}
|
|
1010
|
-
throw new AcpSdkAdapterError("method_not_found", `Unknown ACP ext method: ${method}`);
|
|
1084
|
+
const id = typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
1085
|
+
if (!id) throw new AcpSdkAdapterError("invalid_input", "sessionId is required.");
|
|
1086
|
+
const result = await this.#adapter(id).handle(method, params);
|
|
1087
|
+
return object(result) ?? {};
|
|
1011
1088
|
} catch (error) {
|
|
1012
1089
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "internal";
|
|
1013
1090
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1074,8 +1151,9 @@ export class AcpAgent implements Agent {
|
|
|
1074
1151
|
if (attached) {
|
|
1075
1152
|
if (path.resolve(attached.cwd) !== path.resolve(cwd))
|
|
1076
1153
|
throw new AcpSdkAdapterError("conflict", `ACP session ${id} has conflicting cwd authority.`);
|
|
1077
|
-
|
|
1078
|
-
|
|
1154
|
+
// ACP clients replay their declared MCP servers when reconnecting. The live
|
|
1155
|
+
// session host remains authoritative for its immutable configuration, so
|
|
1156
|
+
// attachment must not reinterpret the replay as a mutation request.
|
|
1079
1157
|
return;
|
|
1080
1158
|
}
|
|
1081
1159
|
const knownCwd = this.#knownSessionCwds.get(id);
|
|
@@ -1113,8 +1191,8 @@ export class AcpAgent implements Agent {
|
|
|
1113
1191
|
const indexed = await this.#scopedBrokerSession(id, cwd);
|
|
1114
1192
|
this.#assertSessionEpoch(id, epoch);
|
|
1115
1193
|
if (indexed?.live) {
|
|
1116
|
-
|
|
1117
|
-
|
|
1194
|
+
// A reconnect may repeat the client's MCP declaration. Attaching to the
|
|
1195
|
+
// existing endpoint preserves the live host's immutable configuration.
|
|
1118
1196
|
const result = await this.#brokerEndpoint(id, indexed.endpointGeneration);
|
|
1119
1197
|
this.#assertSessionEpoch(id, epoch);
|
|
1120
1198
|
await this.#attach(id, cwd, endpoint(result), epoch);
|
|
@@ -1193,6 +1271,19 @@ export class AcpAgent implements Agent {
|
|
|
1193
1271
|
connection: this.#reverseConnection(id),
|
|
1194
1272
|
providers: this.#providers(),
|
|
1195
1273
|
});
|
|
1274
|
+
let capabilities: JsonObject | undefined;
|
|
1275
|
+
try {
|
|
1276
|
+
const response = object(await adapter.query("runtime.capabilities"));
|
|
1277
|
+
const result = object(response?.result) ?? response;
|
|
1278
|
+
// Q18 is a paged query surface: the capability object arrives as the single
|
|
1279
|
+
// page item, so fall back to the envelope only for direct-result hosts.
|
|
1280
|
+
capabilities = object(pageItems(result)[0]) ?? result;
|
|
1281
|
+
} catch {}
|
|
1282
|
+
if (capabilities?.promptTerminalOutcomeVersion !== 1)
|
|
1283
|
+
throw new AcpSdkAdapterError(
|
|
1284
|
+
"unavailable",
|
|
1285
|
+
"This ACP client requires a newer GJC SDK session; restart the session.",
|
|
1286
|
+
);
|
|
1196
1287
|
this.#assertSessionEpoch(id, epoch);
|
|
1197
1288
|
const record: SessionRecord = {
|
|
1198
1289
|
cwd,
|
|
@@ -1201,7 +1292,9 @@ export class AcpAgent implements Agent {
|
|
|
1201
1292
|
unsubscribe: () => {},
|
|
1202
1293
|
reconnectUnsubscribe: () => {},
|
|
1203
1294
|
frameTail: Promise.resolve(),
|
|
1295
|
+
settledPromptCorrelations: [],
|
|
1204
1296
|
inboundSequence: 0,
|
|
1297
|
+
connectionId: adapter.connectionId,
|
|
1205
1298
|
busy: false,
|
|
1206
1299
|
toolArgs: new Map(),
|
|
1207
1300
|
};
|
|
@@ -1378,7 +1471,7 @@ export class AcpAgent implements Agent {
|
|
|
1378
1471
|
|
|
1379
1472
|
#adapter(id: string): AcpSdkAdapter {
|
|
1380
1473
|
const record = this.#sessions.get(id);
|
|
1381
|
-
if (!record) throw new AcpSdkAdapterError("not_found", `
|
|
1474
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${id}`);
|
|
1382
1475
|
return record.adapter;
|
|
1383
1476
|
}
|
|
1384
1477
|
|
|
@@ -1398,29 +1491,18 @@ export class AcpAgent implements Agent {
|
|
|
1398
1491
|
}
|
|
1399
1492
|
|
|
1400
1493
|
#reverseConnection(sessionId: string): AcpReverseConnection {
|
|
1401
|
-
|
|
1402
|
-
"fs.readTextFile": "readTextFile",
|
|
1403
|
-
"fs.writeTextFile": "writeTextFile",
|
|
1404
|
-
"terminal.create": "createTerminal",
|
|
1405
|
-
"permission.request": "requestPermission",
|
|
1406
|
-
"ui.elicit": "unstable_createElicitation",
|
|
1407
|
-
};
|
|
1408
|
-
return {
|
|
1409
|
-
request: async (method: string, params: JsonObject): Promise<unknown> => {
|
|
1410
|
-
const name = methods[method] ?? method;
|
|
1411
|
-
const target = (this.#connection as unknown as Record<string, unknown>)[name];
|
|
1412
|
-
if (typeof target !== "function")
|
|
1413
|
-
throw new AcpSdkAdapterError("acp_reverse_unavailable", `ACP reverse method is unavailable: ${method}`);
|
|
1414
|
-
const request = method === "permission.request" ? { ...params, sessionId } : params;
|
|
1415
|
-
return await (target as (input: JsonObject) => Promise<unknown>)(request);
|
|
1416
|
-
},
|
|
1417
|
-
};
|
|
1494
|
+
return createAcpReverseConnection(this.#connection, sessionId);
|
|
1418
1495
|
}
|
|
1419
1496
|
|
|
1420
1497
|
#observeSessionActivity(record: SessionRecord, frame: JsonObject): void {
|
|
1498
|
+
if (frame.type === "activity") {
|
|
1499
|
+
if (frame.state === "busy") record.busy = true;
|
|
1500
|
+
else if (frame.state === "idle") record.busy = false;
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1421
1503
|
const event = receivedSdkEvent(frame)?.event;
|
|
1422
1504
|
if (event?.type === "agent_start") record.busy = true;
|
|
1423
|
-
else if (event?.type === "agent_end") record.busy = false;
|
|
1505
|
+
else if (event?.type === "agent_end" || event?.type === "agent_failed") record.busy = false;
|
|
1424
1506
|
}
|
|
1425
1507
|
|
|
1426
1508
|
#frameProcessingFailure(error: unknown): AcpSdkAdapterError {
|
|
@@ -1432,22 +1514,72 @@ export class AcpAgent implements Agent {
|
|
|
1432
1514
|
#enqueueSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject): void {
|
|
1433
1515
|
const record = this.#sessions.get(id);
|
|
1434
1516
|
if (!record || record.adapter !== adapter) return;
|
|
1435
|
-
//
|
|
1436
|
-
// A frame received before acknowledgement stays before that prompt's boundary.
|
|
1517
|
+
// Ingress ordering is recorded before queued work begins.
|
|
1437
1518
|
this.#observeSessionActivity(record, frame);
|
|
1438
|
-
|
|
1439
|
-
const task = record.frameTail.then(async () => await this.#handleSdkFrame(id, adapter, frame
|
|
1519
|
+
++record.inboundSequence;
|
|
1520
|
+
const task = record.frameTail.then(async () => await this.#handleSdkFrame(id, adapter, frame));
|
|
1440
1521
|
record.frameTail = task.catch(
|
|
1441
1522
|
async error => await this.#failSession(id, adapter, this.#frameProcessingFailure(error)),
|
|
1442
1523
|
);
|
|
1443
1524
|
}
|
|
1444
1525
|
|
|
1445
|
-
async #handleSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject
|
|
1526
|
+
async #handleSdkFrame(id: string, adapter: AcpSdkAdapter, frame: JsonObject): Promise<void> {
|
|
1446
1527
|
const record = this.#sessions.get(id);
|
|
1447
1528
|
if (!record || record.adapter !== adapter) return;
|
|
1529
|
+
if ((frame.type === "hello" || frame.type === "server_hello") && typeof frame.connectionId === "string") {
|
|
1530
|
+
const reconnected = record.connectionId !== undefined && record.connectionId !== frame.connectionId;
|
|
1531
|
+
record.connectionId = frame.connectionId;
|
|
1532
|
+
if (reconnected) {
|
|
1533
|
+
const waiter = record.activePrompt;
|
|
1534
|
+
if (waiter && !waiter.settled && !waiter.terminal) {
|
|
1535
|
+
record.activePrompt = undefined;
|
|
1536
|
+
waiter.settled = true;
|
|
1537
|
+
if (hasCorrelation(waiter.correlation)) {
|
|
1538
|
+
record.settledPromptCorrelations.push(waiter.correlation);
|
|
1539
|
+
while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
|
|
1540
|
+
record.settledPromptCorrelations.shift();
|
|
1541
|
+
}
|
|
1542
|
+
waiter.reject(
|
|
1543
|
+
new AcpSdkAdapterError(
|
|
1544
|
+
"connection_closed",
|
|
1545
|
+
"The prompt owner connection was lost before completion.",
|
|
1546
|
+
),
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1448
1552
|
const received = receivedSdkEvent(frame);
|
|
1449
1553
|
if (!received) return;
|
|
1450
1554
|
const { event, wirePayload } = received;
|
|
1555
|
+
const isTerminal = event.type === "agent_end" || event.type === "agent_failed";
|
|
1556
|
+
const correlation = (isTerminal ? strictCorrelationFrom(frame, event) : correlationFrom(frame, event)) ?? {};
|
|
1557
|
+
const activePrompt = record.activePrompt;
|
|
1558
|
+
const outcome = isTerminal ? terminalOutcome(event) : undefined;
|
|
1559
|
+
if (isTerminal) {
|
|
1560
|
+
// Terminal ownership requires a complete identity. Unowned, partial, and
|
|
1561
|
+
// duplicate terminals are never allowed to publish or query anything.
|
|
1562
|
+
if (!hasCompleteCorrelation(correlation) || !activePrompt || activePrompt.settled) return;
|
|
1563
|
+
if (!activePrompt.acknowledged) {
|
|
1564
|
+
// Hold the entire frame until the prompt acknowledgement proves ownership.
|
|
1565
|
+
activePrompt.deferredFrames.push(frame);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
if (!correlationsExactlyMatch(activePrompt.correlation, correlation) || activePrompt.terminal) return;
|
|
1569
|
+
if (!outcome) {
|
|
1570
|
+
const detail =
|
|
1571
|
+
typeof (event as { error?: { message?: unknown } }).error?.message === "string"
|
|
1572
|
+
? (event as { error: { message: string } }).error.message
|
|
1573
|
+
: "the prompt terminal omitted a valid normalized outcome";
|
|
1574
|
+
this.#rejectPrompt(
|
|
1575
|
+
record,
|
|
1576
|
+
activePrompt,
|
|
1577
|
+
new AcpSdkAdapterError("connection_closed", `ACP prompt terminal was invalid: ${detail}`),
|
|
1578
|
+
);
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
activePrompt.terminal = { outcome, correlation };
|
|
1582
|
+
}
|
|
1451
1583
|
const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
|
|
1452
1584
|
if (
|
|
1453
1585
|
toolCallId &&
|
|
@@ -1456,31 +1588,21 @@ export class AcpAgent implements Agent {
|
|
|
1456
1588
|
) {
|
|
1457
1589
|
record.toolArgs.set(toolCallId, event.args);
|
|
1458
1590
|
}
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
sequence > activePrompt.boundary &&
|
|
1469
|
-
!correlationsConflict(activePrompt.correlation, correlation)
|
|
1470
|
-
) {
|
|
1471
|
-
if (isPromptActivity(String(event.type))) {
|
|
1472
|
-
activePrompt.activityObserved = true;
|
|
1473
|
-
} else if (event.type === "agent_end") {
|
|
1474
|
-
if (
|
|
1475
|
-
correlationsMatch(activePrompt.correlation, correlation) ||
|
|
1476
|
-
correlation.commandId !== undefined ||
|
|
1477
|
-
correlation.turnId !== undefined ||
|
|
1478
|
-
activePrompt.activityObserved ||
|
|
1479
|
-
activePrompt.steeringAtAcknowledgement
|
|
1480
|
-
)
|
|
1481
|
-
activePrompt.pendingTerminal = correlation;
|
|
1591
|
+
const settledCorrelation = record.settledPromptCorrelations.some(settled =>
|
|
1592
|
+
correlationsMatch(settled, correlation),
|
|
1593
|
+
);
|
|
1594
|
+
if (settledCorrelation) {
|
|
1595
|
+
// Frames for an already-settled correlation stay closed until an active prompt
|
|
1596
|
+
// acknowledges the exact same identity.
|
|
1597
|
+
if (activePrompt && !activePrompt.settled && !activePrompt.acknowledged) {
|
|
1598
|
+
activePrompt.deferredFrames.push(frame);
|
|
1599
|
+
return;
|
|
1482
1600
|
}
|
|
1601
|
+
if (!activePrompt || activePrompt.settled || !correlationsMatch(activePrompt.correlation, correlation)) return;
|
|
1483
1602
|
}
|
|
1603
|
+
// After a correlated settlement with no active prompt, correlationless wire frames
|
|
1604
|
+
// have no prompt to belong to and must not publish further updates.
|
|
1605
|
+
if (!record.activePrompt && record.settledPromptCorrelations.length > 0 && !hasCorrelation(correlation)) return;
|
|
1484
1606
|
if (wirePayload) {
|
|
1485
1607
|
for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(wirePayload as never, id, {
|
|
1486
1608
|
cwd: record.cwd,
|
|
@@ -1546,21 +1668,47 @@ export class AcpAgent implements Agent {
|
|
|
1546
1668
|
}
|
|
1547
1669
|
}
|
|
1548
1670
|
await this.#emitEndOfTurnUpdates(id, adapter);
|
|
1671
|
+
} else if (event.type === "agent_failed") {
|
|
1672
|
+
await this.#emitEndOfTurnUpdates(id, adapter);
|
|
1549
1673
|
}
|
|
1550
1674
|
if (activePrompt) this.#settlePrompt(record, activePrompt);
|
|
1551
1675
|
}
|
|
1552
1676
|
|
|
1677
|
+
#rejectPrompt(record: SessionRecord, waiter: PromptWaiter, error: AcpSdkAdapterError): void {
|
|
1678
|
+
if (record.activePrompt !== waiter || waiter.settled) return;
|
|
1679
|
+
record.activePrompt = undefined;
|
|
1680
|
+
waiter.settled = true;
|
|
1681
|
+
waiter.deferredFrames.length = 0;
|
|
1682
|
+
waiter.terminal = undefined;
|
|
1683
|
+
if (hasCompleteCorrelation(waiter.correlation)) {
|
|
1684
|
+
record.settledPromptCorrelations.push(waiter.correlation);
|
|
1685
|
+
while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
|
|
1686
|
+
record.settledPromptCorrelations.shift();
|
|
1687
|
+
}
|
|
1688
|
+
waiter.reject(error);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1553
1691
|
#settlePrompt(record: SessionRecord, waiter: PromptWaiter): void {
|
|
1554
|
-
if (record.activePrompt !== waiter || !waiter.acknowledged || !waiter.
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
!waiter.steeringAtAcknowledgement
|
|
1560
|
-
)
|
|
1692
|
+
if (record.activePrompt !== waiter || waiter.settled || !waiter.acknowledged || !waiter.terminal) return;
|
|
1693
|
+
// A terminal captured before acknowledgement is only this prompt's terminal when the
|
|
1694
|
+
// eventual acknowledgement correlates with it; otherwise it belonged to an earlier prompt.
|
|
1695
|
+
if (!correlationsExactlyMatch(waiter.correlation, waiter.terminal.correlation)) {
|
|
1696
|
+
waiter.terminal = undefined;
|
|
1561
1697
|
return;
|
|
1698
|
+
}
|
|
1562
1699
|
record.activePrompt = undefined;
|
|
1563
|
-
waiter.
|
|
1700
|
+
waiter.settled = true;
|
|
1701
|
+
if (hasCorrelation(waiter.correlation)) {
|
|
1702
|
+
record.settledPromptCorrelations.push(waiter.correlation);
|
|
1703
|
+
while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
|
|
1704
|
+
record.settledPromptCorrelations.shift();
|
|
1705
|
+
}
|
|
1706
|
+
const { outcome } = waiter.terminal;
|
|
1707
|
+
if (outcome.kind === "stopped") {
|
|
1708
|
+
waiter.resolve({ stopReason: outcome.reason });
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
waiter.reject(new AcpSdkAdapterError(outcome.code, outcome.message));
|
|
1564
1712
|
}
|
|
1565
1713
|
|
|
1566
1714
|
async #emitEndOfTurnUpdates(id: string, adapter: AcpSdkAdapter): Promise<void> {
|
|
@@ -1634,7 +1782,7 @@ export class AcpAgent implements Agent {
|
|
|
1634
1782
|
rejectUnavailableStartupPreset = false,
|
|
1635
1783
|
): Promise<Pick<NewSessionResponse, "configOptions" | "modes">> {
|
|
1636
1784
|
const record = this.#sessions.get(id);
|
|
1637
|
-
if (!record) throw new AcpSdkAdapterError("not_found", `
|
|
1785
|
+
if (!record) throw new AcpSdkAdapterError("not_found", `Unknown session, not found: ${id}`);
|
|
1638
1786
|
const modelPreset = this.#startupOptions?.modelPreset;
|
|
1639
1787
|
const [config, modelCatalog] = await Promise.all([
|
|
1640
1788
|
record.adapter.query("config.list/get"),
|