@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
package/src/sdk/client/client.ts
CHANGED
|
@@ -67,6 +67,8 @@ export interface SdkClientOptions {
|
|
|
67
67
|
timeoutMs?: number;
|
|
68
68
|
/** Absolute wall-clock deadline shared by connect, hello, retry, and request work. */
|
|
69
69
|
deadline?: number;
|
|
70
|
+
/** Optional client capabilities sent in the protocol hello handshake. */
|
|
71
|
+
capabilities?: readonly string[];
|
|
70
72
|
|
|
71
73
|
reconnectAttempts?: number;
|
|
72
74
|
reconnectBackoffMs?: number;
|
|
@@ -288,6 +290,7 @@ export class SdkClient {
|
|
|
288
290
|
*/
|
|
289
291
|
readonly #closeGraceMs: number;
|
|
290
292
|
readonly #deadline?: number;
|
|
293
|
+
readonly #capabilities?: readonly string[];
|
|
291
294
|
#currentSocketRecord: Incarnation | null = null;
|
|
292
295
|
#opening: Cycle | null = null;
|
|
293
296
|
#cycleGeneration = 0;
|
|
@@ -309,6 +312,7 @@ export class SdkClient {
|
|
|
309
312
|
this.#closeGraceMs = Math.max(1, Math.min(this.#timeoutMs, 1_000));
|
|
310
313
|
this.#deadline =
|
|
311
314
|
typeof options.deadline === "number" && Number.isFinite(options.deadline) ? options.deadline : undefined;
|
|
315
|
+
this.#capabilities = options.capabilities;
|
|
312
316
|
|
|
313
317
|
this.#reconnectAttempts = options.reconnectAttempts ?? 3;
|
|
314
318
|
this.#reconnectBackoffMs = options.reconnectBackoffMs ?? 25;
|
|
@@ -831,6 +835,20 @@ export class SdkClient {
|
|
|
831
835
|
incarnation.resolveOpen = undefined;
|
|
832
836
|
incarnation.rejectOpen = undefined;
|
|
833
837
|
this.#beginHello(incarnation);
|
|
838
|
+
if (this.#capabilities !== undefined) {
|
|
839
|
+
try {
|
|
840
|
+
socket.send(
|
|
841
|
+
JSON.stringify({
|
|
842
|
+
type: "hello",
|
|
843
|
+
protocolVersion: 3,
|
|
844
|
+
capabilities: [...this.#capabilities],
|
|
845
|
+
}),
|
|
846
|
+
);
|
|
847
|
+
} catch (error) {
|
|
848
|
+
this.#onSocketFailure(incarnation, error as Event);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
834
852
|
const earlyHello = incarnation.earlyHello;
|
|
835
853
|
if (earlyHello) {
|
|
836
854
|
incarnation.earlyHello = undefined;
|
|
@@ -241,13 +241,13 @@ export async function fetchGuideManifestOnline(params: {
|
|
|
241
241
|
*/
|
|
242
242
|
const BUNDLED_GUIDE_MANIFEST_ID = "gajae-code-advisory-bundled";
|
|
243
243
|
const BUNDLED_GUIDE_SIGNATURE_HEX =
|
|
244
|
-
"
|
|
244
|
+
"62715b7716b30d3ea7b6ef41dfd7848c82dffed972a8729e4a5b8e3a9ee05d8cbcefeabc27b8796b1754622672a8fd2698a7ab638ca205c202f7479c34763c00";
|
|
245
245
|
|
|
246
246
|
const bundledGuideAdvisoryTexts: Readonly<Record<string, string>> = {
|
|
247
247
|
"getting-started":
|
|
248
248
|
"GJC ships a small set of signed advisory guides with the client so `gjc sdk guides list` and `gjc sdk guides show <guideId>` work offline on a fresh install. Guides are advisory text only: they are rendered for reading and never executed or applied as configuration. To receive newer guides, run `gjc sdk guides refresh --url <https manifest url>`. The manifest must come from the allowlisted HTTPS host and must be signed by a pinned Ed25519 key; a rejected refresh falls back to the last verified cache, then to the bundled seed, and any rejection is reported in the warnings list.",
|
|
249
249
|
"sdk/session-cli":
|
|
250
|
-
"`gjc sdk session` is the broker-bound command family for operating live GJC SDK sessions from the terminal: `list` enumerates managed sessions, `inspect` shows session details, `send` submits a prompt turn, `status` reports session health,
|
|
250
|
+
"`gjc sdk session` is the broker-bound command family for operating live GJC SDK sessions from the terminal: `list` enumerates managed sessions, `inspect` shows session details, `send` submits a prompt turn, `status` reports session health, `tail` follows the event stream, and `close` ends the current live host generation. The explicit `raw` hatch dispatches one SDK operation as `control`, `query`, or `global`. Authority resolves through the local broker and endpoint credentials are never rendered by the CLI.",
|
|
251
251
|
"troubleshooting/sdk-connection":
|
|
252
252
|
"When an SDK connection fails, first check that the session host is alive and healthy: `gjc sdk session status` reports readiness and liveness. If the broker is gone, restart it and re-list sessions; detached hosts are reaped after a bounded absence grace. Fetch-boundary failures (offline host, allowlisted URL violations, signature rejections) are reported as typed errors with exit code 1 so scripts can fail closed instead of silently serving unverified content.",
|
|
253
253
|
};
|
package/src/sdk/guides/cli.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAgentDir } from "@gajae-code/utils";
|
|
2
|
+
import { normalizePublicCommandFailure, PublicCommandFailure } from "../../cli/public-command-errors";
|
|
2
3
|
import { readGuideCache } from "./cache";
|
|
3
4
|
import { BUNDLED_GUIDE_MANIFESTS, GuideCatalog, guideFetchPolicy, isGuideFetchUrlAllowed } from "./catalog";
|
|
4
5
|
import { GUIDE_CLIENT_VERSION, GUIDE_PINNED_KEYS } from "./verify";
|
|
@@ -39,50 +40,20 @@ export interface SdkGuidesCliArgs {
|
|
|
39
40
|
|
|
40
41
|
const GUIDE_CLI_ACTIONS: readonly string[] = ["refresh", "list", "show", "status", "trust"];
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
readonly code: string,
|
|
45
|
-
message: string,
|
|
46
|
-
readonly exitCode: 1 | 2,
|
|
47
|
-
) {
|
|
48
|
-
super(message);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function writeJson(value: unknown): void {
|
|
53
|
-
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function usageFailure(message: string): SdkGuidesCliError {
|
|
57
|
-
return new SdkGuidesCliError("usage", message, 2);
|
|
43
|
+
function usageFailure(): PublicCommandFailure {
|
|
44
|
+
return new PublicCommandFailure({ kind: "usage", proof: "pre-effect" });
|
|
58
45
|
}
|
|
59
46
|
|
|
60
|
-
function operationalFailure(
|
|
61
|
-
return new
|
|
47
|
+
function operationalFailure(): PublicCommandFailure {
|
|
48
|
+
return new PublicCommandFailure({ kind: "operation_failed", proof: "unknown" });
|
|
62
49
|
}
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* exit 2; every verification/operational failure exits 1. Fail-closed codes
|
|
67
|
-
* from the verification pipeline map to exit 1 so scripts observe a hard
|
|
68
|
-
* failure on tamper, unknown key, rollback, expiry, or corrupt cache.
|
|
69
|
-
*/
|
|
70
|
-
function toCliError(error: unknown): SdkGuidesCliError {
|
|
71
|
-
if (error instanceof SdkGuidesCliError) return error;
|
|
72
|
-
const code = error instanceof Error && "code" in error ? String((error as { code: unknown }).code) : undefined;
|
|
73
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
-
switch (code) {
|
|
75
|
-
case "usage":
|
|
76
|
-
case "invalid_input":
|
|
77
|
-
case "fetch_forbidden":
|
|
78
|
-
return new SdkGuidesCliError(code ?? "operation_failed", message, 2);
|
|
79
|
-
default:
|
|
80
|
-
return operationalFailure(code ?? "operation_failed", message);
|
|
81
|
-
}
|
|
51
|
+
function writeJson(value: unknown): void {
|
|
52
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
82
53
|
}
|
|
83
54
|
|
|
84
|
-
function requireValue(value: string | undefined
|
|
85
|
-
if (value === undefined || value.length === 0) throw usageFailure(
|
|
55
|
+
function requireValue(value: string | undefined): string {
|
|
56
|
+
if (value === undefined || value.length === 0) throw usageFailure();
|
|
86
57
|
return value;
|
|
87
58
|
}
|
|
88
59
|
|
|
@@ -105,8 +76,8 @@ function manifestSummary(manifest: {
|
|
|
105
76
|
}
|
|
106
77
|
|
|
107
78
|
async function runRefresh(agentDir: string, args: SdkGuidesCliArgs): Promise<unknown> {
|
|
108
|
-
const url = requireValue(args.url
|
|
109
|
-
if (!isGuideFetchUrlAllowed(url)) throw usageFailure(
|
|
79
|
+
const url = requireValue(args.url);
|
|
80
|
+
if (!isGuideFetchUrlAllowed(url)) throw usageFailure();
|
|
110
81
|
const catalog = new GuideCatalog({
|
|
111
82
|
agentDir,
|
|
112
83
|
onlineUrl: url,
|
|
@@ -114,15 +85,9 @@ async function runRefresh(agentDir: string, args: SdkGuidesCliArgs): Promise<unk
|
|
|
114
85
|
fetchImpl: args.fetchImpl,
|
|
115
86
|
});
|
|
116
87
|
const result = await catalog.refresh();
|
|
117
|
-
if (!result.ok) throw operationalFailure(
|
|
88
|
+
if (!result.ok) throw operationalFailure();
|
|
118
89
|
const selection = result.value;
|
|
119
|
-
if (selection.source !== "online")
|
|
120
|
-
const reasons = selection.warnings.length > 0 ? ` ${selection.warnings.join("; ")}` : "";
|
|
121
|
-
throw operationalFailure(
|
|
122
|
-
"online_refresh_failed",
|
|
123
|
-
`Online refresh did not select an online source (selected ${selection.source} instead)${reasons}.`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
90
|
+
if (selection.source !== "online") throw operationalFailure();
|
|
126
91
|
return {
|
|
127
92
|
source: selection.source,
|
|
128
93
|
manifest: manifestSummary(selection.manifest),
|
|
@@ -134,7 +99,7 @@ async function runRefresh(agentDir: string, args: SdkGuidesCliArgs): Promise<unk
|
|
|
134
99
|
async function runList(agentDir: string): Promise<unknown> {
|
|
135
100
|
const catalog = new GuideCatalog({ agentDir });
|
|
136
101
|
const result = await catalog.load();
|
|
137
|
-
if (!result.ok) throw operationalFailure(
|
|
102
|
+
if (!result.ok) throw operationalFailure();
|
|
138
103
|
const selection = result.value;
|
|
139
104
|
return {
|
|
140
105
|
source: selection.source,
|
|
@@ -145,10 +110,10 @@ async function runList(agentDir: string): Promise<unknown> {
|
|
|
145
110
|
}
|
|
146
111
|
|
|
147
112
|
async function runShow(agentDir: string, args: SdkGuidesCliArgs): Promise<unknown> {
|
|
148
|
-
const guideId = requireValue(args.guideId
|
|
113
|
+
const guideId = requireValue(args.guideId);
|
|
149
114
|
const catalog = new GuideCatalog({ agentDir });
|
|
150
115
|
const result = await catalog.advisory(guideId);
|
|
151
|
-
if (!result.ok) throw operationalFailure(
|
|
116
|
+
if (!result.ok) throw operationalFailure();
|
|
152
117
|
return {
|
|
153
118
|
source: result.value.source,
|
|
154
119
|
guideId: result.value.guideId,
|
|
@@ -194,21 +159,16 @@ function runTrust(): unknown {
|
|
|
194
159
|
|
|
195
160
|
/**
|
|
196
161
|
* Runs the `gjc sdk guides` command family. Exported for command routing from
|
|
197
|
-
* `src/commands/sdk.ts` and for direct service use
|
|
198
|
-
*
|
|
199
|
-
* the process exit code.
|
|
162
|
+
* `src/commands/sdk.ts` and for direct service use. Only successful results are
|
|
163
|
+
* written here; typed failures belong to the outer public family writer.
|
|
200
164
|
*/
|
|
201
165
|
export async function runSdkGuidesCli(
|
|
202
166
|
args: SdkGuidesCliArgs,
|
|
203
167
|
writeOutput: (value: unknown) => void = writeJson,
|
|
204
|
-
setExitCode: (exitCode: 1 | 2) => void = exitCode => {
|
|
205
|
-
process.exitCode = exitCode;
|
|
206
|
-
},
|
|
207
168
|
): Promise<void> {
|
|
208
169
|
try {
|
|
209
170
|
const action = args.action;
|
|
210
|
-
if (action === undefined || !GUIDE_CLI_ACTIONS.includes(action))
|
|
211
|
-
throw usageFailure(`Expected one of: ${GUIDE_CLI_ACTIONS.join(", ")}.`);
|
|
171
|
+
if (action === undefined || !GUIDE_CLI_ACTIONS.includes(action)) throw usageFailure();
|
|
212
172
|
const agentDir = args.agentDir ?? getAgentDir();
|
|
213
173
|
switch (action) {
|
|
214
174
|
case "refresh":
|
|
@@ -228,8 +188,6 @@ export async function runSdkGuidesCli(
|
|
|
228
188
|
return;
|
|
229
189
|
}
|
|
230
190
|
} catch (error) {
|
|
231
|
-
|
|
232
|
-
writeOutput({ ok: false, error: { code: cliError.code, message: cliError.message } });
|
|
233
|
-
setExitCode(cliError.exitCode);
|
|
191
|
+
throw normalizePublicCommandFailure(error);
|
|
234
192
|
}
|
|
235
193
|
}
|
package/src/sdk/guides/verify.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface GuidePinnedKey {
|
|
|
21
21
|
/** DER-encoded SPKI public key, hex. */
|
|
22
22
|
spkiDerHex: string;
|
|
23
23
|
source: "bundled";
|
|
24
|
+
/** Optional retirement bound for old bundled signers. */
|
|
25
|
+
validUntil?: number;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -30,10 +32,20 @@ export interface GuidePinnedKey {
|
|
|
30
32
|
* Private key material is never shipped; manifests are signed out-of-band.
|
|
31
33
|
*/
|
|
32
34
|
const guidePinnedKeys: GuidePinnedKey[] = [
|
|
35
|
+
// Bundled advisory seed signer for the current offline catalog. The previous
|
|
36
|
+
// key remains trusted below until the current seed's 2036 expiry so already-
|
|
37
|
+
// cached manifests do not lose authority during a bundled-guide refresh.
|
|
38
|
+
{
|
|
39
|
+
keyId: "71efa9f212e39350e34f88aed5ab7242854e7e20ea91455e8861c973a6fc2eaf",
|
|
40
|
+
spkiDerHex: "302a300506032b657003210062c441fc5953cdc67d0c46f09b814dcb30de1465dcafb385092dbdcf9844cfeb",
|
|
41
|
+
source: "bundled",
|
|
42
|
+
validUntil: Date.UTC(2036, 0, 1),
|
|
43
|
+
},
|
|
33
44
|
{
|
|
34
45
|
keyId: "6c4b134ff9fb86a52d55cb6bb7c2fab938405b53b4148afc4249a2cb6f504bce",
|
|
35
46
|
spkiDerHex: "302a300506032b6570032100ef665d05c6795341dfc893866d8fe5be4b48891c0ed0d125940d7032de37723e",
|
|
36
47
|
source: "bundled",
|
|
48
|
+
validUntil: Date.UTC(2036, 0, 1),
|
|
37
49
|
},
|
|
38
50
|
];
|
|
39
51
|
export const GUIDE_PINNED_KEYS: readonly GuidePinnedKey[] = Object.freeze(
|
|
@@ -125,6 +137,8 @@ export function verifyGuideManifest(params: {
|
|
|
125
137
|
}
|
|
126
138
|
if (!valid)
|
|
127
139
|
return guideFailure("invalid_signature", "Detached signature does not match the canonical manifest bytes.");
|
|
140
|
+
if (pinned.validUntil !== undefined && now > pinned.validUntil)
|
|
141
|
+
return guideFailure("expired", `Guide signer ${pinned.keyId} expired before this manifest was read.`);
|
|
128
142
|
if (!Number.isSafeInteger(now) || now < manifest.issuedAt)
|
|
129
143
|
return guideFailure("not_yet_valid", "Guide manifest is not authoritative until its issuedAt instant.");
|
|
130
144
|
if (now > manifest.expiresAt)
|
package/src/sdk/host/host.ts
CHANGED
|
@@ -103,9 +103,18 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
|
|
|
103
103
|
* must see the same capability-gated event kinds on both legs, or live and
|
|
104
104
|
* replay delivery diverge for the same subscriber. */
|
|
105
105
|
export const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
|
|
106
|
+
/** Capability for non-replayable streamed turn content. */
|
|
107
|
+
export const TURN_STREAM_CAPABILITY = "turn_stream";
|
|
108
|
+
/** Capability used by notification adapters that observe a host without owning its work. */
|
|
109
|
+
export const SESSION_HOST_OBSERVER_CAPABILITY = "session_host_observer_v1";
|
|
106
110
|
export const CAP_GATED_FRAME_KINDS: ReadonlySet<string> = new Set(["tool_activity", "reasoning_summary"]);
|
|
107
111
|
const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
|
|
108
112
|
|
|
113
|
+
/** Keep live delivery and replay on one capability gate. */
|
|
114
|
+
export function canDeliverSdkEvent(kind: string, capabilities: ReadonlySet<string> | undefined): boolean {
|
|
115
|
+
return !CAP_GATED_FRAME_KINDS.has(kind) || capabilities?.has(TOOL_ACTIVITY_CAPABILITY) === true;
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
/** Safe, identifier-free explanations for every refused activation status. */
|
|
110
119
|
const ACTIVATION_MESSAGES: Record<
|
|
111
120
|
Exclude<SessionActivationOutcome, "activated" | "already"> | "session_mismatch",
|
|
@@ -210,6 +219,9 @@ export class SessionSdkHost {
|
|
|
210
219
|
#stopPromise?: Promise<"stopped">;
|
|
211
220
|
#unsubscribe?: () => void;
|
|
212
221
|
#registration?: { writer: BrokerIndexWriter; generation: number };
|
|
222
|
+
#reportedActivityState?: "active" | "idle";
|
|
223
|
+
/** Serializes durable activity transitions so a later state cannot be lost. */
|
|
224
|
+
#activityReportTail: Promise<void> = Promise.resolve();
|
|
213
225
|
/** The generation whose readiness signal has already been published. */
|
|
214
226
|
#readyGeneration?: number;
|
|
215
227
|
/** Serializes activation attempts so a concurrent pair cannot both publish. */
|
|
@@ -276,6 +288,7 @@ export class SessionSdkHost {
|
|
|
276
288
|
|
|
277
289
|
async start(): Promise<"started" | "already"> {
|
|
278
290
|
if (this.#started) return "already";
|
|
291
|
+
this.#reportedActivityState = undefined;
|
|
279
292
|
this.events.restart();
|
|
280
293
|
if (isAutoroutingInactive(this)) this.emitAutoroutingInactiveNotice();
|
|
281
294
|
if (this.#options.readiness !== "deferred") this.#publishReadiness();
|
|
@@ -298,6 +311,9 @@ export class SessionSdkHost {
|
|
|
298
311
|
stateRoot: this.#options.stateRoot,
|
|
299
312
|
endpointGeneration: this.events.generation,
|
|
300
313
|
});
|
|
314
|
+
void this.reportActivity("idle").catch(error =>
|
|
315
|
+
logger.warn(`sdk: initial idle activity checkpoint failed: ${String(error)}`),
|
|
316
|
+
);
|
|
301
317
|
return "started";
|
|
302
318
|
} catch (error) {
|
|
303
319
|
this.#unsubscribe?.();
|
|
@@ -364,7 +380,9 @@ export class SessionSdkHost {
|
|
|
364
380
|
return "activated";
|
|
365
381
|
}
|
|
366
382
|
|
|
367
|
-
async stop(
|
|
383
|
+
async stop(
|
|
384
|
+
options: { allowLockContention?: boolean; unregisterReason?: "detached_idle" } = {},
|
|
385
|
+
): Promise<"stopped" | "already"> {
|
|
368
386
|
if (this.#stopPromise) return this.#stopPromise;
|
|
369
387
|
if (!this.#started) return "already";
|
|
370
388
|
const stopPromise = this.#stopStartedHost(options);
|
|
@@ -376,7 +394,10 @@ export class SessionSdkHost {
|
|
|
376
394
|
}
|
|
377
395
|
}
|
|
378
396
|
|
|
379
|
-
async #stopStartedHost(options: {
|
|
397
|
+
async #stopStartedHost(options: {
|
|
398
|
+
allowLockContention?: boolean;
|
|
399
|
+
unregisterReason?: "detached_idle";
|
|
400
|
+
}): Promise<"stopped"> {
|
|
380
401
|
// Fence before the first await: everything after this point is teardown,
|
|
381
402
|
// and no in-flight activation may publish readiness across it.
|
|
382
403
|
this.#stopping = true;
|
|
@@ -388,6 +409,7 @@ export class SessionSdkHost {
|
|
|
388
409
|
sessionId: this.#options.sessionId,
|
|
389
410
|
stateRoot: this.#options.stateRoot,
|
|
390
411
|
endpointGeneration: this.events.generation,
|
|
412
|
+
...(options.unregisterReason === undefined ? {} : { reason: options.unregisterReason }),
|
|
391
413
|
});
|
|
392
414
|
} catch (error) {
|
|
393
415
|
// A live broker may hold the shared session-index lock beyond the
|
|
@@ -415,6 +437,30 @@ export class SessionSdkHost {
|
|
|
415
437
|
stateRoot: this.#options.stateRoot,
|
|
416
438
|
endpointGeneration: this.events.generation,
|
|
417
439
|
});
|
|
440
|
+
if (this.#started)
|
|
441
|
+
void this.reportActivity("idle").catch(error =>
|
|
442
|
+
logger.warn(`sdk: initial idle activity checkpoint failed: ${String(error)}`),
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** Persist the host's observable activity for broker/session-list consumers. */
|
|
447
|
+
async reportActivity(state: "active" | "idle", at = Date.now()): Promise<void> {
|
|
448
|
+
const registration = this.#registration;
|
|
449
|
+
if (!this.#started || this.#stopping || !registration?.writer.heartbeat) return;
|
|
450
|
+
if (!Number.isFinite(at)) at = Date.now();
|
|
451
|
+
const write = async (): Promise<void> => {
|
|
452
|
+
if (this.#reportedActivityState === state) return;
|
|
453
|
+
await registration.writer.heartbeat!({
|
|
454
|
+
sessionId: this.#options.sessionId,
|
|
455
|
+
stateRoot: this.#options.stateRoot,
|
|
456
|
+
endpointGeneration: this.events.generation,
|
|
457
|
+
activity: { state, at },
|
|
458
|
+
});
|
|
459
|
+
this.#reportedActivityState = state;
|
|
460
|
+
};
|
|
461
|
+
const report = this.#activityReportTail.then(write, write);
|
|
462
|
+
this.#activityReportTail = report.catch(() => undefined);
|
|
463
|
+
await report;
|
|
418
464
|
}
|
|
419
465
|
|
|
420
466
|
async #send(connectionId: string, frame: SdkFrame): Promise<"written" | "dropped"> {
|
|
@@ -563,9 +609,7 @@ export class SessionSdkHost {
|
|
|
563
609
|
const sinceSeq = rawSeq;
|
|
564
610
|
const replay = this.events.replay(sinceSeq, sinceGeneration);
|
|
565
611
|
const capabilities = this.#options.connectionCapabilities?.(connectionId) ?? EMPTY_CAPABILITIES;
|
|
566
|
-
const events = replay.events.filter(
|
|
567
|
-
event => !CAP_GATED_FRAME_KINDS.has(String(event.kind)) || capabilities.has(TOOL_ACTIVITY_CAPABILITY),
|
|
568
|
-
);
|
|
612
|
+
const events = replay.events.filter(event => canDeliverSdkEvent(String(event.kind), capabilities));
|
|
569
613
|
await this.#send(connectionId, {
|
|
570
614
|
type: "event_replay_result",
|
|
571
615
|
id,
|
|
@@ -19,6 +19,7 @@ import { createKindAwareReconciliation } from "../bus/kind-aware-reconciliation"
|
|
|
19
19
|
import { createPromptReconciliation } from "../bus/prompt-reconciliation";
|
|
20
20
|
import { createReconciliationStore } from "../bus/reconciliation-store";
|
|
21
21
|
import { BROKER_RUNTIME_ABORT_CAPABILITY_FIELD, setBrokerRuntimeAbortCapabilityForTest } from "./control/runtime-gate";
|
|
22
|
+
import { SESSION_HOST_OBSERVER_CAPABILITY, TURN_STREAM_CAPABILITY } from "./host";
|
|
22
23
|
import { CursorRegistry, QueryHandlers, type QueryResponse, RevisionStore } from "./query";
|
|
23
24
|
import {
|
|
24
25
|
createInvocationReconciliation,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
createSdkSurfaceFactory,
|
|
27
28
|
type SdkOnlyInvocationRecord,
|
|
28
29
|
type SdkOnlyReconciliationStore,
|
|
30
|
+
type SdkOnlyTerminalAbortSeams,
|
|
29
31
|
SessionSdkSessionRuntime,
|
|
30
32
|
type SessionSdkTransport,
|
|
31
33
|
} from "./session-runtime";
|
|
@@ -1260,6 +1262,26 @@ describe("SessionSdkSessionRuntime", () => {
|
|
|
1260
1262
|
);
|
|
1261
1263
|
await runtime.stop();
|
|
1262
1264
|
});
|
|
1265
|
+
|
|
1266
|
+
test("uses a custom capability provider for live delivery without transport negotiation hooks", async () => {
|
|
1267
|
+
const transport = memoryTransport();
|
|
1268
|
+
const runtime = new SessionSdkSessionRuntime({
|
|
1269
|
+
transport,
|
|
1270
|
+
connectionCapabilities: connectionId =>
|
|
1271
|
+
connectionId === "observer"
|
|
1272
|
+
? new Set([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY])
|
|
1273
|
+
: new Set(),
|
|
1274
|
+
});
|
|
1275
|
+
await runtime.start();
|
|
1276
|
+
transport.feed("observer", { type: "event_replay", id: "replay", sinceSeq: 0 });
|
|
1277
|
+
runtime.sendFrameToCapabilities([TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY], {
|
|
1278
|
+
type: "event",
|
|
1279
|
+
kind: "message_update",
|
|
1280
|
+
payload: {},
|
|
1281
|
+
});
|
|
1282
|
+
expect(transport.sent).toContainEqual({ type: "event", kind: "message_update", payload: {} });
|
|
1283
|
+
await runtime.stop();
|
|
1284
|
+
});
|
|
1263
1285
|
test("SDK-only host publishes one replayable bash_folded frame per fold and drops the subscription on shutdown", async () => {
|
|
1264
1286
|
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-bash-folded-"));
|
|
1265
1287
|
const handlers = new Map<string, (event: unknown, ctx: ExtensionContext) => Promise<void> | void>();
|
|
@@ -3784,6 +3806,8 @@ async function invocationHarness(
|
|
|
3784
3806
|
agentFailedWriteFailures?: number;
|
|
3785
3807
|
branch?: unknown[];
|
|
3786
3808
|
onInvocationCompletionReconciled?: (kind: string, correlation: { commandId: string; turnId: string }) => void;
|
|
3809
|
+
/** Override/extend the INTERNAL terminal-abort seams the runtime is threaded. */
|
|
3810
|
+
terminalAbortSeams?: Partial<SdkOnlyTerminalAbortSeams>;
|
|
3787
3811
|
},
|
|
3788
3812
|
): Promise<InvocationHarness> {
|
|
3789
3813
|
const waiters = new Map<string, (frame: ResponseFrame) => void>();
|
|
@@ -3812,14 +3836,15 @@ async function invocationHarness(
|
|
|
3812
3836
|
createSdkSessionRuntimeExtension(api, {
|
|
3813
3837
|
agentDir: cwd,
|
|
3814
3838
|
...(hooks.onLifecycleDrainTimeout ? { onLifecycleDrainTimeoutForTests: hooks.onLifecycleDrainTimeout } : {}),
|
|
3815
|
-
...(interceptorStore
|
|
3839
|
+
...(interceptorStore || hooks.terminalAbortSeams
|
|
3816
3840
|
? {
|
|
3817
3841
|
terminalAbortSeams: {
|
|
3818
|
-
getReconciliationStore: () => interceptorStore,
|
|
3819
3842
|
getTerminalTurnEpoch: () => undefined,
|
|
3820
3843
|
getActivePromptHandle: () => undefined,
|
|
3821
3844
|
cancelPendingPreflightForTerminalAbort: () => {},
|
|
3822
3845
|
abortPromptAndWaitWithTerminal: async () => ({ status: "settled", terminalScope: {} }),
|
|
3846
|
+
...(interceptorStore ? { getReconciliationStore: () => interceptorStore } : {}),
|
|
3847
|
+
...hooks.terminalAbortSeams,
|
|
3823
3848
|
},
|
|
3824
3849
|
}
|
|
3825
3850
|
: {}),
|
|
@@ -5800,6 +5825,113 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
5800
5825
|
while (Date.now() < budgetEndsAt && !ready(correlatedFrames(harness, correlation))) await Bun.sleep(5);
|
|
5801
5826
|
};
|
|
5802
5827
|
|
|
5828
|
+
test("an SDK-only prompt deadline publishes its terminal frames without aborting the run", async () => {
|
|
5829
|
+
// #5637 review finding 1. The bus route's deadline fences the run through
|
|
5830
|
+
// `abortPromptAndWaitWithTerminal`, which aborts the cancellation domain
|
|
5831
|
+
// BEFORE it waits for settlement — that is the mid-tool kill the boundary
|
|
5832
|
+
// wait exists to prevent, and it is why the bus consumes the ledger seam.
|
|
5833
|
+
//
|
|
5834
|
+
// This route is architecturally different: its `PromptDeadlineManager.onExpired`
|
|
5835
|
+
// publishes `agent_failed` + `agent_end` and cleans up lifecycle references,
|
|
5836
|
+
// and aborts NOTHING. That is precisely why no boundary wait is required
|
|
5837
|
+
// here, and it is the fact this case pins. If someone later wires an abort
|
|
5838
|
+
// into this path, the `abortPromptAndWaitWithTerminal` assertion below fails
|
|
5839
|
+
// loudly — and whoever does it must add a boundary wait, for which the
|
|
5840
|
+
// `pendingToolExecutions` seam is now threaded (both hosts expose one
|
|
5841
|
+
// contract; only the bus consumes it today).
|
|
5842
|
+
const cwd = await mkdtemp(path.join(os.tmpdir(), "gjc-sdk-only-deadline-no-abort-"));
|
|
5843
|
+
const abortCalls: Array<{ handle: string; graceMs: number }> = [];
|
|
5844
|
+
const pendingLedgerReads: string[] = [];
|
|
5845
|
+
try {
|
|
5846
|
+
const harness = await invocationHarness("sdk-only-deadline-no-abort", cwd, {
|
|
5847
|
+
settings: zeroProgressSettings,
|
|
5848
|
+
sendUserMessage: async (_content, options) => {
|
|
5849
|
+
await options?.onPreflightAcceptCommit?.();
|
|
5850
|
+
await neverSettlingPromise();
|
|
5851
|
+
},
|
|
5852
|
+
terminalAbortSeams: {
|
|
5853
|
+
getActivePromptHandle: () => "sdk-only-deadline-handle",
|
|
5854
|
+
abortPromptAndWaitWithTerminal: async (handle, seamOptions) => {
|
|
5855
|
+
abortCalls.push({ handle, graceMs: seamOptions.graceMs });
|
|
5856
|
+
return { status: "settled", terminalScope: {} };
|
|
5857
|
+
},
|
|
5858
|
+
// Threaded through `SdkOnlyTerminalAbortSeams` (change 1): the seam
|
|
5859
|
+
// must type-check and the extension must construct with it present.
|
|
5860
|
+
// Reads are recorded rather than asserted non-empty — nothing on this
|
|
5861
|
+
// route consults it yet, which is the honest state of the contract.
|
|
5862
|
+
pendingToolExecutions: handle => {
|
|
5863
|
+
pendingLedgerReads.push(handle);
|
|
5864
|
+
return ["sdk-only-mutating-tool"];
|
|
5865
|
+
},
|
|
5866
|
+
},
|
|
5867
|
+
});
|
|
5868
|
+
const accepted = await harness.control("turn.prompt", { text: "sdk-only deadline" });
|
|
5869
|
+
expect(accepted.ok).toBe(true);
|
|
5870
|
+
const correlation = {
|
|
5871
|
+
commandId: accepted.result?.commandId,
|
|
5872
|
+
turnId: accepted.result?.turnId,
|
|
5873
|
+
};
|
|
5874
|
+
await harness.emit("agent_start");
|
|
5875
|
+
// A dispatched tool is running when the deadline expires: its start has
|
|
5876
|
+
// been delivered and no end ever follows.
|
|
5877
|
+
await harness.emit("tool_execution_start", {
|
|
5878
|
+
type: "tool_execution_start",
|
|
5879
|
+
toolCallId: "sdk-only-mutating-tool",
|
|
5880
|
+
toolName: "apply_patch",
|
|
5881
|
+
args: {},
|
|
5882
|
+
});
|
|
5883
|
+
|
|
5884
|
+
expect(await settledStatus(harness, "turn.prompt_status", correlation)).toMatchObject({
|
|
5885
|
+
status: "failed",
|
|
5886
|
+
error: { code: "prompt_deadline_exceeded" },
|
|
5887
|
+
outcome: { kind: "failed", code: "prompt_deadline_exceeded", provenance: "deadline" },
|
|
5888
|
+
});
|
|
5889
|
+
await awaitCorrelatedFrames(
|
|
5890
|
+
harness,
|
|
5891
|
+
correlation,
|
|
5892
|
+
frames =>
|
|
5893
|
+
frames.some(frame => frame.kind === "agent_failed") && frames.some(frame => frame.kind === "agent_end"),
|
|
5894
|
+
);
|
|
5895
|
+
const frames = correlatedFrames(harness, correlation);
|
|
5896
|
+
expect(frames.filter(frame => frame.kind === "agent_failed")).toEqual([
|
|
5897
|
+
expect.objectContaining({
|
|
5898
|
+
payload: expect.objectContaining({
|
|
5899
|
+
error: expect.objectContaining({ code: "prompt_deadline_exceeded" }),
|
|
5900
|
+
}),
|
|
5901
|
+
}),
|
|
5902
|
+
]);
|
|
5903
|
+
expect(frames.filter(frame => frame.kind === "agent_end")).toEqual([
|
|
5904
|
+
expect.objectContaining({
|
|
5905
|
+
payload: expect.objectContaining({
|
|
5906
|
+
outcome: expect.objectContaining({
|
|
5907
|
+
kind: "failed",
|
|
5908
|
+
code: "prompt_deadline_exceeded",
|
|
5909
|
+
provenance: "deadline",
|
|
5910
|
+
}),
|
|
5911
|
+
}),
|
|
5912
|
+
}),
|
|
5913
|
+
]);
|
|
5914
|
+
|
|
5915
|
+
// THE point of the case: the terminal was published, and the run was never
|
|
5916
|
+
// aborted — so there is no mid-tool kill to prevent on this route.
|
|
5917
|
+
expect(abortCalls).toEqual([]);
|
|
5918
|
+
// A late real boundary for the still-"running" tool changes nothing.
|
|
5919
|
+
await harness.emit("tool_execution_end", {
|
|
5920
|
+
type: "tool_execution_end",
|
|
5921
|
+
toolCallId: "sdk-only-mutating-tool",
|
|
5922
|
+
toolName: "apply_patch",
|
|
5923
|
+
isError: false,
|
|
5924
|
+
});
|
|
5925
|
+
await Bun.sleep(50);
|
|
5926
|
+
expect(abortCalls).toEqual([]);
|
|
5927
|
+
expect(correlatedFrames(harness, correlation).filter(frame => frame.kind === "agent_end")).toHaveLength(1);
|
|
5928
|
+
await harness.stop();
|
|
5929
|
+
} finally {
|
|
5930
|
+
await Bun.sleep(50);
|
|
5931
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5932
|
+
}
|
|
5933
|
+
});
|
|
5934
|
+
|
|
5803
5935
|
test("the deadline/agent_end overlap publishes exactly one correlated terminal boundary", async () => {
|
|
5804
5936
|
// Review P1: the two terminal publishers genuinely overlap. The provider
|
|
5805
5937
|
// agent_end handler captures its transitions synchronously and then awaits
|
|
@@ -6085,6 +6217,10 @@ describe("accepted-control zero-execution bound (#4668)", () => {
|
|
|
6085
6217
|
const expiringIds = { commandId: expiring.result?.commandId, turnId: expiring.result?.turnId };
|
|
6086
6218
|
const leadingIds = { commandId: leading.result?.commandId, turnId: leading.result?.turnId };
|
|
6087
6219
|
await harness.emit("agent_start");
|
|
6220
|
+
await awaitCorrelatedFrames(harness, expiringIds, frames =>
|
|
6221
|
+
frames.some(frame => frame.kind === "agent_start"),
|
|
6222
|
+
);
|
|
6223
|
+
await awaitCorrelatedFrames(harness, leadingIds, frames => frames.some(frame => frame.kind === "agent_start"));
|
|
6088
6224
|
// Both must be in the drained batch: a lease that fired before the drain
|
|
6089
6225
|
// would leave the handler with nothing to park on and prove nothing.
|
|
6090
6226
|
expect({
|