@gajae-code/coding-agent 0.17.2 → 0.17.4
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 +194 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/github-release.d.ts +17 -0
- package/dist/types/cli/public-command-entry.d.ts +56 -0
- package/dist/types/cli/public-command-errors.d.ts +126 -0
- package/dist/types/cli/public-command-evidence.d.ts +99 -0
- package/dist/types/cli/public-command-help.d.ts +65 -0
- package/dist/types/cli/public-command-registry.d.ts +69 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/cli/skills-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +4 -50
- package/dist/types/commands/sdk.d.ts +20 -37
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/commands/skills.d.ts +9 -0
- package/dist/types/config/autorouting-tier-map.d.ts +18 -0
- package/dist/types/config/file-lock.d.ts +9 -1
- package/dist/types/config/model-profile-activation.d.ts +45 -1
- package/dist/types/config/model-registry.d.ts +11 -2
- package/dist/types/config/settings-schema.d.ts +11 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
- package/dist/types/daemon/operator-contract.d.ts +5 -1
- package/dist/types/eval/js/worker-protocol.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +12 -0
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
- package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/parse.d.ts +9 -1
- package/dist/types/main.d.ts +22 -0
- package/dist/types/modes/acp/acp-agent.d.ts +55 -1
- package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
- package/dist/types/modes/components/session-selector.d.ts +5 -2
- package/dist/types/modes/components/user-message-selector.d.ts +5 -2
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
- package/dist/types/modes/types.d.ts +2 -2
- package/dist/types/runtime/process-lifecycle.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +56 -0
- package/dist/types/runtime-mcp/pool.d.ts +17 -0
- package/dist/types/runtime-mcp/redaction.d.ts +20 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
- package/dist/types/sdk/broker/broker.d.ts +13 -2
- package/dist/types/sdk/broker/identity.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
- package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
- package/dist/types/sdk/broker/runtime.d.ts +1 -1
- package/dist/types/sdk/broker/session-index.d.ts +22 -3
- package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
- package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
- package/dist/types/sdk/bus/index.d.ts +19 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
- package/dist/types/sdk/cli/master-cli.d.ts +6 -10
- package/dist/types/sdk/cli/rows.d.ts +19 -1
- package/dist/types/sdk/cli/session-cli.d.ts +22 -5
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/guides/cli.d.ts +3 -4
- package/dist/types/sdk/guides/verify.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +9 -0
- package/dist/types/sdk/host/session-runtime.d.ts +31 -0
- package/dist/types/sdk/host/types.d.ts +10 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
- package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -0
- package/dist/types/sdk/transport/relay.d.ts +1 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
- package/dist/types/sdk/transport/stdio.d.ts +5 -1
- package/dist/types/session/agent-session.d.ts +168 -21
- package/dist/types/session/fallback-chain-controller.d.ts +6 -6
- package/dist/types/session/fold-coordinator.d.ts +17 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
- package/dist/types/session/session-manager.d.ts +64 -1
- package/dist/types/session/session-work-lease.d.ts +14 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
- package/dist/types/setup/provider-onboarding.d.ts +81 -1
- package/dist/types/tools/bash.d.ts +37 -11
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/steer-fold.d.ts +31 -0
- package/dist/types/utils/git.d.ts +28 -2
- package/dist/types/utils/mupdf-embedded.d.ts +2 -0
- package/dist/types/utils/mupdf.d.ts +4 -0
- package/dist/types/utils/session-title-generation.d.ts +6 -0
- package/dist/types/utils/title-generator.d.ts +11 -2
- package/dist/types/web/scrapers/types.d.ts +3 -0
- package/package.json +18 -10
- package/scripts/build-binary.ts +9 -3
- package/scripts/compile-args.ts +0 -9
- package/scripts/embed-mupdf.ts +52 -0
- package/scripts/generate-sdk-operation-inventory.ts +31 -1
- package/scripts/vendor-markit.ts +196 -0
- package/src/async/job-manager.ts +22 -1
- package/src/cli/daemon-cli.ts +53 -18
- package/src/cli/github-release.ts +87 -5
- package/src/cli/mcp-cli.ts +38 -4
- package/src/cli/public-command-entry.ts +450 -0
- package/src/cli/public-command-errors.ts +581 -0
- package/src/cli/public-command-evidence.ts +717 -0
- package/src/cli/public-command-help.ts +360 -0
- package/src/cli/public-command-registry.ts +535 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +9 -1
- package/src/cli/skills-cli.ts +46 -2
- package/src/cli.ts +61 -1
- package/src/commands/daemon.ts +65 -69
- package/src/commands/sdk.ts +175 -117
- package/src/commands/setup.ts +2 -0
- package/src/commands/skills.ts +11 -0
- package/src/config/autorouting-tier-map.ts +14 -0
- package/src/config/file-lock.ts +147 -51
- package/src/config/model-profile-activation.ts +334 -43
- package/src/config/model-registry.ts +183 -25
- package/src/config/settings-schema.ts +26 -2
- package/src/coordinator-mcp/policy.ts +10 -5
- package/src/coordinator-mcp/question-gate-codec.ts +17 -0
- package/src/coordinator-mcp/question-state.ts +175 -8
- package/src/coordinator-mcp/server.ts +507 -81
- package/src/coordinator-mcp/session-reaper.ts +6 -4
- package/src/crash/record-loader.ts +6 -2
- package/src/daemon/operator-contract.ts +26 -12
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/context-manager.ts +8 -8
- package/src/eval/js/worker-core.ts +9 -4
- package/src/eval/js/worker-protocol.ts +2 -0
- package/src/extensibility/extensions/runner.ts +52 -4
- package/src/extensibility/extensions/types.ts +10 -0
- package/src/extensibility/gjc-plugins/registry.ts +196 -37
- package/src/extensibility/runtime-skill-discovery.ts +61 -3
- package/src/extensibility/shared-events.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +165 -24
- package/src/gjc-runtime/ultragoal-succession.ts +31 -3
- package/src/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/embedded-protocol.ts +83 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/parse.ts +21 -1
- package/src/internal-urls/router.ts +4 -2
- package/src/main.ts +123 -55
- package/src/modes/acp/acp-agent.ts +408 -35
- package/src/modes/acp/startup-provenance.ts +59 -0
- package/src/modes/components/custom-provider-wizard.ts +308 -7
- package/src/modes/components/oauth-selector.ts +9 -0
- package/src/modes/components/session-selector.ts +47 -4
- package/src/modes/components/user-message-selector.ts +57 -15
- package/src/modes/controllers/command-controller.ts +58 -32
- package/src/modes/controllers/extension-ui-controller.ts +33 -7
- package/src/modes/controllers/input-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +161 -15
- package/src/modes/interactive-mode.ts +21 -7
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
- package/src/modes/types.ts +2 -2
- package/src/prompts/agent-fragments/browser-aside.md +1 -0
- package/src/prompts/system/title-system.md +1 -1
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/job.md +1 -1
- package/src/prompts/tools/read.md +2 -0
- package/src/runtime/network-prewarm-service.test.ts +36 -1
- package/src/runtime/network-prewarm-service.ts +28 -3
- package/src/runtime/process-lifecycle.ts +22 -11
- package/src/runtime-mcp/config.ts +16 -1
- package/src/runtime-mcp/manager.ts +689 -55
- package/src/runtime-mcp/pool.test.ts +31 -0
- package/src/runtime-mcp/pool.ts +304 -28
- package/src/runtime-mcp/redaction.ts +42 -0
- package/src/runtime-mcp/startup-policy.ts +7 -0
- package/src/runtime-mcp/tool-bridge.ts +71 -0
- package/src/runtime-mcp/transports/http.ts +108 -25
- package/src/sdk/acp/adapter.ts +27 -8
- package/src/sdk/broker/broker.ts +378 -52
- package/src/sdk/broker/identity.ts +9 -1
- package/src/sdk/broker/lifecycle-ledger.ts +95 -31
- package/src/sdk/broker/lifecycle.ts +690 -141
- package/src/sdk/broker/runtime.ts +1 -1
- package/src/sdk/broker/session-index.ts +97 -4
- package/src/sdk/broker/spawn-authority.ts +35 -7
- package/src/sdk/broker/stderr-drainer.ts +59 -0
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/index.ts +690 -80
- package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -3
- package/src/sdk/cli/master-cli.ts +47 -36
- package/src/sdk/cli/rows.ts +30 -7
- package/src/sdk/cli/session-cli.ts +687 -184
- package/src/sdk/client/client.ts +18 -0
- package/src/sdk/guides/catalog.ts +2 -2
- package/src/sdk/guides/cli.ts +20 -62
- package/src/sdk/guides/verify.ts +14 -0
- package/src/sdk/host/host.ts +49 -5
- package/src/sdk/host/session-runtime.test.ts +138 -2
- package/src/sdk/host/session-runtime.ts +311 -97
- package/src/sdk/host/types.ts +12 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
- package/src/sdk/host/websocket-transport.ts +40 -6
- package/src/sdk/index.ts +11 -0
- package/src/sdk/prompt-deadline-flush.ts +334 -0
- package/src/sdk/prompt-deadline-manager.ts +133 -1
- package/src/sdk/prompt-tool-boundary.ts +121 -0
- package/src/sdk/protocol/operation-inventory.generated.json +106 -0
- package/src/sdk/router/session-router.ts +9 -1
- package/src/sdk/session.ts +182 -7
- package/src/sdk/startup-capability.ts +1 -1
- package/src/sdk/transport/relay.ts +71 -6
- package/src/sdk/transport/serve-cli.ts +397 -42
- package/src/sdk/transport/socket.ts +12 -11
- package/src/sdk/transport/stdio.ts +7 -3
- package/src/session/agent-session.ts +2437 -400
- package/src/session/fallback-chain-controller.ts +6 -7
- package/src/session/fold-coordinator.ts +24 -5
- package/src/session/internal/managed-session-scope.ts +58 -18
- package/src/session/internal/managed-session-storage.ts +304 -11
- package/src/session/session-manager.ts +382 -44
- package/src/session/session-work-lease.ts +34 -0
- package/src/setup/model-onboarding-guidance.ts +1 -1
- package/src/setup/provider-onboarding.ts +397 -24
- package/src/slash-commands/builtin-registry.ts +258 -14
- package/src/task/index.ts +14 -2
- package/src/tools/bash.ts +98 -78
- package/src/tools/fetch.ts +119 -20
- package/src/tools/job.ts +151 -25
- package/src/tools/output-meta.ts +15 -2
- package/src/tools/path-utils.ts +27 -3
- package/src/tools/read.ts +3 -3
- package/src/tools/steer-fold.ts +107 -0
- package/src/tools/tool-catalog.generated.ts +29 -2
- package/src/tools/tool-errors.ts +4 -0
- package/src/utils/git.ts +53 -3
- package/src/utils/markit.ts +55 -41
- package/src/utils/mupdf-assets.d.ts +4 -0
- package/src/utils/mupdf-embedded.ts +4 -0
- package/src/utils/mupdf.ts +129 -0
- package/src/utils/session-title-generation.ts +29 -0
- package/src/utils/title-generator.ts +46 -2
- package/src/web/scrapers/types.ts +24 -3
- package/vendor/markit-ai/LICENSE +21 -0
- package/vendor/markit-ai/MANIFEST.json +412 -0
- package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/config.js +139 -0
- package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
- package/vendor/markit-ai/dist/commands/convert.js +113 -0
- package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/formats.js +92 -0
- package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/init.js +29 -0
- package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
- package/vendor/markit-ai/dist/commands/onboard.js +61 -0
- package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
- package/vendor/markit-ai/dist/commands/plugin.js +58 -0
- package/vendor/markit-ai/dist/config.d.ts +26 -0
- package/vendor/markit-ai/dist/config.js +42 -0
- package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/audio.js +104 -0
- package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
- package/vendor/markit-ai/dist/converters/csv.js +83 -0
- package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/docx.js +57 -0
- package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/epub.js +128 -0
- package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
- package/vendor/markit-ai/dist/converters/github.js +148 -0
- package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/html.js +30 -0
- package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/image.js +124 -0
- package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
- package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
- package/vendor/markit-ai/dist/converters/iwork.js +391 -0
- package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/json.js +21 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
- package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
- package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
- package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
- package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
- package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
- package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
- package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
- package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
- package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
- package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
- package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/pptx.js +289 -0
- package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
- package/vendor/markit-ai/dist/converters/rss.js +138 -0
- package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
- package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
- package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/xml.js +18 -0
- package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
- package/vendor/markit-ai/dist/converters/yaml.js +17 -0
- package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
- package/vendor/markit-ai/dist/converters/zip.js +57 -0
- package/vendor/markit-ai/dist/index.d.ts +30 -0
- package/vendor/markit-ai/dist/index.js +26 -0
- package/vendor/markit-ai/dist/main.d.ts +2 -0
- package/vendor/markit-ai/dist/main.js +191 -0
- package/vendor/markit-ai/dist/markit.d.ts +45 -0
- package/vendor/markit-ai/dist/markit.js +267 -0
- package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
- package/vendor/markit-ai/dist/plugins/api.js +42 -0
- package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
- package/vendor/markit-ai/dist/plugins/index.js +3 -0
- package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/installer.js +176 -0
- package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
- package/vendor/markit-ai/dist/plugins/loader.js +61 -0
- package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
- package/vendor/markit-ai/dist/plugins/types.js +1 -0
- package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
- package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
- package/vendor/markit-ai/dist/providers/index.js +56 -0
- package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
- package/vendor/markit-ai/dist/providers/openai.js +76 -0
- package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
- package/vendor/markit-ai/dist/providers/types.js +1 -0
- package/vendor/markit-ai/dist/types.d.ts +36 -0
- package/vendor/markit-ai/dist/types.js +1 -0
- package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
- package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
- package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
- package/vendor/markit-ai/dist/utils/output.js +31 -0
- package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
- package/vendor/markit-ai/dist/utils/turndown.js +70 -0
- package/vendor/markit-ai/package.json +74 -0
- package/vendor/markit-ai.patch +49 -0
|
@@ -60,7 +60,11 @@ import {
|
|
|
60
60
|
promptFailureRetryability,
|
|
61
61
|
rephaseFailedOutcome,
|
|
62
62
|
} from "../../sdk/prompt-failure";
|
|
63
|
-
import type {
|
|
63
|
+
import type {
|
|
64
|
+
SdkPromptFailureCategory,
|
|
65
|
+
SdkPromptFailurePhase,
|
|
66
|
+
SdkPromptTerminalOutcome,
|
|
67
|
+
} from "../../sdk/prompt-status";
|
|
64
68
|
import { PromptActivity, type PromptWatchdogClock, systemPromptWatchdogClock } from "../../sdk/prompt-watchdog";
|
|
65
69
|
import { validateRequiredPromptText } from "../../sdk/protocol/adapter-validation";
|
|
66
70
|
import { type SessionAttachment, SessionRouter, type SessionRouterFrame } from "../../sdk/router";
|
|
@@ -74,6 +78,7 @@ import {
|
|
|
74
78
|
} from "./acp-event-mapper";
|
|
75
79
|
import { resolveAcpPermissionMode } from "./permission-mode";
|
|
76
80
|
import type { AcpStartupOptions } from "./startup-options";
|
|
81
|
+
import { resolveStartupProvenance } from "./startup-provenance";
|
|
77
82
|
import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
|
|
78
83
|
|
|
79
84
|
const ACP_DEFAULT_MODE_ID = "default";
|
|
@@ -159,6 +164,9 @@ const ACP_FIRST_PROMPT_RETRY_BASE_DELAY_MS = 250;
|
|
|
159
164
|
type JsonObject = Record<string, unknown>;
|
|
160
165
|
interface PromptWaiter {
|
|
161
166
|
acknowledged: boolean;
|
|
167
|
+
invocationKind: "prompt" | "skill";
|
|
168
|
+
/** ACP-owned identity, never inherited from caller metadata or reused for replay. */
|
|
169
|
+
clientRef: string;
|
|
162
170
|
/** True once turn.prompt / skill.invoke has been sent; cancel must not fake-settle after this. */
|
|
163
171
|
dispatched: boolean;
|
|
164
172
|
/** True only while the dispatched control request can still reveal its correlation. */
|
|
@@ -253,6 +261,8 @@ type SessionRecord = {
|
|
|
253
261
|
/** Replayable startup notice captured before ACP bootstrap; emitted once after the session id is known. */
|
|
254
262
|
routingInactiveNotice?: string;
|
|
255
263
|
activePrompt?: PromptWaiter;
|
|
264
|
+
/** One bounded read-only recovery owner for this attachment and waiter. */
|
|
265
|
+
statusRecovery?: PromptWaiter;
|
|
256
266
|
/** Set by `session/cancel` so an in-flight prompt settles as `cancelled`, never as an error. */
|
|
257
267
|
cancelRequested?: boolean;
|
|
258
268
|
/** True once the session's first logical prompt has settled; gates first-turn readiness retries. */
|
|
@@ -640,6 +650,46 @@ export class AcpPromptFailureError extends AcpSdkAdapterError {
|
|
|
640
650
|
}
|
|
641
651
|
}
|
|
642
652
|
|
|
653
|
+
/**
|
|
654
|
+
* Recover the shared prompt-failure projection for a direct SDK rejection.
|
|
655
|
+
*
|
|
656
|
+
* A terminal frame normally reaches {@link AcpPromptFailureError} through
|
|
657
|
+
* `#settlePrompt`, but a host may reject the `turn.prompt` control request
|
|
658
|
+
* itself with `code: "prompt_failed"`. That path never has a terminal outcome
|
|
659
|
+
* to retain, so translating the raw adapter error used to bypass
|
|
660
|
+
* `promptFailureWireData` and publish only the bare `code`/`details` pair.
|
|
661
|
+
* Keep the fallback deliberately narrow: only the two prompt terminal codes
|
|
662
|
+
* are upgraded, and any optional provider classifier is re-validated before
|
|
663
|
+
* it enters the existing classifier/projection seam.
|
|
664
|
+
*/
|
|
665
|
+
function promptFailureFromDirectError(error: unknown): SdkPromptFailedOutcome | undefined {
|
|
666
|
+
if (error instanceof AcpPromptFailureError) return error.failure;
|
|
667
|
+
let code: unknown;
|
|
668
|
+
let providerCode: unknown;
|
|
669
|
+
let phase: unknown;
|
|
670
|
+
try {
|
|
671
|
+
const candidate = error as {
|
|
672
|
+
code?: unknown;
|
|
673
|
+
providerCode?: unknown;
|
|
674
|
+
transportFailure?: { providerCode?: unknown };
|
|
675
|
+
phase?: unknown;
|
|
676
|
+
};
|
|
677
|
+
code = candidate?.code;
|
|
678
|
+
providerCode = candidate?.providerCode ?? candidate?.transportFailure?.providerCode;
|
|
679
|
+
phase = candidate?.phase;
|
|
680
|
+
} catch {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
if (code !== "prompt_failed" && code !== "prompt_deadline_exceeded") return undefined;
|
|
684
|
+
return failedPromptOutcome({
|
|
685
|
+
code,
|
|
686
|
+
provenance: code === "prompt_deadline_exceeded" ? "deadline" : "agent_failed",
|
|
687
|
+
...(isSafePromptFailureCode(providerCode) ? { providerCode } : {}),
|
|
688
|
+
...(isSdkPromptFailurePhase(phase) ? { phase } : {}),
|
|
689
|
+
evidence: {},
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
643
693
|
/**
|
|
644
694
|
* The wire projection of a prompt terminal's classification (issue #5615).
|
|
645
695
|
*
|
|
@@ -655,12 +705,71 @@ export class AcpPromptFailureError extends AcpSdkAdapterError {
|
|
|
655
705
|
* dropped instead of becoming a leak of provider text (the redaction contract in
|
|
656
706
|
* `sanitizePromptFailure`). A dropped or absent code is omitted, never nulled.
|
|
657
707
|
*/
|
|
708
|
+
export const OPERATOR_UPSTREAM_LABEL = "Upstream provider failure";
|
|
709
|
+
export const OPERATOR_UPSTREAM_SUFFIX = ": the model provider ended this turn.";
|
|
710
|
+
/**
|
|
711
|
+
* `post_start` proves execution began, so tools may already have run and had effects before the
|
|
712
|
+
* provider died. The wording therefore qualifies the turn rather than absolving the task: saying
|
|
713
|
+
* the task was unaffected would assert something the bounded classifiers do not establish.
|
|
714
|
+
*/
|
|
715
|
+
export const OPERATOR_PARTIAL_WORK_NOTE =
|
|
716
|
+
" Work this turn had already performed may have taken effect; check before retrying.";
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Operator-facing wording for a post-start terminal, assembled ONLY from bounded safe tokens: the
|
|
720
|
+
* classifier `phase`, the `category`, and a `providerCode` that passes `isSafePromptFailureCode`.
|
|
721
|
+
* Raw provider text never reaches this function and must never reach it.
|
|
722
|
+
*
|
|
723
|
+
* Gated on BOTH `phase === "post_start"` and `category === "provider_transport"`. A submission-phase
|
|
724
|
+
* provider failure is rejected by the phase gate: nothing executed, so wording about a turn the
|
|
725
|
+
* provider ended would describe an event that never occurred.
|
|
726
|
+
*
|
|
727
|
+
* It claims only what those tokens prove — that the upstream provider ended the turn — and says
|
|
728
|
+
* nothing about whether the operator's work survived. `post_start` proves execution began, so the
|
|
729
|
+
* wording qualifies that earlier work may already have taken effect instead of absolving the task.
|
|
730
|
+
*
|
|
731
|
+
* This is additive. The wire `code`/`details` pair and every `PROMPT_FAILURE_MESSAGE_*` constant
|
|
732
|
+
* stay exactly as they are — pinned ACP core-v1 conformance asserts on them — so this wording
|
|
733
|
+
* travels alongside the redacted message instead of repurposing it.
|
|
734
|
+
*
|
|
735
|
+
* It says NOTHING about the worktree, deliberately. Nothing on this path inspects the worktree, so
|
|
736
|
+
* any sentence about uncommitted work would assert a check that never ran — reporting an
|
|
737
|
+
* uninspected tree as clean is the absence-of-evidence-as-evidence-of-absence error, and an
|
|
738
|
+
* operator who believed it would sweep work away. Silence is the only honest option until something
|
|
739
|
+
* actually looks.
|
|
740
|
+
*
|
|
741
|
+
* Returns `undefined` for every other category: `phase` and `category` are already on the wire, so
|
|
742
|
+
* restating them in prose tells an operator nothing they do not already have.
|
|
743
|
+
*/
|
|
744
|
+
export function postStartOperatorMessage(input: {
|
|
745
|
+
phase: SdkPromptFailurePhase;
|
|
746
|
+
category: SdkPromptFailureCategory;
|
|
747
|
+
providerCode?: string;
|
|
748
|
+
}): string | undefined {
|
|
749
|
+
// Phase first: a submission-phase provider failure never started executing, so wording about a
|
|
750
|
+
// turn that ended mid-flight would describe something that did not happen. `phase` is a bounded
|
|
751
|
+
// classifier already on the wire, so gating on it widens nothing.
|
|
752
|
+
if (input.phase !== "post_start") return undefined;
|
|
753
|
+
if (input.category !== "provider_transport") return undefined;
|
|
754
|
+
const code = isSafePromptFailureCode(input.providerCode) ? ` (${input.providerCode})` : "";
|
|
755
|
+
return `${OPERATOR_UPSTREAM_LABEL}${code}${OPERATOR_UPSTREAM_SUFFIX}${OPERATOR_PARTIAL_WORK_NOTE}`;
|
|
756
|
+
}
|
|
757
|
+
|
|
658
758
|
function promptFailureWireData(failure: SdkPromptFailedOutcome): Record<string, string> {
|
|
759
|
+
// `operatorMessage` says, in words, what the classifier tokens already say in codes: that the
|
|
760
|
+
// cause was an upstream provider problem rather than a failure of the operator's task. Built
|
|
761
|
+
// only from those same bounded tokens, so it widens nothing that reaches the wire.
|
|
762
|
+
const operatorMessage = postStartOperatorMessage({
|
|
763
|
+
phase: failure.phase,
|
|
764
|
+
category: failure.category,
|
|
765
|
+
...(failure.providerCode === undefined ? {} : { providerCode: failure.providerCode }),
|
|
766
|
+
});
|
|
659
767
|
return {
|
|
660
768
|
phase: failure.phase,
|
|
661
769
|
category: failure.category,
|
|
662
770
|
retryability: promptFailureRetryability(failure.category),
|
|
663
771
|
...(isSafePromptFailureCode(failure.providerCode) ? { providerCode: failure.providerCode } : {}),
|
|
772
|
+
...(operatorMessage === undefined ? {} : { operatorMessage }),
|
|
664
773
|
};
|
|
665
774
|
}
|
|
666
775
|
|
|
@@ -1366,9 +1475,10 @@ export function acpRequestFailure(error: unknown): unknown {
|
|
|
1366
1475
|
// spread is empty for every other error, so their payloads are byte-identical to
|
|
1367
1476
|
// before. `code`/`details` and the JSON-RPC code itself are untouched either way:
|
|
1368
1477
|
// this enriches `data`, it does not restate the redacted message.
|
|
1478
|
+
const promptFailure = promptFailureFromDirectError(error);
|
|
1369
1479
|
const data =
|
|
1370
|
-
|
|
1371
|
-
? { code, details: message, ...promptFailureWireData(
|
|
1480
|
+
promptFailure !== undefined
|
|
1481
|
+
? { code, details: message, ...promptFailureWireData(promptFailure) }
|
|
1372
1482
|
: { code, details: message };
|
|
1373
1483
|
// An abandoned prompt additionally publishes the plan it never finished (issue #5669).
|
|
1374
1484
|
// Same rule as above: `code`/`details` and the JSON-RPC code are untouched, and an abandon
|
|
@@ -1510,7 +1620,7 @@ export class AcpAgent implements Agent {
|
|
|
1510
1620
|
/** Retain settled prompt identities across automatic transport reattachment. */
|
|
1511
1621
|
readonly #retiredPromptCorrelations = new Map<string, PromptCorrelation[]>();
|
|
1512
1622
|
/** Prevent a successor admission while a retired prompt can still reveal its identity. */
|
|
1513
|
-
readonly #retiredPromptAcknowledgements = new
|
|
1623
|
+
readonly #retiredPromptAcknowledgements = new Map<string, PromptWaiter>();
|
|
1514
1624
|
/** Ready terminal metadata writes retain ownership across same-id record replacement. */
|
|
1515
1625
|
readonly #terminalMetadataTails = new Map<string, Promise<void>>();
|
|
1516
1626
|
/** Unstreamed terminal text retains transcript ownership across same-id replacement. */
|
|
@@ -2112,6 +2222,7 @@ export class AcpAgent implements Agent {
|
|
|
2112
2222
|
|
|
2113
2223
|
const payload = acpPromptPayload(params.prompt);
|
|
2114
2224
|
const skillInvocation = acpSkillInvocation(params.prompt);
|
|
2225
|
+
const clientRef = randomUUID();
|
|
2115
2226
|
if (!skillInvocation) {
|
|
2116
2227
|
const promptError = validateRequiredPromptText("turn.prompt", {
|
|
2117
2228
|
text: payload.text,
|
|
@@ -2146,7 +2257,7 @@ export class AcpAgent implements Agent {
|
|
|
2146
2257
|
JSON.stringify({
|
|
2147
2258
|
type: "control_request",
|
|
2148
2259
|
operation: skillInvocation ? "skill.invoke" : "turn.prompt",
|
|
2149
|
-
input: skillInvocation ?? { text: payload.text, images: payload.images },
|
|
2260
|
+
input: { ...(skillInvocation ?? { text: payload.text, images: payload.images }), clientRef },
|
|
2150
2261
|
// AcpSdkAdapter.control passes `confirm: false` to SdkClient, which serializes
|
|
2151
2262
|
// the field even though it is not part of the skill input payload.
|
|
2152
2263
|
...(skillInvocation ? { confirm: false } : {}),
|
|
@@ -2168,6 +2279,8 @@ export class AcpAgent implements Agent {
|
|
|
2168
2279
|
record.promptObservedAssistantOutput = false;
|
|
2169
2280
|
const { promise: response, resolve, reject } = Promise.withResolvers<PromptResponse>();
|
|
2170
2281
|
const waiter: PromptWaiter = {
|
|
2282
|
+
invocationKind: skillInvocation ? "skill" : "prompt",
|
|
2283
|
+
clientRef,
|
|
2171
2284
|
acknowledged: false,
|
|
2172
2285
|
dispatched: false,
|
|
2173
2286
|
acknowledgementPending: false,
|
|
@@ -2212,11 +2325,11 @@ export class AcpAgent implements Agent {
|
|
|
2212
2325
|
} catch (error) {
|
|
2213
2326
|
waiter.awaitingProviderPreflight = false;
|
|
2214
2327
|
if (waiter.settled || record.activePrompt !== waiter) {
|
|
2215
|
-
this.#
|
|
2328
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2216
2329
|
return await response;
|
|
2217
2330
|
}
|
|
2218
2331
|
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
2219
|
-
this.#
|
|
2332
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2220
2333
|
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
2221
2334
|
return await response;
|
|
2222
2335
|
}
|
|
@@ -2224,18 +2337,22 @@ export class AcpAgent implements Agent {
|
|
|
2224
2337
|
record.activePrompt = undefined;
|
|
2225
2338
|
record.busy = record.backgroundBusy;
|
|
2226
2339
|
clearPromptWatchdog(waiter);
|
|
2227
|
-
|
|
2340
|
+
// #5526's guarded release replaces the unguarded delete: it only clears
|
|
2341
|
+
// the entry when it still belongs to THIS waiter, so a successor's
|
|
2342
|
+
// acknowledgement is never dropped. Strictly narrower than the delete
|
|
2343
|
+
// it supersedes; dev's watchdog clear above is unaffected.
|
|
2344
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2228
2345
|
void this.#publishPromptPhaseIdle(params.sessionId, record.adapter);
|
|
2229
2346
|
}
|
|
2230
2347
|
throw error;
|
|
2231
2348
|
}
|
|
2232
2349
|
waiter.awaitingProviderPreflight = false;
|
|
2233
2350
|
if (waiter.settled || record.activePrompt !== waiter) {
|
|
2234
|
-
this.#
|
|
2351
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2235
2352
|
return await response;
|
|
2236
2353
|
}
|
|
2237
2354
|
if (record.cancelRequested && waiter.cancelAcknowledged) {
|
|
2238
|
-
this.#
|
|
2355
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2239
2356
|
await this.#settleCancelledPrompt(params.sessionId, record, waiter);
|
|
2240
2357
|
return await response;
|
|
2241
2358
|
}
|
|
@@ -2278,14 +2395,18 @@ export class AcpAgent implements Agent {
|
|
|
2278
2395
|
}
|
|
2279
2396
|
|
|
2280
2397
|
waiter.acknowledgementPending = true;
|
|
2398
|
+
const promptAdapter = record.adapter;
|
|
2281
2399
|
const acknowledgementTask = (async (): Promise<PromptResponse> => {
|
|
2282
2400
|
if (waiter.settled || record.activePrompt !== waiter) return await response;
|
|
2283
2401
|
const acknowledgement = skillInvocation
|
|
2284
|
-
? await
|
|
2285
|
-
: await
|
|
2402
|
+
? await promptAdapter.control("skill.invoke", { ...skillInvocation, clientRef })
|
|
2403
|
+
: await promptAdapter.prompt({
|
|
2286
2404
|
text: payload.text,
|
|
2405
|
+
clientRef,
|
|
2287
2406
|
...(payload.images.length ? { images: payload.images } : {}),
|
|
2288
2407
|
});
|
|
2408
|
+
// A recovered waiter already owns its exact identity; a late ack cannot rebind it.
|
|
2409
|
+
if (waiter.settled && waiter.acknowledged) return await response;
|
|
2289
2410
|
|
|
2290
2411
|
const acknowledgementCorrelation = promptAcknowledgement(acknowledgement);
|
|
2291
2412
|
if (!acknowledgementCorrelation)
|
|
@@ -2293,6 +2414,14 @@ export class AcpAgent implements Agent {
|
|
|
2293
2414
|
"invalid_prompt_acknowledgement",
|
|
2294
2415
|
"SDK prompt acknowledgement must accept the prompt and include commandId and turnId.",
|
|
2295
2416
|
);
|
|
2417
|
+
if (
|
|
2418
|
+
this.#sessions.get(params.sessionId) !== record ||
|
|
2419
|
+
record.adapter !== promptAdapter ||
|
|
2420
|
+
record.activePrompt !== waiter
|
|
2421
|
+
) {
|
|
2422
|
+
this.#rememberSettledPromptCorrelation(params.sessionId, record, acknowledgementCorrelation);
|
|
2423
|
+
return await response;
|
|
2424
|
+
}
|
|
2296
2425
|
if (this.#hasRetiredPromptCorrelation(params.sessionId, record, acknowledgementCorrelation)) {
|
|
2297
2426
|
try {
|
|
2298
2427
|
const abortAcknowledgement = await record.adapter.cancel("turn");
|
|
@@ -2427,10 +2556,20 @@ export class AcpAgent implements Agent {
|
|
|
2427
2556
|
}
|
|
2428
2557
|
this.#settlePrompt(params.sessionId, record, waiter);
|
|
2429
2558
|
return await response;
|
|
2430
|
-
})()
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2559
|
+
})()
|
|
2560
|
+
.catch(async error => {
|
|
2561
|
+
if (
|
|
2562
|
+
!(error instanceof SdkClientError || error instanceof AcpSdkAdapterError) ||
|
|
2563
|
+
error.code !== "uncertain_after_send"
|
|
2564
|
+
)
|
|
2565
|
+
throw error;
|
|
2566
|
+
if (record.adapter === promptAdapter) this.#startUncertainPromptRecovery(params.sessionId, record, waiter);
|
|
2567
|
+
return await response;
|
|
2568
|
+
})
|
|
2569
|
+
.finally(() => {
|
|
2570
|
+
waiter.acknowledgementPending = false;
|
|
2571
|
+
this.#releaseRetiredPromptAcknowledgement(params.sessionId, waiter);
|
|
2572
|
+
});
|
|
2434
2573
|
// Settlement can win before the SDK answers `turn.prompt`; acknowledgement processing
|
|
2435
2574
|
// must remain alive to tombstone its eventual correlation without holding the ACP caller.
|
|
2436
2575
|
void acknowledgementTask.catch(() => undefined);
|
|
@@ -2889,24 +3028,23 @@ export class AcpAgent implements Agent {
|
|
|
2889
3028
|
// a broker-launched SDK host keeps ACP ownership across reconnects.
|
|
2890
3029
|
await adapter.start({ activateProviders: false });
|
|
2891
3030
|
let capabilities: JsonObject | undefined;
|
|
3031
|
+
// A query that never answered and a host that answered without provenance are
|
|
3032
|
+
// different operator problems, so the failure is retained instead of discarded.
|
|
3033
|
+
let capabilitiesFailure: unknown;
|
|
2892
3034
|
try {
|
|
2893
3035
|
const response = object(await adapter.query("runtime.capabilities"));
|
|
2894
3036
|
const result = object(response?.result) ?? response;
|
|
2895
3037
|
// Q18 is a paged query surface: the capability object arrives as the single
|
|
2896
3038
|
// page item, so fall back to the envelope only for direct-result hosts.
|
|
2897
3039
|
capabilities = object(pageItems(result)[0]) ?? result;
|
|
2898
|
-
} catch {
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
throw new AcpSdkAdapterError(
|
|
2907
|
-
"unavailable",
|
|
2908
|
-
"This ACP client requires a newer GJC SDK session with startup control provenance; restart the session.",
|
|
2909
|
-
);
|
|
3040
|
+
} catch (error) {
|
|
3041
|
+
capabilitiesFailure = error;
|
|
3042
|
+
}
|
|
3043
|
+
const primaryControlSurface = resolveStartupProvenance({
|
|
3044
|
+
sessionId: id,
|
|
3045
|
+
capabilities,
|
|
3046
|
+
queryFailure: capabilitiesFailure,
|
|
3047
|
+
});
|
|
2910
3048
|
if (primaryControlSurface === "sdk") {
|
|
2911
3049
|
adapter.authorizeProviderActivation();
|
|
2912
3050
|
await adapter.ensureProviders();
|
|
@@ -3017,7 +3155,11 @@ export class AcpAgent implements Agent {
|
|
|
3017
3155
|
|
|
3018
3156
|
#fenceRetiredPromptAcknowledgement(id: string, waiter: PromptWaiter): void {
|
|
3019
3157
|
if (waiter.dispatched && waiter.acknowledgementPending && !waiter.acknowledged)
|
|
3020
|
-
this.#retiredPromptAcknowledgements.
|
|
3158
|
+
this.#retiredPromptAcknowledgements.set(id, waiter);
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
#releaseRetiredPromptAcknowledgement(id: string, waiter: PromptWaiter): void {
|
|
3162
|
+
if (this.#retiredPromptAcknowledgements.get(id) === waiter) this.#retiredPromptAcknowledgements.delete(id);
|
|
3021
3163
|
}
|
|
3022
3164
|
|
|
3023
3165
|
#advanceTerminalGeneration(record: SessionRecord): void {
|
|
@@ -3027,6 +3169,15 @@ export class AcpAgent implements Agent {
|
|
|
3027
3169
|
#recoverSessionAfterTransportFailure(id: string, adapter: AcpSdkAdapter, error: Error): void {
|
|
3028
3170
|
const record = this.#sessions.get(id);
|
|
3029
3171
|
if (!record || record.adapter !== adapter) return;
|
|
3172
|
+
if (error instanceof SdkClientError && error.code === "uncertain_after_send") {
|
|
3173
|
+
// Provider-registration requests share this transport error. Recover only when the
|
|
3174
|
+
// prompt's own dispatched acknowledgement is still pending; an acknowledged turn
|
|
3175
|
+
// must continue until its terminal frame arrives.
|
|
3176
|
+
const waiter = record.activePrompt;
|
|
3177
|
+
if (waiter?.dispatched && waiter.acknowledgementPending && !waiter.acknowledged)
|
|
3178
|
+
this.#startUncertainPromptRecovery(id, record, waiter);
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3030
3181
|
if (
|
|
3031
3182
|
error instanceof SdkClientError &&
|
|
3032
3183
|
error.code !== "reconnect_exhausted" &&
|
|
@@ -3041,11 +3192,191 @@ export class AcpAgent implements Agent {
|
|
|
3041
3192
|
logger.warn(`ACP session ${id} non-transport error reached reconnect handler; ignoring terminal recovery.`);
|
|
3042
3193
|
return;
|
|
3043
3194
|
}
|
|
3195
|
+
const waiter = record.activePrompt;
|
|
3196
|
+
if (waiter?.dispatched) {
|
|
3197
|
+
// A prior uncertainty notification already owns recovery for this waiter. Keep
|
|
3198
|
+
// the in-flight lookup alive; only declined, non-recovering waiters fall through
|
|
3199
|
+
// to the ordinary transport-loss teardown and reattachment path.
|
|
3200
|
+
if (record.statusRecovery === waiter || this.#startUncertainPromptRecovery(id, record, waiter)) return;
|
|
3201
|
+
}
|
|
3044
3202
|
const detail = error.message || "SDK transport reconnect failed.";
|
|
3045
3203
|
const terminal = new AcpSdkAdapterError("connection_closed", `ACP session transport was lost: ${detail}`);
|
|
3046
3204
|
void this.#recoverSessionAfterTransportFailureAsync(id, adapter, record.cwd, terminal);
|
|
3047
3205
|
}
|
|
3048
3206
|
|
|
3207
|
+
#startUncertainPromptRecovery(id: string, record: SessionRecord, waiter: PromptWaiter): boolean {
|
|
3208
|
+
if (
|
|
3209
|
+
this.#sessions.get(id) !== record ||
|
|
3210
|
+
promptWaiterRetired(record, waiter) ||
|
|
3211
|
+
!waiter.dispatched ||
|
|
3212
|
+
waiter.terminalReserved ||
|
|
3213
|
+
record.statusRecovery === waiter
|
|
3214
|
+
)
|
|
3215
|
+
return false;
|
|
3216
|
+
record.statusRecovery = waiter;
|
|
3217
|
+
clearPromptWatchdog(waiter);
|
|
3218
|
+
void this.#recoverUncertainPrompt(id, record, record.adapter, waiter);
|
|
3219
|
+
return true;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
async #recoverUncertainPrompt(
|
|
3223
|
+
id: string,
|
|
3224
|
+
record: SessionRecord,
|
|
3225
|
+
adapter: AcpSdkAdapter,
|
|
3226
|
+
waiter: PromptWaiter,
|
|
3227
|
+
): Promise<void> {
|
|
3228
|
+
const ownsRecovery = (): boolean =>
|
|
3229
|
+
this.#sessions.get(id) === record &&
|
|
3230
|
+
record.adapter === adapter &&
|
|
3231
|
+
record.statusRecovery === waiter &&
|
|
3232
|
+
!promptWaiterRetired(record, waiter) &&
|
|
3233
|
+
!waiter.terminalReserved;
|
|
3234
|
+
// Keep the lookup selector's authority even if an ack arrives during the read.
|
|
3235
|
+
const acknowledgedAtLookup = waiter.acknowledged;
|
|
3236
|
+
let detail = "status query unavailable";
|
|
3237
|
+
let timer: NodeJS.Timeout | undefined;
|
|
3238
|
+
let timedOut = false;
|
|
3239
|
+
try {
|
|
3240
|
+
const deadline = Promise.withResolvers<never>();
|
|
3241
|
+
timer = setTimeout(() => {
|
|
3242
|
+
timedOut = true;
|
|
3243
|
+
deadline.reject(new Error("status query timeout"));
|
|
3244
|
+
}, 5_000);
|
|
3245
|
+
const refreshAttachment = async (): Promise<void> => {
|
|
3246
|
+
if (this.#sessions.get(id) !== record || record.adapter !== adapter) return;
|
|
3247
|
+
// A reconnect can revoke the adapter's opaque attachment before Router has
|
|
3248
|
+
// published its replacement. Force an authority pass first, then hand the
|
|
3249
|
+
// exact current capability back to the adapter before issuing the read-only
|
|
3250
|
+
// lookup. This pass does not replay the prompt mutation.
|
|
3251
|
+
await this.#router.reconcile({ waitForReplay: false });
|
|
3252
|
+
if (!ownsRecovery()) return;
|
|
3253
|
+
const refreshed = this.#router.attachment(id);
|
|
3254
|
+
if (!refreshed?.isCurrent())
|
|
3255
|
+
throw new AcpSdkAdapterError(
|
|
3256
|
+
"unavailable",
|
|
3257
|
+
"ACP session has no current Router attachment for recovery.",
|
|
3258
|
+
);
|
|
3259
|
+
if (refreshed !== record.attachment || !record.attachment.isCurrent()) {
|
|
3260
|
+
await adapter.attachmentReady(refreshed);
|
|
3261
|
+
record.attachment = refreshed;
|
|
3262
|
+
}
|
|
3263
|
+
};
|
|
3264
|
+
await Promise.race([refreshAttachment(), deadline.promise]);
|
|
3265
|
+
if (!ownsRecovery()) return;
|
|
3266
|
+
// Bound observation only: the mutation and the read are never replayed or aborted.
|
|
3267
|
+
const response = object(
|
|
3268
|
+
await Promise.race([
|
|
3269
|
+
adapter.query("turn.result", {
|
|
3270
|
+
kind: waiter.invocationKind,
|
|
3271
|
+
...(acknowledgedAtLookup ? waiter.correlation : { clientRef: waiter.clientRef }),
|
|
3272
|
+
}),
|
|
3273
|
+
deadline.promise,
|
|
3274
|
+
]),
|
|
3275
|
+
);
|
|
3276
|
+
if (!ownsRecovery()) return;
|
|
3277
|
+
const status = object(response?.result) ?? response;
|
|
3278
|
+
const correlation = strictCorrelationFrom(response, status);
|
|
3279
|
+
const outcome = status ? terminalOutcome(status) : undefined;
|
|
3280
|
+
const content = object(status?.content);
|
|
3281
|
+
const readableText =
|
|
3282
|
+
content?.version === 1 &&
|
|
3283
|
+
content.type === "text" &&
|
|
3284
|
+
typeof content.text === "string" &&
|
|
3285
|
+
content.text.trim().length > 0;
|
|
3286
|
+
const failure = object(status?.error);
|
|
3287
|
+
const conflictingIdentity = [response, status].some(
|
|
3288
|
+
value =>
|
|
3289
|
+
(value?.kind !== undefined && value.kind !== waiter.invocationKind) ||
|
|
3290
|
+
(value?.sessionId !== undefined && value.sessionId !== id) ||
|
|
3291
|
+
(value?.clientRef !== undefined && value.clientRef !== waiter.clientRef),
|
|
3292
|
+
);
|
|
3293
|
+
if (
|
|
3294
|
+
status?.kind !== waiter.invocationKind ||
|
|
3295
|
+
conflictingIdentity ||
|
|
3296
|
+
(!acknowledgedAtLookup && status.clientRef !== waiter.clientRef) ||
|
|
3297
|
+
!correlation ||
|
|
3298
|
+
!hasCompleteCorrelation(correlation) ||
|
|
3299
|
+
(waiter.acknowledged && !correlationsExactlyMatch(waiter.correlation, correlation)) ||
|
|
3300
|
+
this.#hasRetiredPromptCorrelation(id, record, correlation)
|
|
3301
|
+
) {
|
|
3302
|
+
detail = "status correlation is missing, retired, or mismatched";
|
|
3303
|
+
} else if (
|
|
3304
|
+
status.status === "failed" &&
|
|
3305
|
+
status.outcome === undefined &&
|
|
3306
|
+
(status.receiptState === "present" ||
|
|
3307
|
+
status.receiptState === "missing" ||
|
|
3308
|
+
status.receiptState === "unknown") &&
|
|
3309
|
+
typeof failure?.code === "string" &&
|
|
3310
|
+
/^[a-zA-Z0-9_.-]{1,64}$/.test(failure.code) &&
|
|
3311
|
+
typeof failure.message === "string" &&
|
|
3312
|
+
failure.message.trim().length > 0 &&
|
|
3313
|
+
failure.message.length <= 512
|
|
3314
|
+
) {
|
|
3315
|
+
waiter.correlation = correlation;
|
|
3316
|
+
waiter.acknowledged = true;
|
|
3317
|
+
record.busy = record.backgroundBusy;
|
|
3318
|
+
this.#advanceTerminalGeneration(record);
|
|
3319
|
+
void this.#rejectPrompt(record, id, waiter, new AcpSdkAdapterError(failure.code, failure.message));
|
|
3320
|
+
return;
|
|
3321
|
+
} else if (
|
|
3322
|
+
(status.status === "terminal_ok" &&
|
|
3323
|
+
outcome?.kind === "stopped" &&
|
|
3324
|
+
(status.receiptState === "present" || status.receiptState === "missing") &&
|
|
3325
|
+
(outcome.reason !== "end_turn" || (status.receiptState === "present" && readableText))) ||
|
|
3326
|
+
(status.status === "failed" &&
|
|
3327
|
+
outcome?.kind === "failed" &&
|
|
3328
|
+
(status.receiptState === "present" ||
|
|
3329
|
+
status.receiptState === "missing" ||
|
|
3330
|
+
status.receiptState === "unknown"))
|
|
3331
|
+
) {
|
|
3332
|
+
waiter.correlation = correlation;
|
|
3333
|
+
waiter.acknowledged = true;
|
|
3334
|
+
record.busy = record.backgroundBusy;
|
|
3335
|
+
this.#advanceTerminalGeneration(record);
|
|
3336
|
+
const settledOutcome =
|
|
3337
|
+
record.cancelRequested && outcome.kind === "stopped" && outcome.reason === "end_turn"
|
|
3338
|
+
? { ...outcome, reason: "cancelled" as const, provenance: "client_cancel" as const }
|
|
3339
|
+
: outcome;
|
|
3340
|
+
const publicationBarrier = record.frameTail;
|
|
3341
|
+
waiter.terminal = { outcome: settledOutcome, correlation };
|
|
3342
|
+
// Recovered final text is published on the async terminal tail below. Record it
|
|
3343
|
+
// before settling so a first-turn startup-readiness failure cannot be retried
|
|
3344
|
+
// after this retained answer has already been committed for publication.
|
|
3345
|
+
if (readableText) record.promptObservedAssistantOutput = true;
|
|
3346
|
+
this.#settlePrompt(id, record, waiter);
|
|
3347
|
+
this.#scheduleTerminalUpdates(
|
|
3348
|
+
id,
|
|
3349
|
+
adapter,
|
|
3350
|
+
record.publicationGeneration,
|
|
3351
|
+
{
|
|
3352
|
+
type: settledOutcome.kind === "failed" ? "agent_failed" : "agent_end",
|
|
3353
|
+
outcome: settledOutcome,
|
|
3354
|
+
finalText: readableText ? content.text : undefined,
|
|
3355
|
+
finalTextTruncated: content?.truncated === true,
|
|
3356
|
+
},
|
|
3357
|
+
waiter,
|
|
3358
|
+
publicationBarrier,
|
|
3359
|
+
);
|
|
3360
|
+
return;
|
|
3361
|
+
} else {
|
|
3362
|
+
detail = "status has no usable retained terminal outcome and receipt";
|
|
3363
|
+
}
|
|
3364
|
+
} catch {
|
|
3365
|
+
detail = timedOut ? "status query timed out after 5000ms" : "status query unavailable";
|
|
3366
|
+
} finally {
|
|
3367
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
3368
|
+
}
|
|
3369
|
+
if (!ownsRecovery()) return;
|
|
3370
|
+
await this.#failSession(
|
|
3371
|
+
id,
|
|
3372
|
+
adapter,
|
|
3373
|
+
new AcpSdkAdapterError(
|
|
3374
|
+
"terminal_uncertain",
|
|
3375
|
+
`ACP prompt outcome remains uncertain: ${detail}. No mutation was replayed. Inspect turn.result for kind=${waiter.invocationKind}, clientRef=${waiter.clientRef}, commandId=${waiter.correlation.commandId ?? "unknown"}, turnId=${waiter.correlation.turnId ?? "unknown"} before submitting more work.`,
|
|
3376
|
+
),
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3049
3380
|
async #recoverSessionAfterTransportFailureAsync(
|
|
3050
3381
|
id: string,
|
|
3051
3382
|
adapter: AcpSdkAdapter,
|
|
@@ -3093,13 +3424,15 @@ export class AcpAgent implements Agent {
|
|
|
3093
3424
|
if (attaching) await Promise.allSettled([attaching.task]);
|
|
3094
3425
|
// A CLI-primary host owns its own process lifecycle: release this ACP
|
|
3095
3426
|
// attachment locally and never ask the broker to close the session.
|
|
3096
|
-
|
|
3427
|
+
const closesHost = this.#ownedSessionIds.has(id);
|
|
3428
|
+
await this.#teardownSession(id, "closed", closesHost);
|
|
3097
3429
|
this.#knownSessionCwds.delete(id);
|
|
3098
3430
|
this.#ownedSessionIds.delete(id);
|
|
3099
3431
|
this.#knownSessionMcpServers.delete(id);
|
|
3100
3432
|
this.#knownSessionMetadata.delete(id);
|
|
3101
3433
|
this.#retiredPromptCorrelations.delete(id);
|
|
3102
|
-
|
|
3434
|
+
// Detaching locally cannot retire an outstanding host acknowledgement.
|
|
3435
|
+
if (closesHost) this.#retiredPromptAcknowledgements.delete(id);
|
|
3103
3436
|
return {};
|
|
3104
3437
|
} finally {
|
|
3105
3438
|
this.#finishTeardown(id);
|
|
@@ -3337,7 +3670,7 @@ export class AcpAgent implements Agent {
|
|
|
3337
3670
|
* running forever behind a dead session.
|
|
3338
3671
|
*/
|
|
3339
3672
|
#armPromptWatchdog(id: string, record: SessionRecord, waiter: PromptWaiter): void {
|
|
3340
|
-
if (waiter.settled) return;
|
|
3673
|
+
if (waiter.settled || record.statusRecovery === waiter) return;
|
|
3341
3674
|
waiter.cancelWatchdog?.();
|
|
3342
3675
|
// No frame can still arrive once the transport is known to be gone, so waiting out
|
|
3343
3676
|
// the full bound would only add dead time to an outcome that is already decided.
|
|
@@ -3400,7 +3733,13 @@ export class AcpAgent implements Agent {
|
|
|
3400
3733
|
* turn can no longer be observed, it does not cancel or tear down the session.
|
|
3401
3734
|
*/
|
|
3402
3735
|
async #expirePromptWatchdog(id: string, record: SessionRecord, waiter: PromptWaiter): Promise<void> {
|
|
3403
|
-
if (
|
|
3736
|
+
if (
|
|
3737
|
+
record.activePrompt !== waiter ||
|
|
3738
|
+
waiter.settled ||
|
|
3739
|
+
waiter.terminalReserved ||
|
|
3740
|
+
record.statusRecovery === waiter
|
|
3741
|
+
)
|
|
3742
|
+
return;
|
|
3404
3743
|
const silenceMs = Math.max(0, this.#promptWatchdogClock.now() - waiter.lastFrameAt);
|
|
3405
3744
|
// A prompt still in preflight has never reached the host, so "stopped producing frames"
|
|
3406
3745
|
// would point the reader at the wrong phase: nothing was ever dispatched to produce them.
|
|
@@ -3506,6 +3845,18 @@ export class AcpAgent implements Agent {
|
|
|
3506
3845
|
await this.#settleCancelledPrompt(id, record, waiter);
|
|
3507
3846
|
return;
|
|
3508
3847
|
}
|
|
3848
|
+
if (waiter.terminalReserved) return;
|
|
3849
|
+
if (record.statusRecovery === waiter || (waiter.dispatched && !waiter.acknowledged)) {
|
|
3850
|
+
await this.#failSession(
|
|
3851
|
+
id,
|
|
3852
|
+
adapter,
|
|
3853
|
+
new AcpSdkAdapterError(
|
|
3854
|
+
"terminal_uncertain",
|
|
3855
|
+
`Prompt owner connection changed before retained evidence was resolved. No mutation was replayed. Inspect turn.result for kind=${waiter.invocationKind}, clientRef=${waiter.clientRef}.`,
|
|
3856
|
+
),
|
|
3857
|
+
);
|
|
3858
|
+
return;
|
|
3859
|
+
}
|
|
3509
3860
|
record.activePrompt = undefined;
|
|
3510
3861
|
record.busy = record.backgroundBusy;
|
|
3511
3862
|
clearPromptWatchdog(waiter);
|
|
@@ -3796,13 +4147,15 @@ export class AcpAgent implements Agent {
|
|
|
3796
4147
|
publicationGeneration: number,
|
|
3797
4148
|
event: JsonObject,
|
|
3798
4149
|
promptOwner: PromptWaiter | undefined,
|
|
4150
|
+
publicationBarrier?: Promise<void>,
|
|
3799
4151
|
): void {
|
|
3800
4152
|
const failedTerminal = event.type === "agent_failed";
|
|
3801
4153
|
if (promptOwner) this.#flushFailureDiagnostics(id, promptOwner, adapter);
|
|
3802
|
-
let decorationStart = Promise.resolve();
|
|
4154
|
+
let decorationStart = publicationBarrier ?? Promise.resolve();
|
|
3803
4155
|
const finalText = typeof event.finalText === "string" ? event.finalText : "";
|
|
3804
4156
|
if (promptOwner && hasAcpFinalTextContent(finalText)) {
|
|
3805
4157
|
const finalTextTask = (async () => {
|
|
4158
|
+
if (publicationBarrier) await publicationBarrier;
|
|
3806
4159
|
await Bun.sleep(0);
|
|
3807
4160
|
const resolution = resolveAcpFinalText(promptOwner.emittedAssistantText, finalText);
|
|
3808
4161
|
if (resolution.kind === "emit") {
|
|
@@ -3814,7 +4167,9 @@ export class AcpAgent implements Agent {
|
|
|
3814
4167
|
update: {
|
|
3815
4168
|
sessionUpdate: "agent_message_chunk",
|
|
3816
4169
|
content: { type: "text", text: resolution.text },
|
|
3817
|
-
...(resolution.final.truncated
|
|
4170
|
+
...(resolution.final.truncated || event.finalTextTruncated === true
|
|
4171
|
+
? { _meta: { gjcFinalTextTruncated: true } }
|
|
4172
|
+
: {}),
|
|
3818
4173
|
},
|
|
3819
4174
|
},
|
|
3820
4175
|
adapter,
|
|
@@ -3829,6 +4184,24 @@ export class AcpAgent implements Agent {
|
|
|
3829
4184
|
finalLength: resolution.final.text.length,
|
|
3830
4185
|
});
|
|
3831
4186
|
}
|
|
4187
|
+
if (resolution.kind !== "emit" && event.finalTextTruncated === true) {
|
|
4188
|
+
// A retained prefix may already equal the streamed text, leaving no suffix for
|
|
4189
|
+
// `resolveAcpFinalText` to emit. Still publish a metadata-only chunk so the
|
|
4190
|
+
// caller can distinguish an incomplete retained result from a complete answer.
|
|
4191
|
+
await this.#publishSessionUpdate(
|
|
4192
|
+
id,
|
|
4193
|
+
{
|
|
4194
|
+
sessionId: id,
|
|
4195
|
+
update: {
|
|
4196
|
+
sessionUpdate: "agent_message_chunk",
|
|
4197
|
+
content: { type: "text", text: "" },
|
|
4198
|
+
_meta: { gjcFinalTextTruncated: true },
|
|
4199
|
+
},
|
|
4200
|
+
},
|
|
4201
|
+
adapter,
|
|
4202
|
+
publicationGeneration,
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
3832
4205
|
})();
|
|
3833
4206
|
let finalTextTail: Promise<void>;
|
|
3834
4207
|
finalTextTail = finalTextTask
|