@jini-ai/agent-runtime 0.1.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/LICENSE +201 -0
- package/README.md +43 -0
- package/dist/acp-model-probe.d.ts +48 -0
- package/dist/acp-model-probe.d.ts.map +1 -0
- package/dist/acp-model-probe.js +15 -0
- package/dist/acp-model-probe.js.map +1 -0
- package/dist/agent-protocol/acp/account-failure.d.ts +54 -0
- package/dist/agent-protocol/acp/account-failure.d.ts.map +1 -0
- package/dist/agent-protocol/acp/account-failure.js +37 -0
- package/dist/agent-protocol/acp/account-failure.js.map +1 -0
- package/dist/agent-protocol/acp/constants.d.ts +26 -0
- package/dist/agent-protocol/acp/constants.d.ts.map +1 -0
- package/dist/agent-protocol/acp/constants.js +39 -0
- package/dist/agent-protocol/acp/constants.js.map +1 -0
- package/dist/agent-protocol/acp/index.d.ts +14 -0
- package/dist/agent-protocol/acp/index.d.ts.map +1 -0
- package/dist/agent-protocol/acp/index.js +14 -0
- package/dist/agent-protocol/acp/index.js.map +1 -0
- package/dist/agent-protocol/acp/json.d.ts +82 -0
- package/dist/agent-protocol/acp/json.d.ts.map +1 -0
- package/dist/agent-protocol/acp/json.js +144 -0
- package/dist/agent-protocol/acp/json.js.map +1 -0
- package/dist/agent-protocol/acp/models.d.ts +112 -0
- package/dist/agent-protocol/acp/models.d.ts.map +1 -0
- package/dist/agent-protocol/acp/models.js +293 -0
- package/dist/agent-protocol/acp/models.js.map +1 -0
- package/dist/agent-protocol/acp/rpc.d.ts +107 -0
- package/dist/agent-protocol/acp/rpc.d.ts.map +1 -0
- package/dist/agent-protocol/acp/rpc.js +162 -0
- package/dist/agent-protocol/acp/rpc.js.map +1 -0
- package/dist/agent-protocol/acp/session-params.d.ts +55 -0
- package/dist/agent-protocol/acp/session-params.d.ts.map +1 -0
- package/dist/agent-protocol/acp/session-params.js +81 -0
- package/dist/agent-protocol/acp/session-params.js.map +1 -0
- package/dist/agent-protocol/acp/session.d.ts +101 -0
- package/dist/agent-protocol/acp/session.d.ts.map +1 -0
- package/dist/agent-protocol/acp/session.js +832 -0
- package/dist/agent-protocol/acp/session.js.map +1 -0
- package/dist/agent-protocol/acp/text-suppression.d.ts +73 -0
- package/dist/agent-protocol/acp/text-suppression.d.ts.map +1 -0
- package/dist/agent-protocol/acp/text-suppression.js +229 -0
- package/dist/agent-protocol/acp/text-suppression.js.map +1 -0
- package/dist/agent-protocol/acp/types.d.ts +27 -0
- package/dist/agent-protocol/acp/types.d.ts.map +1 -0
- package/dist/agent-protocol/acp/types.js +2 -0
- package/dist/agent-protocol/acp/types.js.map +1 -0
- package/dist/agent-protocol/acp/updates.d.ts +166 -0
- package/dist/agent-protocol/acp/updates.d.ts.map +1 -0
- package/dist/agent-protocol/acp/updates.js +268 -0
- package/dist/agent-protocol/acp/updates.js.map +1 -0
- package/dist/agent-protocol/core/index.d.ts +6 -0
- package/dist/agent-protocol/core/index.d.ts.map +1 -0
- package/dist/agent-protocol/core/index.js +6 -0
- package/dist/agent-protocol/core/index.js.map +1 -0
- package/dist/agent-protocol/core/json-line-stream.d.ts +42 -0
- package/dist/agent-protocol/core/json-line-stream.d.ts.map +1 -0
- package/dist/agent-protocol/core/json-line-stream.js +333 -0
- package/dist/agent-protocol/core/json-line-stream.js.map +1 -0
- package/dist/agent-protocol/index.d.ts +8 -0
- package/dist/agent-protocol/index.d.ts.map +1 -0
- package/dist/agent-protocol/index.js +8 -0
- package/dist/agent-protocol/index.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/events.d.ts +26 -0
- package/dist/agent-protocol/pi-rpc/events.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/events.js +167 -0
- package/dist/agent-protocol/pi-rpc/events.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/index.d.ts +11 -0
- package/dist/agent-protocol/pi-rpc/index.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/index.js +11 -0
- package/dist/agent-protocol/pi-rpc/index.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/internal.d.ts +45 -0
- package/dist/agent-protocol/pi-rpc/internal.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/internal.js +41 -0
- package/dist/agent-protocol/pi-rpc/internal.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/models.d.ts +21 -0
- package/dist/agent-protocol/pi-rpc/models.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/models.js +48 -0
- package/dist/agent-protocol/pi-rpc/models.js.map +1 -0
- package/dist/agent-protocol/pi-rpc/session.d.ts +106 -0
- package/dist/agent-protocol/pi-rpc/session.d.ts.map +1 -0
- package/dist/agent-protocol/pi-rpc/session.js +371 -0
- package/dist/agent-protocol/pi-rpc/session.js.map +1 -0
- package/dist/amr-model-cache.d.ts +40 -0
- package/dist/amr-model-cache.d.ts.map +1 -0
- package/dist/amr-model-cache.js +79 -0
- package/dist/amr-model-cache.js.map +1 -0
- package/dist/amr-profile-resolver.d.ts +21 -0
- package/dist/amr-profile-resolver.d.ts.map +1 -0
- package/dist/amr-profile-resolver.js +4 -0
- package/dist/amr-profile-resolver.js.map +1 -0
- package/dist/artifact-taxonomy.d.ts +27 -0
- package/dist/artifact-taxonomy.d.ts.map +1 -0
- package/dist/artifact-taxonomy.js +5 -0
- package/dist/artifact-taxonomy.js.map +1 -0
- package/dist/auth.d.ts +25 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +332 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities.d.ts +9 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +2 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/claude-stream.d.ts +37 -0
- package/dist/claude-stream.d.ts.map +1 -0
- package/dist/claude-stream.js +660 -0
- package/dist/claude-stream.js.map +1 -0
- package/dist/copilot-stream.d.ts +37 -0
- package/dist/copilot-stream.d.ts.map +1 -0
- package/dist/copilot-stream.js +136 -0
- package/dist/copilot-stream.js.map +1 -0
- package/dist/defs/aider.d.ts +13 -0
- package/dist/defs/aider.d.ts.map +1 -0
- package/dist/defs/aider.js +63 -0
- package/dist/defs/aider.js.map +1 -0
- package/dist/defs/amp.d.ts +12 -0
- package/dist/defs/amp.d.ts.map +1 -0
- package/dist/defs/amp.js +56 -0
- package/dist/defs/amp.js.map +1 -0
- package/dist/defs/amr.d.ts +37 -0
- package/dist/defs/amr.d.ts.map +1 -0
- package/dist/defs/amr.js +303 -0
- package/dist/defs/amr.js.map +1 -0
- package/dist/defs/antigravity.d.ts +25 -0
- package/dist/defs/antigravity.d.ts.map +1 -0
- package/dist/defs/antigravity.js +213 -0
- package/dist/defs/antigravity.js.map +1 -0
- package/dist/defs/claude.d.ts +30 -0
- package/dist/defs/claude.d.ts.map +1 -0
- package/dist/defs/claude.js +130 -0
- package/dist/defs/claude.js.map +1 -0
- package/dist/defs/codebuddy.d.ts +26 -0
- package/dist/defs/codebuddy.d.ts.map +1 -0
- package/dist/defs/codebuddy.js +124 -0
- package/dist/defs/codebuddy.js.map +1 -0
- package/dist/defs/codex.d.ts +30 -0
- package/dist/defs/codex.d.ts.map +1 -0
- package/dist/defs/codex.js +196 -0
- package/dist/defs/codex.js.map +1 -0
- package/dist/defs/copilot.d.ts +12 -0
- package/dist/defs/copilot.d.ts.map +1 -0
- package/dist/defs/copilot.js +88 -0
- package/dist/defs/copilot.js.map +1 -0
- package/dist/defs/cursor-agent.d.ts +27 -0
- package/dist/defs/cursor-agent.d.ts.map +1 -0
- package/dist/defs/cursor-agent.js +93 -0
- package/dist/defs/cursor-agent.js.map +1 -0
- package/dist/defs/deepseek.d.ts +12 -0
- package/dist/defs/deepseek.d.ts.map +1 -0
- package/dist/defs/deepseek.js +53 -0
- package/dist/defs/deepseek.js.map +1 -0
- package/dist/defs/devin.d.ts +12 -0
- package/dist/defs/devin.d.ts.map +1 -0
- package/dist/defs/devin.js +45 -0
- package/dist/defs/devin.js.map +1 -0
- package/dist/defs/grok-build.d.ts +26 -0
- package/dist/defs/grok-build.d.ts.map +1 -0
- package/dist/defs/grok-build.js +105 -0
- package/dist/defs/grok-build.js.map +1 -0
- package/dist/defs/hermes.d.ts +13 -0
- package/dist/defs/hermes.d.ts.map +1 -0
- package/dist/defs/hermes.js +51 -0
- package/dist/defs/hermes.js.map +1 -0
- package/dist/defs/index.d.ts +33 -0
- package/dist/defs/index.d.ts.map +1 -0
- package/dist/defs/index.js +33 -0
- package/dist/defs/index.js.map +1 -0
- package/dist/defs/kilo.d.ts +12 -0
- package/dist/defs/kilo.d.ts.map +1 -0
- package/dist/defs/kilo.js +20 -0
- package/dist/defs/kilo.js.map +1 -0
- package/dist/defs/kimi.d.ts +13 -0
- package/dist/defs/kimi.d.ts.map +1 -0
- package/dist/defs/kimi.js +26 -0
- package/dist/defs/kimi.js.map +1 -0
- package/dist/defs/kiro.d.ts +12 -0
- package/dist/defs/kiro.d.ts.map +1 -0
- package/dist/defs/kiro.js +20 -0
- package/dist/defs/kiro.js.map +1 -0
- package/dist/defs/mimo.d.ts +13 -0
- package/dist/defs/mimo.d.ts.map +1 -0
- package/dist/defs/mimo.js +26 -0
- package/dist/defs/mimo.js.map +1 -0
- package/dist/defs/opencode.d.ts +27 -0
- package/dist/defs/opencode.d.ts.map +1 -0
- package/dist/defs/opencode.js +89 -0
- package/dist/defs/opencode.js.map +1 -0
- package/dist/defs/pi.d.ts +18 -0
- package/dist/defs/pi.d.ts.map +1 -0
- package/dist/defs/pi.js +94 -0
- package/dist/defs/pi.js.map +1 -0
- package/dist/defs/qoder.d.ts +11 -0
- package/dist/defs/qoder.d.ts.map +1 -0
- package/dist/defs/qoder.js +50 -0
- package/dist/defs/qoder.js.map +1 -0
- package/dist/defs/qwen.d.ts +11 -0
- package/dist/defs/qwen.d.ts.map +1 -0
- package/dist/defs/qwen.js +28 -0
- package/dist/defs/qwen.js.map +1 -0
- package/dist/defs/reasonix.d.ts +20 -0
- package/dist/defs/reasonix.d.ts.map +1 -0
- package/dist/defs/reasonix.js +60 -0
- package/dist/defs/reasonix.js.map +1 -0
- package/dist/defs/shared.d.ts +23 -0
- package/dist/defs/shared.d.ts.map +1 -0
- package/dist/defs/shared.js +58 -0
- package/dist/defs/shared.js.map +1 -0
- package/dist/defs/trae-cli.d.ts +14 -0
- package/dist/defs/trae-cli.d.ts.map +1 -0
- package/dist/defs/trae-cli.js +22 -0
- package/dist/defs/trae-cli.js.map +1 -0
- package/dist/defs/vibe.d.ts +12 -0
- package/dist/defs/vibe.d.ts.map +1 -0
- package/dist/defs/vibe.js +20 -0
- package/dist/defs/vibe.js.map +1 -0
- package/dist/detection.d.ts +14 -0
- package/dist/detection.d.ts.map +1 -0
- package/dist/detection.js +266 -0
- package/dist/detection.js.map +1 -0
- package/dist/diagnostics.d.ts +8 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +95 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/env.d.ts +19 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +95 -0
- package/dist/env.js.map +1 -0
- package/dist/executables.d.ts +24 -0
- package/dist/executables.d.ts.map +1 -0
- package/dist/executables.js +349 -0
- package/dist/executables.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/invocation.d.ts +6 -0
- package/dist/invocation.d.ts.map +1 -0
- package/dist/invocation.js +47 -0
- package/dist/invocation.js.map +1 -0
- package/dist/json-event-stream.d.ts +9 -0
- package/dist/json-event-stream.d.ts.map +1 -0
- package/dist/json-event-stream.js +856 -0
- package/dist/json-event-stream.js.map +1 -0
- package/dist/launch.d.ts +12 -0
- package/dist/launch.d.ts.map +1 -0
- package/dist/launch.js +221 -0
- package/dist/launch.js.map +1 -0
- package/dist/mcp.d.ts +44 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +12 -0
- package/dist/mcp.js.map +1 -0
- package/dist/metadata.d.ts +24 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +100 -0
- package/dist/metadata.js.map +1 -0
- package/dist/mmd-routes.d.ts +12 -0
- package/dist/mmd-routes.d.ts.map +1 -0
- package/dist/mmd-routes.js +146 -0
- package/dist/mmd-routes.js.map +1 -0
- package/dist/model-registry.d.ts +120 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +77 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/models.d.ts +16 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +93 -0
- package/dist/models.js.map +1 -0
- package/dist/opencode-log.d.ts +16 -0
- package/dist/opencode-log.d.ts.map +1 -0
- package/dist/opencode-log.js +163 -0
- package/dist/opencode-log.js.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +29 -0
- package/dist/paths.js.map +1 -0
- package/dist/pi-models.d.ts +13 -0
- package/dist/pi-models.d.ts.map +1 -0
- package/dist/pi-models.js +36 -0
- package/dist/pi-models.js.map +1 -0
- package/dist/prompt-augmenter.d.ts +60 -0
- package/dist/prompt-augmenter.d.ts.map +1 -0
- package/dist/prompt-augmenter.js +26 -0
- package/dist/prompt-augmenter.js.map +1 -0
- package/dist/prompt-budget.d.ts +20 -0
- package/dist/prompt-budget.d.ts.map +1 -0
- package/dist/prompt-budget.js +226 -0
- package/dist/prompt-budget.js.map +1 -0
- package/dist/prompt-file.d.ts +7 -0
- package/dist/prompt-file.d.ts.map +1 -0
- package/dist/prompt-file.js +27 -0
- package/dist/prompt-file.js.map +1 -0
- package/dist/providers/aihubmix.d.ts +99 -0
- package/dist/providers/aihubmix.d.ts.map +1 -0
- package/dist/providers/aihubmix.js +239 -0
- package/dist/providers/aihubmix.js.map +1 -0
- package/dist/providers/anthropic-messages.d.ts +116 -0
- package/dist/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/providers/anthropic-messages.js +313 -0
- package/dist/providers/anthropic-messages.js.map +1 -0
- package/dist/providers/azure-chat.d.ts +99 -0
- package/dist/providers/azure-chat.d.ts.map +1 -0
- package/dist/providers/azure-chat.js +164 -0
- package/dist/providers/azure-chat.js.map +1 -0
- package/dist/providers/connection-guard.d.ts +67 -0
- package/dist/providers/connection-guard.d.ts.map +1 -0
- package/dist/providers/connection-guard.js +211 -0
- package/dist/providers/connection-guard.js.map +1 -0
- package/dist/providers/elevenlabs.d.ts +28 -0
- package/dist/providers/elevenlabs.d.ts.map +1 -0
- package/dist/providers/elevenlabs.js +138 -0
- package/dist/providers/elevenlabs.js.map +1 -0
- package/dist/providers/google-messages.d.ts +108 -0
- package/dist/providers/google-messages.d.ts.map +1 -0
- package/dist/providers/google-messages.js +284 -0
- package/dist/providers/google-messages.js.map +1 -0
- package/dist/providers/google.d.ts +20 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/google.js.map +1 -0
- package/dist/providers/index.d.ts +39 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +39 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/model-catalog.d.ts +32 -0
- package/dist/providers/model-catalog.d.ts.map +1 -0
- package/dist/providers/model-catalog.js +394 -0
- package/dist/providers/model-catalog.js.map +1 -0
- package/dist/providers/oauth-callback-server.d.ts +35 -0
- package/dist/providers/oauth-callback-server.d.ts.map +1 -0
- package/dist/providers/oauth-callback-server.js +217 -0
- package/dist/providers/oauth-callback-server.js.map +1 -0
- package/dist/providers/oauth-credentials.d.ts +29 -0
- package/dist/providers/oauth-credentials.d.ts.map +1 -0
- package/dist/providers/oauth-credentials.js +70 -0
- package/dist/providers/oauth-credentials.js.map +1 -0
- package/dist/providers/oauth-provider.d.ts +95 -0
- package/dist/providers/oauth-provider.d.ts.map +1 -0
- package/dist/providers/oauth-provider.js +130 -0
- package/dist/providers/oauth-provider.js.map +1 -0
- package/dist/providers/oauth-tokens.d.ts +35 -0
- package/dist/providers/oauth-tokens.d.ts.map +1 -0
- package/dist/providers/oauth-tokens.js +136 -0
- package/dist/providers/oauth-tokens.js.map +1 -0
- package/dist/providers/ollama-chat.d.ts +106 -0
- package/dist/providers/ollama-chat.d.ts.map +1 -0
- package/dist/providers/ollama-chat.js +292 -0
- package/dist/providers/ollama-chat.js.map +1 -0
- package/dist/providers/openai-chat.d.ts +147 -0
- package/dist/providers/openai-chat.d.ts.map +1 -0
- package/dist/providers/openai-chat.js +302 -0
- package/dist/providers/openai-chat.js.map +1 -0
- package/dist/providers/pkce.d.ts +81 -0
- package/dist/providers/pkce.d.ts.map +1 -0
- package/dist/providers/pkce.js +176 -0
- package/dist/providers/pkce.js.map +1 -0
- package/dist/providers/sse-decode.d.ts +47 -0
- package/dist/providers/sse-decode.d.ts.map +1 -0
- package/dist/providers/sse-decode.js +109 -0
- package/dist/providers/sse-decode.js.map +1 -0
- package/dist/providers/token-params.d.ts +29 -0
- package/dist/providers/token-params.d.ts.map +1 -0
- package/dist/providers/token-params.js +41 -0
- package/dist/providers/token-params.js.map +1 -0
- package/dist/providers/turn-end-guard.d.ts +37 -0
- package/dist/providers/turn-end-guard.d.ts.map +1 -0
- package/dist/providers/turn-end-guard.js +42 -0
- package/dist/providers/turn-end-guard.js.map +1 -0
- package/dist/providers/types.d.ts +36 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +13 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/qoder-stream.d.ts +19 -0
- package/dist/qoder-stream.d.ts.map +1 -0
- package/dist/qoder-stream.js +167 -0
- package/dist/qoder-stream.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +58 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolution.d.ts +2 -0
- package/dist/resolution.d.ts.map +1 -0
- package/dist/resolution.js +19 -0
- package/dist/resolution.js.map +1 -0
- package/dist/role-marker-guard.d.ts +53 -0
- package/dist/role-marker-guard.d.ts.map +1 -0
- package/dist/role-marker-guard.js +280 -0
- package/dist/role-marker-guard.js.map +1 -0
- package/dist/telemetry-sink.d.ts +35 -0
- package/dist/telemetry-sink.d.ts.map +1 -0
- package/dist/telemetry-sink.js +5 -0
- package/dist/telemetry-sink.js.map +1 -0
- package/dist/terminal-launch.d.ts +11 -0
- package/dist/terminal-launch.d.ts.map +1 -0
- package/dist/terminal-launch.js +123 -0
- package/dist/terminal-launch.js.map +1 -0
- package/dist/types.d.ts +185 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/** @module agent-protocol/acp/session
|
|
2
|
+
* ACP session orchestrator: performs the full initialize → session/new (or
|
|
3
|
+
* session/load) → session/prompt handshake with an already-spawned ACP
|
|
4
|
+
* subprocess, streams updates to the daemon event bus, handles permission
|
|
5
|
+
* replies, artifact-write mirroring, DSML text suppression, and clean abort.
|
|
6
|
+
* Depends on every other acp/ file and the core JSON-line stream. Consumed by
|
|
7
|
+
* connection-test and server entry points (via the acp/ barrel).
|
|
8
|
+
*/
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { createDsmlArtifactTextSuppressor, createToolCallTextSuppressor, } from './text-suppression.js';
|
|
11
|
+
import { createJsonLineStream } from '../core/index.js';
|
|
12
|
+
import { ACP_PROTOCOL_VERSION, DEFAULT_STAGE_TIMEOUT_MS, ACP_ARTIFACT_ECHO_START_RE, ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT, AMR_STDERR_RETRY_TAIL_LIMIT, } from './constants.js';
|
|
13
|
+
import { errorMessage, asObject, extractAcpUpdateText } from './json.js';
|
|
14
|
+
import { sendRpc, sendRpcResult, isJsonRpcId, rpcErrorMessage, rpcErrorData, rpcErrorRetryable, promotedOpenCodeSessionErrorPayload, formatUsage, } from './rpc.js';
|
|
15
|
+
import { acpRawEventShape, isAcpCompletedStatus, isAcpTerminalFailureStatus, acpToolCallId, isAcpArtifactWriteLabel, isAcpArtifactWriteUpdate, acpArtifactWritePath, promotedAmrRetryStatusPayload, promotedAmrStderrPayload, } from './updates.js';
|
|
16
|
+
import { findModelConfigOption, currentModelFromSessionResult, modelSelectionErrorIsRecoverable, } from './models.js';
|
|
17
|
+
import { buildAcpSessionNewParams, buildPromptBlocks } from './session-params.js';
|
|
18
|
+
import { noopAccountFailureClassifier } from './account-failure.js';
|
|
19
|
+
/**
|
|
20
|
+
* Attaches an ACP protocol session to an already-spawned child process and
|
|
21
|
+
* drives the full JSON-RPC conversation from handshake to prompt completion.
|
|
22
|
+
*
|
|
23
|
+
* Sequence:
|
|
24
|
+
* 1. Sends `initialize` to confirm the protocol version.
|
|
25
|
+
* 2. Sends `session/new` (or `session/load` when `resumeSessionId` is set).
|
|
26
|
+
* 3. Optionally sends `session/set_model` when a non-default model is requested.
|
|
27
|
+
* 4. Sends `session/prompt` with the user's prompt and any image attachments.
|
|
28
|
+
* 5. Streams `session/update` events to the `send` callback, translating:
|
|
29
|
+
* - `agent_thought_chunk` → `thinking_start` / `thinking_delta`
|
|
30
|
+
* - `agent_message_chunk` → `text_delta` (with DSML and tool-call text suppression)
|
|
31
|
+
* - `tool_call` / `tool_call_update` → deferred `tool_use` / `tool_result` pairs
|
|
32
|
+
* - status updates → `agent.status` events
|
|
33
|
+
* 6. Handles `session/request_permission` calls through an injected policy,
|
|
34
|
+
* failing closed when no policy is supplied.
|
|
35
|
+
* 7. On prompt completion, flushes suppression buffers, emits usage, and closes stdin.
|
|
36
|
+
*
|
|
37
|
+
* Returns a controller object that the caller may use to query session state
|
|
38
|
+
* and to abort the in-progress turn.
|
|
39
|
+
*
|
|
40
|
+
* @param options - See `AttachAcpSessionOptions` for all fields.
|
|
41
|
+
* @returns A controller with `hasFatalError`, `getDurableSessionId`,
|
|
42
|
+
* `completedSuccessfully`, and `abort` methods.
|
|
43
|
+
*/
|
|
44
|
+
export function attachAcpSession({ child, prompt, cwd, model, imagePaths = [], mcpServers, envFormat = 'array', send, clientName = 'agent-runtime', clientVersion = 'runtime-adapter', stageTimeoutMs = DEFAULT_STAGE_TIMEOUT_MS, executionProfile = 'filesystem', modelUnavailableErrorCode, accountFailureClassifier = noopAccountFailureClassifier, resumeSessionId, onPermissionRequest, onCliReady, onSessionInit, }) {
|
|
45
|
+
const runStartedAt = Date.now();
|
|
46
|
+
const effectiveCwd = path.resolve(cwd || process.cwd());
|
|
47
|
+
if (!child.stdin || !child.stdout) {
|
|
48
|
+
throw new Error('ACP child process must expose stdin and stdout streams');
|
|
49
|
+
}
|
|
50
|
+
const stdin = child.stdin;
|
|
51
|
+
const stdout = child.stdout;
|
|
52
|
+
let expectedId = 1;
|
|
53
|
+
let nextId = 2;
|
|
54
|
+
let promptRequestId = null;
|
|
55
|
+
let setModelRequestId = null;
|
|
56
|
+
let sessionId = null;
|
|
57
|
+
// The durable upstream session handle reported by the agent on session/new or
|
|
58
|
+
// session/load (e.g. a vendor bridge's own session id). The caller stores it
|
|
59
|
+
// per conversation to resume next turn. Distinct from `sessionId`, which is
|
|
60
|
+
// the ACP wrapper id.
|
|
61
|
+
let durableSessionId = null;
|
|
62
|
+
let activeModel = null;
|
|
63
|
+
let modelConfigId = null;
|
|
64
|
+
let emittedThinkingStart = false;
|
|
65
|
+
let emittedFirstTokenStatus = false;
|
|
66
|
+
let emittedTextChunk = false;
|
|
67
|
+
let emittedVisibleTextChunk = false;
|
|
68
|
+
let emittedToolCall = false;
|
|
69
|
+
let emittedConcreteToolEvent = false;
|
|
70
|
+
let emittedTextBuffer = '';
|
|
71
|
+
let rawAcpShapeDiagnosticCount = 0;
|
|
72
|
+
let artifactSuppressionDiagnosticCount = 0;
|
|
73
|
+
let amrStderrRetryTail = '';
|
|
74
|
+
let finished = false;
|
|
75
|
+
let fatal = false;
|
|
76
|
+
let aborted = false;
|
|
77
|
+
let stageTimer = null;
|
|
78
|
+
let dsmlArtifactSuppressor = null;
|
|
79
|
+
let dsmlArtifactSuppressorLastSuppressedChars = 0;
|
|
80
|
+
let dsmlArtifactSuppressorToolCallId = null;
|
|
81
|
+
let dsmlArtifactSuppressorArmedAfterText = false;
|
|
82
|
+
let dsmlArtifactSuppressorSawIncrementalProse = false;
|
|
83
|
+
const pendingPermissionRequestIds = new Set();
|
|
84
|
+
const toolCallTextSuppressor = createToolCallTextSuppressor();
|
|
85
|
+
let toolCallTextSuppressorLastSuppressedChars = 0;
|
|
86
|
+
const artifactTextSuppressionSummary = {
|
|
87
|
+
suppressedChars: 0,
|
|
88
|
+
suppressedChunks: 0,
|
|
89
|
+
openedBlocks: 0,
|
|
90
|
+
closedBlocks: 0,
|
|
91
|
+
};
|
|
92
|
+
const toolCallTextSuppressionSummary = {
|
|
93
|
+
suppressedChars: 0,
|
|
94
|
+
suppressedChunks: 0,
|
|
95
|
+
openedBlocks: 0,
|
|
96
|
+
closedBlocks: 0,
|
|
97
|
+
};
|
|
98
|
+
const acpArtifactWriteToolCallIds = new Set();
|
|
99
|
+
// Per artifact-write tool call, accumulate the best concrete file path seen
|
|
100
|
+
// across its frames and whether we have already mirrored it into canonical
|
|
101
|
+
// tool_use/tool_result events. Emission is deferred to the terminal frame so
|
|
102
|
+
// a `locations`/`rawInput` path that ACP only sends on a later update is used
|
|
103
|
+
// for classification, instead of locking in a first-frame guess.
|
|
104
|
+
const acpArtifactRunEventState = new Map();
|
|
105
|
+
const stageWatchdogDisabled = stageTimeoutMs <= 0;
|
|
106
|
+
const resetStageTimer = (label) => {
|
|
107
|
+
if (stageTimer)
|
|
108
|
+
clearTimeout(stageTimer);
|
|
109
|
+
// `stageTimeoutMs <= 0` disables the watchdog. Mirrors a typical outer
|
|
110
|
+
// run-level inactivity watchdog's escape hatch. Without this, a caller
|
|
111
|
+
// passing a zero stage timeout would schedule a 0ms timeout that fires on
|
|
112
|
+
// the next tick and kills the session immediately.
|
|
113
|
+
if (stageWatchdogDisabled)
|
|
114
|
+
return;
|
|
115
|
+
stageTimer = setTimeout(() => {
|
|
116
|
+
fail(`ACP ${label} timed out after ${stageTimeoutMs}ms`);
|
|
117
|
+
}, stageTimeoutMs);
|
|
118
|
+
};
|
|
119
|
+
const clearStageTimer = () => {
|
|
120
|
+
if (stageTimer)
|
|
121
|
+
clearTimeout(stageTimer);
|
|
122
|
+
stageTimer = null;
|
|
123
|
+
};
|
|
124
|
+
const amrModelUnavailablePayload = (message) => ({
|
|
125
|
+
message,
|
|
126
|
+
error: {
|
|
127
|
+
code: 'AMR_MODEL_UNAVAILABLE',
|
|
128
|
+
message,
|
|
129
|
+
retryable: false,
|
|
130
|
+
details: { kind: 'amr_model', action: 'choose_model' },
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
const isModelUnavailableError = (message) => {
|
|
134
|
+
const value = message.toLowerCase();
|
|
135
|
+
return (value.includes('model not found') ||
|
|
136
|
+
value.includes('providermodelnotfounderror') ||
|
|
137
|
+
value.includes('unknown model') ||
|
|
138
|
+
value.includes('invalid model'));
|
|
139
|
+
};
|
|
140
|
+
// Both call sites below (the session/update retry-status promotion and the
|
|
141
|
+
// stderr promotion) are already nested inside a scope gated by `finished`
|
|
142
|
+
// at their own entry point (the parser callback's own `if (aborted ||
|
|
143
|
+
// finished) return;`, and the stderr handler's `if (!modelUnavailableErrorCode
|
|
144
|
+
// || finished) return;`), and neither is re-entrant within a single
|
|
145
|
+
// synchronous call — so `failWithPayload` can never actually be invoked a
|
|
146
|
+
// second time once `finished` is true. The origin file carried its own
|
|
147
|
+
// `if (finished) return;` re-entry guard here anyway; removed per this
|
|
148
|
+
// package's coverage-driven dead-branch discipline (verified unreachable
|
|
149
|
+
// by tracing every call site — see source-map.md), not suppressed.
|
|
150
|
+
const failWithPayload = (payload) => {
|
|
151
|
+
finished = true;
|
|
152
|
+
fatal = true;
|
|
153
|
+
clearStageTimer();
|
|
154
|
+
send('error', payload);
|
|
155
|
+
if (!child.killed)
|
|
156
|
+
child.kill('SIGTERM');
|
|
157
|
+
};
|
|
158
|
+
const fail = (message, options = {}) => {
|
|
159
|
+
if (finished)
|
|
160
|
+
return;
|
|
161
|
+
finished = true;
|
|
162
|
+
fatal = true;
|
|
163
|
+
clearStageTimer();
|
|
164
|
+
const useModelUnavailable = modelUnavailableErrorCode &&
|
|
165
|
+
(options.forceModelUnavailable || isModelUnavailableError(message));
|
|
166
|
+
send('error', useModelUnavailable
|
|
167
|
+
? amrModelUnavailablePayload(message)
|
|
168
|
+
: options.details === undefined && options.retryable === undefined
|
|
169
|
+
? { message }
|
|
170
|
+
: {
|
|
171
|
+
message,
|
|
172
|
+
error: {
|
|
173
|
+
code: 'AGENT_EXECUTION_FAILED',
|
|
174
|
+
message,
|
|
175
|
+
retryable: options.retryable ?? false,
|
|
176
|
+
// Every real call site that reaches this branch (the
|
|
177
|
+
// RPC-error path and the no-visible-output path) always
|
|
178
|
+
// supplies `details` whenever it supplies `retryable`, and
|
|
179
|
+
// `retryable` is itself derived from the same `details`
|
|
180
|
+
// source at the RPC-error call site — so `options.details`
|
|
181
|
+
// is never actually undefined here. Spreading it
|
|
182
|
+
// unconditionally (rather than conditionally omitting the
|
|
183
|
+
// key) is behaviorally identical for every real caller;
|
|
184
|
+
// simplified per this package's coverage-driven
|
|
185
|
+
// dead-branch discipline. See source-map.md.
|
|
186
|
+
details: options.details,
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
if (!child.killed)
|
|
190
|
+
child.kill('SIGTERM');
|
|
191
|
+
};
|
|
192
|
+
const writeRpc = (id, method, params, timeoutLabel) => {
|
|
193
|
+
resetStageTimer(timeoutLabel);
|
|
194
|
+
try {
|
|
195
|
+
sendRpc(stdin, id, method, params);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
fail(`stdin write failed: ${errorMessage(err)}`);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const emitAcpRawShapeDiagnostic = (update) => {
|
|
202
|
+
if (!modelUnavailableErrorCode)
|
|
203
|
+
return;
|
|
204
|
+
if (rawAcpShapeDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
205
|
+
return;
|
|
206
|
+
rawAcpShapeDiagnosticCount += 1;
|
|
207
|
+
send('agent', {
|
|
208
|
+
type: 'diagnostic',
|
|
209
|
+
name: 'acp_raw_event_shape',
|
|
210
|
+
source: 'acp-json-rpc',
|
|
211
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
212
|
+
shape: acpRawEventShape(update),
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
// All three call sites guard against an empty `delta` before calling this
|
|
216
|
+
// (the finishCleanPrompt flush, the `if (outputDelta)` branch, and the
|
|
217
|
+
// plain-else branch which is only reached after an earlier `if
|
|
218
|
+
// (!toolCallStrippedDelta) return;` already ruled out emptiness) — so the
|
|
219
|
+
// origin file's own `if (!delta) return;` guard here can never actually
|
|
220
|
+
// fire. Removed per this package's coverage-driven dead-branch
|
|
221
|
+
// discipline (verified unreachable by tracing every call site — see
|
|
222
|
+
// source-map.md), not suppressed.
|
|
223
|
+
const emitVisibleTextDelta = (delta) => {
|
|
224
|
+
emittedVisibleTextChunk = true;
|
|
225
|
+
if (!emittedFirstTokenStatus) {
|
|
226
|
+
emittedFirstTokenStatus = true;
|
|
227
|
+
send('agent', {
|
|
228
|
+
type: 'status',
|
|
229
|
+
label: 'streaming',
|
|
230
|
+
ttftMs: Date.now() - runStartedAt,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
send('agent', { type: 'text_delta', delta });
|
|
234
|
+
};
|
|
235
|
+
const noteArtifactTextSuppression = (reason) => {
|
|
236
|
+
if (!dsmlArtifactSuppressor)
|
|
237
|
+
return;
|
|
238
|
+
const stats = dsmlArtifactSuppressor.stats();
|
|
239
|
+
const suppressedDelta = stats.suppressedChars - dsmlArtifactSuppressorLastSuppressedChars;
|
|
240
|
+
if (suppressedDelta <= 0)
|
|
241
|
+
return;
|
|
242
|
+
dsmlArtifactSuppressorLastSuppressedChars = stats.suppressedChars;
|
|
243
|
+
artifactTextSuppressionSummary.suppressedChars += suppressedDelta;
|
|
244
|
+
artifactTextSuppressionSummary.suppressedChunks = stats.suppressedChunks;
|
|
245
|
+
artifactTextSuppressionSummary.openedBlocks = stats.openedBlocks;
|
|
246
|
+
artifactTextSuppressionSummary.closedBlocks = stats.closedBlocks;
|
|
247
|
+
if (artifactSuppressionDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
248
|
+
return;
|
|
249
|
+
artifactSuppressionDiagnosticCount += 1;
|
|
250
|
+
send('agent', {
|
|
251
|
+
type: 'diagnostic',
|
|
252
|
+
name: 'acp_artifact_text_suppression',
|
|
253
|
+
source: 'acp-json-rpc',
|
|
254
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
255
|
+
reason,
|
|
256
|
+
suppressedChars: artifactTextSuppressionSummary.suppressedChars,
|
|
257
|
+
suppressedChunks: artifactTextSuppressionSummary.suppressedChunks,
|
|
258
|
+
openedBlocks: artifactTextSuppressionSummary.openedBlocks,
|
|
259
|
+
closedBlocks: artifactTextSuppressionSummary.closedBlocks,
|
|
260
|
+
pendingCandidateChars: stats.pendingCandidateChars,
|
|
261
|
+
suppressing: stats.suppressing,
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
const emitArtifactTextSuppressionSummary = () => {
|
|
265
|
+
if (artifactTextSuppressionSummary.suppressedChars <= 0)
|
|
266
|
+
return;
|
|
267
|
+
if (executionProfile === 'filesystem') {
|
|
268
|
+
send('agent', {
|
|
269
|
+
type: 'diagnostic',
|
|
270
|
+
name: 'unexpected_text_artifact_in_filesystem_run',
|
|
271
|
+
source: 'acp-json-rpc',
|
|
272
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
273
|
+
suppressedChars: artifactTextSuppressionSummary.suppressedChars,
|
|
274
|
+
suppressedChunks: artifactTextSuppressionSummary.suppressedChunks,
|
|
275
|
+
openedBlocks: artifactTextSuppressionSummary.openedBlocks,
|
|
276
|
+
closedBlocks: artifactTextSuppressionSummary.closedBlocks,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
send('agent', {
|
|
280
|
+
type: 'diagnostic',
|
|
281
|
+
name: 'acp_artifact_text_suppression_summary',
|
|
282
|
+
source: 'acp-json-rpc',
|
|
283
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
284
|
+
...artifactTextSuppressionSummary,
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
const noteToolCallTextSuppression = (reason) => {
|
|
288
|
+
const stats = toolCallTextSuppressor.stats();
|
|
289
|
+
const suppressedDelta = stats.suppressedChars - toolCallTextSuppressorLastSuppressedChars;
|
|
290
|
+
if (suppressedDelta <= 0)
|
|
291
|
+
return;
|
|
292
|
+
toolCallTextSuppressorLastSuppressedChars = stats.suppressedChars;
|
|
293
|
+
toolCallTextSuppressionSummary.suppressedChars += suppressedDelta;
|
|
294
|
+
toolCallTextSuppressionSummary.suppressedChunks = stats.suppressedChunks;
|
|
295
|
+
toolCallTextSuppressionSummary.openedBlocks = stats.openedBlocks;
|
|
296
|
+
toolCallTextSuppressionSummary.closedBlocks = stats.closedBlocks;
|
|
297
|
+
if (artifactSuppressionDiagnosticCount >= ACP_RAW_EVENT_SHAPE_DIAGNOSTIC_LIMIT)
|
|
298
|
+
return;
|
|
299
|
+
artifactSuppressionDiagnosticCount += 1;
|
|
300
|
+
send('agent', {
|
|
301
|
+
type: 'diagnostic',
|
|
302
|
+
name: 'acp_tool_call_text_suppression',
|
|
303
|
+
source: 'acp-json-rpc',
|
|
304
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
305
|
+
reason,
|
|
306
|
+
suppressedChars: toolCallTextSuppressionSummary.suppressedChars,
|
|
307
|
+
suppressedChunks: toolCallTextSuppressionSummary.suppressedChunks,
|
|
308
|
+
openedBlocks: toolCallTextSuppressionSummary.openedBlocks,
|
|
309
|
+
closedBlocks: toolCallTextSuppressionSummary.closedBlocks,
|
|
310
|
+
pendingCandidateChars: stats.pendingCandidateChars,
|
|
311
|
+
suppressing: stats.suppressing,
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
const emitToolCallTextSuppressionSummary = () => {
|
|
315
|
+
if (toolCallTextSuppressionSummary.suppressedChars <= 0)
|
|
316
|
+
return;
|
|
317
|
+
send('agent', {
|
|
318
|
+
type: 'diagnostic',
|
|
319
|
+
name: 'acp_tool_call_text_suppression_summary',
|
|
320
|
+
source: 'acp-json-rpc',
|
|
321
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
322
|
+
...toolCallTextSuppressionSummary,
|
|
323
|
+
});
|
|
324
|
+
};
|
|
325
|
+
const sendPrompt = () => {
|
|
326
|
+
promptRequestId = nextId;
|
|
327
|
+
expectedId = promptRequestId;
|
|
328
|
+
writeRpc(promptRequestId, 'session/prompt', {
|
|
329
|
+
sessionId,
|
|
330
|
+
prompt: buildPromptBlocks(prompt, imagePaths),
|
|
331
|
+
}, 'session/prompt');
|
|
332
|
+
send('agent', {
|
|
333
|
+
type: 'status',
|
|
334
|
+
label: 'waiting_for_first_output',
|
|
335
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
336
|
+
});
|
|
337
|
+
nextId += 1;
|
|
338
|
+
};
|
|
339
|
+
// `finishCleanPrompt` has exactly one call site, inside the parser
|
|
340
|
+
// callback's promptRequestId-match branch, which is itself already gated
|
|
341
|
+
// by that same callback's own `if (aborted || finished) return;` at entry
|
|
342
|
+
// — so this function can never actually be invoked once `finished` is
|
|
343
|
+
// true. The origin file carried its own re-entry guard here anyway;
|
|
344
|
+
// removed per this package's coverage-driven dead-branch discipline. See
|
|
345
|
+
// source-map.md.
|
|
346
|
+
const finishCleanPrompt = (usageSource) => {
|
|
347
|
+
const flushedToolText = toolCallTextSuppressor.flush();
|
|
348
|
+
noteToolCallTextSuppression('tool_call_xml_flush');
|
|
349
|
+
const flushedText = flushedToolText ? (dsmlArtifactSuppressor?.strip(flushedToolText) ?? flushedToolText) : '';
|
|
350
|
+
if (flushedText) {
|
|
351
|
+
emitVisibleTextDelta(flushedText);
|
|
352
|
+
}
|
|
353
|
+
noteArtifactTextSuppression('artifact_flush');
|
|
354
|
+
emitToolCallTextSuppressionSummary();
|
|
355
|
+
emitArtifactTextSuppressionSummary();
|
|
356
|
+
const usage = formatUsage(usageSource);
|
|
357
|
+
if (usage) {
|
|
358
|
+
send('agent', {
|
|
359
|
+
type: 'usage',
|
|
360
|
+
usage,
|
|
361
|
+
durationMs: Date.now() - runStartedAt,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
finished = true;
|
|
365
|
+
clearStageTimer();
|
|
366
|
+
stdin.end();
|
|
367
|
+
// Some ACP agents keep the child process alive after stdin closes,
|
|
368
|
+
// waiting for another prompt. Each run owns one process per turn, so
|
|
369
|
+
// close it once this prompt is cleanly complete.
|
|
370
|
+
const cleanExitTimer = setTimeout(() => {
|
|
371
|
+
if (!child.killed)
|
|
372
|
+
child.kill('SIGTERM');
|
|
373
|
+
}, 500);
|
|
374
|
+
child.once('close', () => clearTimeout(cleanExitTimer));
|
|
375
|
+
};
|
|
376
|
+
const replyPermission = (raw) => {
|
|
377
|
+
const params = asObject(raw.params);
|
|
378
|
+
if (!params || !isJsonRpcId(raw.id)) {
|
|
379
|
+
fail(`unhandled ACP permission request: ${JSON.stringify(raw)}`);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const requestId = raw.id;
|
|
383
|
+
const optionRecords = Array.isArray(params.options)
|
|
384
|
+
? params.options.filter((option) => Boolean(asObject(option)) && typeof asObject(option)?.optionId === 'string')
|
|
385
|
+
: [];
|
|
386
|
+
const request = {
|
|
387
|
+
requestId,
|
|
388
|
+
sessionId: typeof params.sessionId === 'string' ? params.sessionId : null,
|
|
389
|
+
toolCall: asObject(params.toolCall) ?? null,
|
|
390
|
+
options: optionRecords,
|
|
391
|
+
rawParams: params,
|
|
392
|
+
};
|
|
393
|
+
const respond = (decision) => {
|
|
394
|
+
pendingPermissionRequestIds.delete(requestId);
|
|
395
|
+
if (aborted || finished)
|
|
396
|
+
return;
|
|
397
|
+
if (decision.outcome === 'selected' &&
|
|
398
|
+
!optionRecords.some((option) => option.optionId === decision.optionId)) {
|
|
399
|
+
fail(`ACP permission handler selected an unavailable option: ${decision.optionId}`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
resetStageTimer('session/request_permission');
|
|
403
|
+
try {
|
|
404
|
+
sendRpcResult(stdin, requestId, {
|
|
405
|
+
outcome: decision.outcome === 'selected'
|
|
406
|
+
? { outcome: 'selected', optionId: decision.optionId }
|
|
407
|
+
: { outcome: 'cancelled' },
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
fail(`stdin write failed: ${errorMessage(err)}`);
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const rejectHandlerFailure = (err) => {
|
|
415
|
+
pendingPermissionRequestIds.delete(requestId);
|
|
416
|
+
fail(`ACP permission handler failed: ${errorMessage(err)}`);
|
|
417
|
+
};
|
|
418
|
+
pendingPermissionRequestIds.add(requestId);
|
|
419
|
+
if (!onPermissionRequest) {
|
|
420
|
+
pendingPermissionRequestIds.delete(requestId);
|
|
421
|
+
fail('ACP permission request received without an injected permission handler');
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
Promise.resolve(onPermissionRequest(request)).then(respond, rejectHandlerFailure);
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
rejectHandlerFailure(err);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const recoverFromModelSelectionError = () => {
|
|
432
|
+
setModelRequestId = null;
|
|
433
|
+
activeModel = activeModel || 'default';
|
|
434
|
+
send('agent', { type: 'status', label: 'model', model: activeModel });
|
|
435
|
+
sendPrompt();
|
|
436
|
+
};
|
|
437
|
+
const parser = createJsonLineStream((raw, rawLine) => {
|
|
438
|
+
if (aborted || finished)
|
|
439
|
+
return;
|
|
440
|
+
resetStageTimer('response');
|
|
441
|
+
const obj = asObject(raw);
|
|
442
|
+
if (!obj)
|
|
443
|
+
return;
|
|
444
|
+
// First well-formed ACP JSON-RPC message = CLI ready. Caller dedupes, so
|
|
445
|
+
// re-notifying on later messages is harmless.
|
|
446
|
+
onCliReady?.();
|
|
447
|
+
const error = asObject(obj.error);
|
|
448
|
+
const params = asObject(obj.params);
|
|
449
|
+
const result = asObject(obj.result);
|
|
450
|
+
const rpcErr = rpcErrorMessage(obj);
|
|
451
|
+
if (rpcErr) {
|
|
452
|
+
// A late-arriving error from the agent after the session has already
|
|
453
|
+
// finished (pipe-broken, cleanup race conditions, etc.) would be safe
|
|
454
|
+
// to ignore here — but this whole callback is already gated by `if
|
|
455
|
+
// (aborted || finished) return;` at its own top (line 499), and
|
|
456
|
+
// nothing between there and here can flip `finished` to true
|
|
457
|
+
// mid-invocation, so a second, inner check of the same condition can
|
|
458
|
+
// never actually fire. Removed per this package's coverage-driven
|
|
459
|
+
// dead-branch discipline (verified unreachable by tracing the
|
|
460
|
+
// callback's own control flow — see source-map.md), not suppressed.
|
|
461
|
+
// JSON-RPC error handling:
|
|
462
|
+
// -32603 unexpected-id errors are cleanup noise. Expected-id model
|
|
463
|
+
// selection failures are recoverable; all other RPC errors are real
|
|
464
|
+
// protocol failures for initialize/session/new/session/prompt.
|
|
465
|
+
if (obj.id === setModelRequestId &&
|
|
466
|
+
modelSelectionErrorIsRecoverable(error?.code) &&
|
|
467
|
+
promptRequestId === null) {
|
|
468
|
+
recoverFromModelSelectionError();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (error?.code === -32603 && obj.id !== expectedId) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const details = rpcErrorData(obj);
|
|
475
|
+
const promotedPayload = promotedOpenCodeSessionErrorPayload(details, rpcErr);
|
|
476
|
+
if (promotedPayload) {
|
|
477
|
+
failWithPayload(promotedPayload);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const retryable = rpcErrorRetryable(details);
|
|
481
|
+
fail(rpcErr, {
|
|
482
|
+
details,
|
|
483
|
+
...(retryable === undefined ? {} : { retryable }),
|
|
484
|
+
});
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (obj.method === 'session/request_permission') {
|
|
488
|
+
replyPermission(obj);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const update = asObject(params?.update);
|
|
492
|
+
if (obj.method === 'session/update' && update) {
|
|
493
|
+
if (modelUnavailableErrorCode) {
|
|
494
|
+
const promotedPayload = promotedAmrRetryStatusPayload(update, accountFailureClassifier);
|
|
495
|
+
if (promotedPayload) {
|
|
496
|
+
failWithPayload(promotedPayload);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (update.sessionUpdate !== 'agent_message_chunk' && update.sessionUpdate !== 'agent_thought_chunk') {
|
|
501
|
+
send('agent', {
|
|
502
|
+
type: 'status',
|
|
503
|
+
label: String(update.sessionUpdate || 'session_update'),
|
|
504
|
+
elapsedMs: Date.now() - runStartedAt,
|
|
505
|
+
});
|
|
506
|
+
emitAcpRawShapeDiagnostic(update);
|
|
507
|
+
}
|
|
508
|
+
if (update.sessionUpdate === 'agent_thought_chunk') {
|
|
509
|
+
emitAcpRawShapeDiagnostic(update);
|
|
510
|
+
const text = extractAcpUpdateText(update);
|
|
511
|
+
if (text) {
|
|
512
|
+
if (!emittedThinkingStart) {
|
|
513
|
+
emittedThinkingStart = true;
|
|
514
|
+
send('agent', { type: 'thinking_start' });
|
|
515
|
+
}
|
|
516
|
+
send('agent', { type: 'thinking_delta', delta: text });
|
|
517
|
+
}
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (update.sessionUpdate === 'agent_message_chunk') {
|
|
521
|
+
emitAcpRawShapeDiagnostic(update);
|
|
522
|
+
const text = extractAcpUpdateText(update);
|
|
523
|
+
if (text) {
|
|
524
|
+
const isCumulativeSnapshot = text.startsWith(emittedTextBuffer);
|
|
525
|
+
const delta = isCumulativeSnapshot
|
|
526
|
+
? text.slice(emittedTextBuffer.length)
|
|
527
|
+
: text;
|
|
528
|
+
if (delta.length > 0) {
|
|
529
|
+
emittedTextChunk = true;
|
|
530
|
+
emittedTextBuffer += delta;
|
|
531
|
+
const wasSuppressingToolCall = toolCallTextSuppressor.isSuppressing();
|
|
532
|
+
const toolCallStrippedDelta = toolCallTextSuppressor.strip(delta);
|
|
533
|
+
noteToolCallTextSuppression(wasSuppressingToolCall || toolCallStrippedDelta !== delta
|
|
534
|
+
? 'tool_call_xml'
|
|
535
|
+
: 'tool_call_candidate');
|
|
536
|
+
if (!toolCallStrippedDelta) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (dsmlArtifactSuppressor) {
|
|
540
|
+
const wasSuppressingArtifact = dsmlArtifactSuppressor.isSuppressing();
|
|
541
|
+
const hadPendingArtifactCandidate = dsmlArtifactSuppressor.hasPendingCandidate();
|
|
542
|
+
const strippedDelta = dsmlArtifactSuppressor.strip(toolCallStrippedDelta);
|
|
543
|
+
noteArtifactTextSuppression(wasSuppressingArtifact || strippedDelta !== toolCallStrippedDelta
|
|
544
|
+
? 'artifact_echo'
|
|
545
|
+
: 'artifact_candidate');
|
|
546
|
+
const hasOpenArtifactCandidate = dsmlArtifactSuppressor.isSuppressing() || dsmlArtifactSuppressor.hasPendingCandidate();
|
|
547
|
+
const consumedArtifactText = wasSuppressingArtifact || strippedDelta !== delta;
|
|
548
|
+
const shouldPreserveIncrementalProse = !isCumulativeSnapshot &&
|
|
549
|
+
!wasSuppressingArtifact &&
|
|
550
|
+
!hadPendingArtifactCandidate &&
|
|
551
|
+
!hasOpenArtifactCandidate &&
|
|
552
|
+
(strippedDelta === toolCallStrippedDelta ||
|
|
553
|
+
(!dsmlArtifactSuppressorArmedAfterText &&
|
|
554
|
+
dsmlArtifactSuppressorSawIncrementalProse &&
|
|
555
|
+
!ACP_ARTIFACT_ECHO_START_RE.test(toolCallStrippedDelta)));
|
|
556
|
+
const outputDelta = shouldPreserveIncrementalProse ? toolCallStrippedDelta : strippedDelta;
|
|
557
|
+
if (outputDelta) {
|
|
558
|
+
emitVisibleTextDelta(outputDelta);
|
|
559
|
+
}
|
|
560
|
+
if (strippedDelta === toolCallStrippedDelta &&
|
|
561
|
+
!wasSuppressingArtifact &&
|
|
562
|
+
!hadPendingArtifactCandidate &&
|
|
563
|
+
!hasOpenArtifactCandidate) {
|
|
564
|
+
dsmlArtifactSuppressorSawIncrementalProse = true;
|
|
565
|
+
}
|
|
566
|
+
if (consumedArtifactText && !hasOpenArtifactCandidate) {
|
|
567
|
+
dsmlArtifactSuppressor = null;
|
|
568
|
+
dsmlArtifactSuppressorToolCallId = null;
|
|
569
|
+
dsmlArtifactSuppressorArmedAfterText = false;
|
|
570
|
+
dsmlArtifactSuppressorSawIncrementalProse = false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
emitVisibleTextDelta(toolCallStrippedDelta);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (update.sessionUpdate === 'tool_call' ||
|
|
581
|
+
update.sessionUpdate === 'tool_call_update') {
|
|
582
|
+
// The turn did real work (a tool call / file edit), which is valid output even
|
|
583
|
+
// when the model emits no closing assistant text. Track it so the prompt-complete
|
|
584
|
+
// handler does not misreport such a turn as "no output / model unavailable".
|
|
585
|
+
emittedToolCall = true;
|
|
586
|
+
const toolCallId = acpToolCallId(update);
|
|
587
|
+
if (toolCallId && isAcpArtifactWriteLabel(update)) {
|
|
588
|
+
acpArtifactWriteToolCallIds.add(toolCallId);
|
|
589
|
+
}
|
|
590
|
+
// Mirror artifact-write tool calls into the daemon's canonical
|
|
591
|
+
// tool_use/tool_result event shape so a run-artifacts scanner can see
|
|
592
|
+
// ACP file writes. Without this, an ACP agent that emits only
|
|
593
|
+
// text/status/thinking events (never a tool_use/tool_result pair)
|
|
594
|
+
// would report zero artifacts even when the run wrote artifacts.
|
|
595
|
+
//
|
|
596
|
+
// This path only feeds the NO-PROJECT fallback (project runs use the
|
|
597
|
+
// filesystem snapshot). Two correctness rules, both learned the hard
|
|
598
|
+
// way in review:
|
|
599
|
+
// 1. Defer emission to the TERMINAL frame and accumulate the best
|
|
600
|
+
// concrete path across frames — ACP often sends `locations` only
|
|
601
|
+
// on the completing update, and emitting on the first frame would
|
|
602
|
+
// lock in a wrong/empty guess that a later path can't correct.
|
|
603
|
+
// 2. Never fabricate an artifact extension. `isArtifactPath` is what
|
|
604
|
+
// decides whether a write counts; feeding it a real path lets it
|
|
605
|
+
// correctly EXCLUDE non-artifact edits (`config.json`, `README.md`)
|
|
606
|
+
// and INCLUDE real artifacts. A write that never carries a concrete
|
|
607
|
+
// path stays keyed on its (extension-less) toolCallId, so it is
|
|
608
|
+
// simply not counted rather than inflating the metric with a
|
|
609
|
+
// synthetic `.html` — under-counting a truly opaque write is
|
|
610
|
+
// acceptable; a false-positive artifact is not.
|
|
611
|
+
if (toolCallId) {
|
|
612
|
+
const isWriteCall = isAcpArtifactWriteLabel(update) || acpArtifactWriteToolCallIds.has(toolCallId);
|
|
613
|
+
if (isWriteCall) {
|
|
614
|
+
let st = acpArtifactRunEventState.get(toolCallId);
|
|
615
|
+
if (!st) {
|
|
616
|
+
st = { path: null, emitted: false };
|
|
617
|
+
acpArtifactRunEventState.set(toolCallId, st);
|
|
618
|
+
}
|
|
619
|
+
if (!st.path)
|
|
620
|
+
st.path = acpArtifactWritePath(update);
|
|
621
|
+
const failed = isAcpTerminalFailureStatus(update);
|
|
622
|
+
if (!st.emitted && (failed || isAcpCompletedStatus(update))) {
|
|
623
|
+
st.emitted = true;
|
|
624
|
+
send('agent', {
|
|
625
|
+
type: 'tool_use',
|
|
626
|
+
id: toolCallId,
|
|
627
|
+
name: 'Write',
|
|
628
|
+
input: { file_path: st.path ?? toolCallId },
|
|
629
|
+
});
|
|
630
|
+
send('agent', { type: 'tool_result', toolUseId: toolCallId, isError: failed });
|
|
631
|
+
emittedConcreteToolEvent = true;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (isAcpArtifactWriteUpdate(update, acpArtifactWriteToolCallIds)) {
|
|
636
|
+
dsmlArtifactSuppressor = createDsmlArtifactTextSuppressor();
|
|
637
|
+
dsmlArtifactSuppressorLastSuppressedChars = 0;
|
|
638
|
+
dsmlArtifactSuppressorToolCallId = toolCallId;
|
|
639
|
+
dsmlArtifactSuppressorArmedAfterText = emittedTextBuffer.length > 0;
|
|
640
|
+
dsmlArtifactSuppressorSawIncrementalProse = false;
|
|
641
|
+
if (toolCallId)
|
|
642
|
+
acpArtifactWriteToolCallIds.delete(toolCallId);
|
|
643
|
+
}
|
|
644
|
+
else if (toolCallId && isAcpTerminalFailureStatus(update)) {
|
|
645
|
+
const ownsPendingWriteSuppression = toolCallId === dsmlArtifactSuppressorToolCallId;
|
|
646
|
+
const ownsPendingWriteCall = acpArtifactWriteToolCallIds.has(toolCallId);
|
|
647
|
+
acpArtifactWriteToolCallIds.delete(toolCallId);
|
|
648
|
+
if (ownsPendingWriteSuppression || ownsPendingWriteCall) {
|
|
649
|
+
dsmlArtifactSuppressor = null;
|
|
650
|
+
dsmlArtifactSuppressorToolCallId = null;
|
|
651
|
+
dsmlArtifactSuppressorArmedAfterText = false;
|
|
652
|
+
dsmlArtifactSuppressorSawIncrementalProse = false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (obj.id !== expectedId || !result) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (expectedId === 1) {
|
|
663
|
+
expectedId = nextId;
|
|
664
|
+
if (resumeSessionId) {
|
|
665
|
+
// Resume the prior upstream session instead of creating a fresh one.
|
|
666
|
+
writeRpc(nextId, 'session/load', { sessionId: resumeSessionId, cwd: effectiveCwd }, 'session/load');
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
writeRpc(nextId, 'session/new', buildAcpSessionNewParams(effectiveCwd, mcpServers ? { mcpServers, envFormat } : { envFormat }), 'session/new');
|
|
670
|
+
}
|
|
671
|
+
nextId += 1;
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (expectedId === 2) {
|
|
675
|
+
sessionId = typeof result.sessionId === 'string' ? result.sessionId : null;
|
|
676
|
+
// The durable handle for resuming this session on the next turn.
|
|
677
|
+
durableSessionId =
|
|
678
|
+
typeof result.openCodeSessionId === 'string' ? result.openCodeSessionId : null;
|
|
679
|
+
// session/new acknowledged with a session id = handshake done.
|
|
680
|
+
if (sessionId)
|
|
681
|
+
onSessionInit?.();
|
|
682
|
+
const modelConfig = findModelConfigOption(result.configOptions);
|
|
683
|
+
modelConfigId = modelConfig?.configId ?? null;
|
|
684
|
+
activeModel = currentModelFromSessionResult(result);
|
|
685
|
+
if (sessionId && activeModel) {
|
|
686
|
+
send('agent', { type: 'status', label: 'model', model: activeModel });
|
|
687
|
+
}
|
|
688
|
+
if (sessionId && model && model !== 'default') {
|
|
689
|
+
setModelRequestId = nextId;
|
|
690
|
+
expectedId = nextId;
|
|
691
|
+
const setModelMethod = modelConfigId ? 'session/set_config_option' : 'session/set_model';
|
|
692
|
+
const setModelParams = modelConfigId
|
|
693
|
+
? { sessionId, configId: modelConfigId, value: model }
|
|
694
|
+
: { sessionId, modelId: model };
|
|
695
|
+
writeRpc(nextId, setModelMethod, setModelParams, setModelMethod);
|
|
696
|
+
nextId += 1;
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (!sessionId) {
|
|
700
|
+
fail(`invalid session/new response: ${rawLine}`);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
sendPrompt();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (promptRequestId !== null && obj.id === promptRequestId) {
|
|
707
|
+
const usage = formatUsage(result.usage);
|
|
708
|
+
if (!emittedVisibleTextChunk && !emittedConcreteToolEvent && modelUnavailableErrorCode) {
|
|
709
|
+
const outputTokens = usage?.output_tokens;
|
|
710
|
+
const hadCompletionTokens = typeof outputTokens === 'number' && outputTokens > 0;
|
|
711
|
+
if (hadCompletionTokens || emittedToolCall || emittedTextChunk) {
|
|
712
|
+
fail('ACP session completed after reporting model activity, but did not produce visible assistant text, concrete tool results, or artifacts.', {
|
|
713
|
+
retryable: true,
|
|
714
|
+
details: {
|
|
715
|
+
kind: 'acp_no_visible_output',
|
|
716
|
+
output_tokens: outputTokens,
|
|
717
|
+
raw_tool_update_seen: emittedToolCall,
|
|
718
|
+
text_chunk_seen: emittedTextChunk,
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
fail('ACP session completed without producing any assistant text. Refresh the AMR model list, choose a supported model, and retry this run.', { forceModelUnavailable: true });
|
|
724
|
+
}
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
finishCleanPrompt(result.usage);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (sessionId && model && model !== 'default' && obj.id === expectedId) {
|
|
731
|
+
activeModel = currentModelFromSessionResult(result) ?? model;
|
|
732
|
+
send('agent', { type: 'status', label: 'model', model: activeModel });
|
|
733
|
+
sendPrompt();
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
stdout.on('data', (chunk) => parser.feed(chunk));
|
|
737
|
+
child.stderr?.setEncoding('utf8');
|
|
738
|
+
child.stderr?.on('data', (chunk) => {
|
|
739
|
+
if (!modelUnavailableErrorCode || finished)
|
|
740
|
+
return;
|
|
741
|
+
amrStderrRetryTail = `${amrStderrRetryTail}${String(chunk)}`.slice(-AMR_STDERR_RETRY_TAIL_LIMIT);
|
|
742
|
+
const promotedPayload = promotedAmrStderrPayload(amrStderrRetryTail, accountFailureClassifier);
|
|
743
|
+
if (promotedPayload)
|
|
744
|
+
failWithPayload(promotedPayload);
|
|
745
|
+
});
|
|
746
|
+
child.on('close', (code, signal) => {
|
|
747
|
+
clearStageTimer();
|
|
748
|
+
parser.flush();
|
|
749
|
+
if (!finished && !aborted && !fatal) {
|
|
750
|
+
fail(`ACP session exited before completion (code=${code ?? 'null'}, signal=${signal ?? 'none'})`);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
child.on('error', (err) => fail(err.message));
|
|
754
|
+
stdin.on('error', (err) => fail(`stdin error: ${err.message}`));
|
|
755
|
+
writeRpc(1, 'initialize', {
|
|
756
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
757
|
+
clientCapabilities: { terminal: false },
|
|
758
|
+
clientInfo: { name: clientName, version: clientVersion },
|
|
759
|
+
}, 'initialize');
|
|
760
|
+
return {
|
|
761
|
+
/** Returns `true` when the session ended with a fatal protocol or transport error, allowing the caller to surface the failure. */
|
|
762
|
+
hasFatalError() {
|
|
763
|
+
return fatal;
|
|
764
|
+
},
|
|
765
|
+
// The durable upstream session handle to persist for resume, or null when
|
|
766
|
+
// none was reported (older agents, or a handshake that never established a
|
|
767
|
+
// session). Mirrors pi-rpc's getLastSessionPath().
|
|
768
|
+
/** Returns the durable upstream session id (e.g. a vendor bridge's own session id) to persist for next-turn resume, or `null` when the agent did not report one. */
|
|
769
|
+
getDurableSessionId() {
|
|
770
|
+
return durableSessionId;
|
|
771
|
+
},
|
|
772
|
+
/** Returns `true` when the prompt request resolved cleanly without a fatal error and without an abort, even if the child process later exited via SIGTERM. */
|
|
773
|
+
completedSuccessfully() {
|
|
774
|
+
// Returns true when the prompt request resolved without a fatal error
|
|
775
|
+
// and was not aborted. The chat consumer treats this as a successful
|
|
776
|
+
// run even if the child process subsequently exited via SIGTERM
|
|
777
|
+
// (which is expected for agents that don't shut down on stdin.end()).
|
|
778
|
+
return finished && !fatal && !aborted;
|
|
779
|
+
},
|
|
780
|
+
/**
|
|
781
|
+
* Aborts an in-progress ACP session. Sends `session/cancel` when a session
|
|
782
|
+
* id has already been established, then always closes stdin so the agent
|
|
783
|
+
* receives EOF and can tear down its own runtime (e.g. a vendor bridge's
|
|
784
|
+
* private backing server). Idempotent — subsequent calls are no-ops.
|
|
785
|
+
*/
|
|
786
|
+
abort() {
|
|
787
|
+
if (aborted || finished)
|
|
788
|
+
return;
|
|
789
|
+
aborted = true;
|
|
790
|
+
finished = true;
|
|
791
|
+
clearStageTimer();
|
|
792
|
+
// ACP requires pending permission requests to receive a terminal
|
|
793
|
+
// `cancelled` response when the prompt turn is cancelled. Do this
|
|
794
|
+
// before closing stdin, which may otherwise make the response
|
|
795
|
+
// impossible to deliver.
|
|
796
|
+
for (const requestId of pendingPermissionRequestIds) {
|
|
797
|
+
try {
|
|
798
|
+
sendRpcResult(stdin, requestId, { outcome: { outcome: 'cancelled' } });
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
// Closing stdin/process signalling below remains the fallback.
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
pendingPermissionRequestIds.clear();
|
|
805
|
+
if (!child.stdin || child.stdin.destroyed || child.stdin.writableEnded)
|
|
806
|
+
return;
|
|
807
|
+
// Only cancel an established session; before session/new resolves there
|
|
808
|
+
// is no sessionId to cancel, but we must still close stdin below.
|
|
809
|
+
if (sessionId) {
|
|
810
|
+
try {
|
|
811
|
+
sendRpc(child.stdin, nextId, 'session/cancel', { sessionId });
|
|
812
|
+
nextId += 1;
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
// The caller owns process-signal fallback if the ACP transport is gone.
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Always close stdin so the agent receives EOF and shuts down its own
|
|
819
|
+
// runtime — some ACP bridges tear down a private backing server on EOF —
|
|
820
|
+
// instead of lingering (and leaking that server) until the caller's
|
|
821
|
+
// SIGTERM fallback fires. This also covers aborts during ACP startup,
|
|
822
|
+
// before session/new returns. Mirrors the clean-completion path above.
|
|
823
|
+
try {
|
|
824
|
+
child.stdin.end();
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
// Best effort; the caller still owns the SIGTERM/SIGKILL fallback.
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
//# sourceMappingURL=session.js.map
|