@gajae-code/coding-agent 0.13.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -2
- package/README.md +2 -0
- package/bin/gajaessi.js +4 -0
- package/dist/types/async/job-manager.d.ts +55 -2
- package/dist/types/async/support.d.ts +14 -0
- package/dist/types/capability/mcp.d.ts +6 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/accounts-cli.d.ts +19 -0
- package/dist/types/cli/args.d.ts +3 -0
- package/dist/types/cli/config-cli.d.ts +6 -0
- package/dist/types/cli/crash-cli.d.ts +10 -0
- package/dist/types/cli/customize-doctor.d.ts +145 -0
- package/dist/types/cli/daemon-cli.d.ts +3 -1
- package/dist/types/cli/mcp-cli.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/root-flags.d.ts +12 -0
- package/dist/types/cli/setup-cli.d.ts +3 -1
- package/dist/types/cli/skills-cli.d.ts +2 -1
- package/dist/types/cli/update-cli.d.ts +19 -1
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/accounts.d.ts +40 -0
- package/dist/types/commands/crash.d.ts +24 -0
- package/dist/types/commands/customize.d.ts +22 -0
- package/dist/types/commands/daemon.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/commands/setup.d.ts +6 -0
- package/dist/types/commands/skills.d.ts +6 -1
- package/dist/types/commands/update.d.ts +7 -0
- package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
- package/dist/types/config/config-schema-version.d.ts +10 -0
- package/dist/types/config/file-lock.d.ts +18 -0
- package/dist/types/config/model-profile-activation.d.ts +3 -1
- package/dist/types/config/model-registry.d.ts +9 -1
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +70 -6
- package/dist/types/config/settings.d.ts +63 -0
- package/dist/types/config/skill-settings-defaults.d.ts +26 -0
- package/dist/types/config/task-delegation.d.ts +39 -0
- package/dist/types/config/update-channel.d.ts +10 -0
- package/dist/types/coordinator-mcp/server.d.ts +5 -1
- package/dist/types/crash/index-store.d.ts +87 -0
- package/dist/types/crash/nudge.d.ts +50 -0
- package/dist/types/crash/record-loader.d.ts +17 -0
- package/dist/types/crash/report.d.ts +134 -0
- package/dist/types/crash/sanitize.d.ts +25 -0
- package/dist/types/cursor.d.ts +13 -2
- package/dist/types/customization/import.d.ts +37 -0
- package/dist/types/customization/index.d.ts +4 -0
- package/dist/types/customization/inventory.d.ts +25 -0
- package/dist/types/customization/mutations.d.ts +32 -0
- package/dist/types/customization/types.d.ts +115 -0
- package/dist/types/daemon/control-types.d.ts +8 -1
- package/dist/types/daemon/operator-contract.d.ts +4 -4
- package/dist/types/discovery/claude.d.ts +39 -1
- package/dist/types/discovery/codex.d.ts +39 -1
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/mcp-compat.d.ts +30 -0
- package/dist/types/edit/renderer.d.ts +23 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -4
- package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
- package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
- package/dist/types/extensibility/hooks/loader.d.ts +19 -5
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
- package/dist/types/extensibility/skill-management.d.ts +107 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
- package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
- package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
- package/dist/types/hooks/events.d.ts +60 -0
- package/dist/types/hooks/normalize.d.ts +83 -0
- package/dist/types/hooks/skill-state.d.ts +9 -2
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/acp/abort-scope.d.ts +13 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -4
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
- package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
- package/dist/types/modes/components/customization/index.d.ts +1 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +8 -30
- package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
- package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
- package/dist/types/modes/components/oauth-selector.d.ts +10 -4
- package/dist/types/modes/components/pet-capability.d.ts +11 -25
- package/dist/types/modes/components/status-line/gh.d.ts +6 -2
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
- package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
- package/dist/types/runtime-mcp/config.d.ts +9 -0
- package/dist/types/runtime-mcp/manager.d.ts +26 -1
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
- package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
- package/dist/types/runtime-mcp/protocol.d.ts +278 -0
- package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
- package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
- package/dist/types/runtime-mcp/types.d.ts +102 -10
- package/dist/types/sdk/acp/adapter.d.ts +26 -5
- package/dist/types/sdk/broker/broker.d.ts +3 -55
- package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
- package/dist/types/sdk/broker/session-index.d.ts +14 -5
- package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
- package/dist/types/sdk/bus/config.d.ts +11 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
- package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
- package/dist/types/sdk/bus/index.d.ts +85 -99
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
- package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
- package/dist/types/sdk/bus/notification-service.d.ts +52 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
- package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
- package/dist/types/sdk/bus/session-control.d.ts +25 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
- package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
- package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
- package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
- package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
- package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
- package/dist/types/sdk/cli/rows.d.ts +2 -11
- package/dist/types/sdk/cli/session-cli.d.ts +15 -25
- package/dist/types/sdk/client/client.d.ts +97 -1
- package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +18 -5
- package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
- package/dist/types/sdk/host/host.d.ts +15 -1
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/host/session-runtime.d.ts +127 -7
- package/dist/types/sdk/host/types.d.ts +1 -1
- package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
- package/dist/types/sdk/index.d.ts +2 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
- package/dist/types/sdk/lifecycle/client.d.ts +59 -0
- package/dist/types/sdk/lifecycle/index.d.ts +3 -0
- package/dist/types/sdk/lifecycle/service.d.ts +232 -0
- package/dist/types/sdk/mcp/server.d.ts +9 -6
- package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
- package/dist/types/sdk/prompt-status.d.ts +10 -3
- package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
- package/dist/types/sdk/receipt-state.d.ts +17 -0
- package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
- package/dist/types/sdk/router/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +152 -0
- package/dist/types/sdk/session-list.d.ts +22 -0
- package/dist/types/sdk/session-reconnect.d.ts +43 -1
- package/dist/types/sdk/session.d.ts +34 -1
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
- package/dist/types/session/account-inventory.d.ts +86 -0
- package/dist/types/session/agent-session.d.ts +145 -6
- package/dist/types/session/agent-storage.d.ts +10 -0
- package/dist/types/session/coordinator-tool-label.d.ts +28 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
- package/dist/types/session/messages.d.ts +1 -1
- package/dist/types/session/secure-token-file.d.ts +3 -0
- package/dist/types/session/session-manager.d.ts +40 -1
- package/dist/types/session/startup-auth-config.d.ts +44 -0
- package/dist/types/session/terminal-abort.d.ts +378 -0
- package/dist/types/session/yield-queue.d.ts +7 -0
- package/dist/types/session-import/claude.d.ts +26 -0
- package/dist/types/session-import/codex.d.ts +88 -0
- package/dist/types/session-import/command.d.ts +35 -0
- package/dist/types/session-import/detect.d.ts +22 -0
- package/dist/types/session-import/index.d.ts +7 -0
- package/dist/types/session-import/provider-codex.d.ts +22 -0
- package/dist/types/session-import/provider-service.d.ts +38 -0
- package/dist/types/session-import/redact.d.ts +23 -0
- package/dist/types/session-import/service.d.ts +47 -0
- package/dist/types/session-import/types.d.ts +120 -0
- package/dist/types/setup/paseo/check.d.ts +16 -0
- package/dist/types/setup/paseo/install-saga.d.ts +115 -0
- package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
- package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
- package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
- package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
- package/dist/types/setup/paseo/provider-config.d.ts +35 -0
- package/dist/types/setup/paseo/remove.d.ts +14 -0
- package/dist/types/setup/paseo/result-types.d.ts +59 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
- package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
- package/dist/types/skill-state/active-state.d.ts +5 -1
- package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
- package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
- package/dist/types/slash-commands/types.d.ts +7 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/tools/ask-answer-registry.d.ts +6 -3
- package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
- package/dist/types/tools/bash.d.ts +2 -6
- package/dist/types/tools/browser/attach.d.ts +11 -0
- package/dist/types/tools/browser/launch.d.ts +20 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
- package/dist/types/tools/browser/render.d.ts +1 -0
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +9 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/monitor.d.ts +1 -1
- package/dist/types/tools/skill-discovery.d.ts +8 -1
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
- package/dist/types/utils/herdr-pane.d.ts +76 -0
- package/package.json +19 -9
- package/scripts/build-binary.ts +2 -1
- package/scripts/build-sdk-package-smoke.ts +2 -13
- package/scripts/capture-sticky-viewport-showcase.ts +0 -1
- package/scripts/compile-args.ts +2 -0
- package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
- package/scripts/generate-sdk-operation-inventory.ts +28 -0
- package/scripts/generate-telegram-baseline-manifest.ts +1 -0
- package/scripts/generate-tool-catalog.ts +6 -4
- package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
- package/src/async/job-manager.ts +352 -17
- package/src/async/support.ts +25 -0
- package/src/autoresearch/dashboard.ts +28 -13
- package/src/capability/index.ts +3 -1
- package/src/capability/mcp.ts +6 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/accounts-cli.ts +395 -0
- package/src/cli/args.ts +23 -0
- package/src/cli/auth-broker-cli.ts +136 -66
- package/src/cli/auth-gateway-cli.ts +94 -70
- package/src/cli/config-cli.ts +27 -2
- package/src/cli/crash-cli.ts +78 -0
- package/src/cli/customize-doctor.ts +1605 -0
- package/src/cli/daemon-cli.ts +12 -3
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/mcp-cli.ts +128 -60
- package/src/cli/notify-cli.ts +8 -14
- package/src/cli/root-flags.ts +8 -0
- package/src/cli/setup-cli.ts +145 -0
- package/src/cli/skills-cli.ts +38 -2
- package/src/cli/update-cli.ts +167 -12
- package/src/cli.ts +4 -0
- package/src/commands/accounts.ts +71 -0
- package/src/commands/crash.ts +35 -0
- package/src/commands/customize.ts +84 -0
- package/src/commands/daemon.ts +2 -2
- package/src/commands/deep-interview.ts +2 -0
- package/src/commands/launch.ts +1 -0
- package/src/commands/mcp.ts +12 -7
- package/src/commands/ralplan.ts +2 -0
- package/src/commands/sdk.ts +65 -32
- package/src/commands/setup.ts +5 -0
- package/src/commands/skills.ts +14 -4
- package/src/commands/ultragoal.ts +10 -1
- package/src/commands/update.ts +26 -15
- package/src/config/atomic-yaml-patch-worker.ts +57 -0
- package/src/config/atomic-yaml-patch.ts +769 -29
- package/src/config/config-schema-version.ts +10 -0
- package/src/config/file-lock.ts +70 -1
- package/src/config/mcp-schema.json +52 -14
- package/src/config/model-profile-activation.ts +20 -1
- package/src/config/model-profiles.ts +29 -19
- package/src/config/model-registry.ts +132 -21
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +76 -6
- package/src/config/settings.ts +4038 -199
- package/src/config/skill-settings-defaults.ts +41 -3
- package/src/config/task-delegation.ts +87 -0
- package/src/config/update-channel.ts +23 -0
- package/src/coordinator-mcp/server.ts +377 -308
- package/src/crash/index-store.ts +723 -0
- package/src/crash/nudge.ts +89 -0
- package/src/crash/record-loader.ts +149 -0
- package/src/crash/report.ts +551 -0
- package/src/crash/sanitize.ts +129 -0
- package/src/cursor.ts +36 -10
- package/src/customization/import.ts +946 -0
- package/src/customization/index.ts +4 -0
- package/src/customization/inventory.ts +335 -0
- package/src/customization/mutations.ts +141 -0
- package/src/customization/types.ts +223 -0
- package/src/daemon/control-types.ts +8 -1
- package/src/daemon/operator-contract.ts +6 -6
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
- package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
- package/src/discovery/claude-hooks.ts +38 -0
- package/src/discovery/claude.ts +142 -209
- package/src/discovery/codex-hooks.ts +38 -0
- package/src/discovery/codex.ts +132 -198
- package/src/discovery/helpers.ts +8 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/mcp-compat.ts +234 -0
- package/src/discovery/mcp-json.ts +6 -0
- package/src/edit/renderer.ts +40 -0
- package/src/edit/streaming.ts +4 -2
- package/src/exec/bash-executor.ts +24 -20
- package/src/export/html/index.ts +51 -2
- package/src/extensibility/extensions/loader.ts +9 -1
- package/src/extensibility/extensions/runner.ts +17 -4
- package/src/extensibility/extensions/types.ts +31 -6
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
- package/src/extensibility/gjc-plugins/installer.ts +9 -5
- package/src/extensibility/gjc-plugins/observability.ts +8 -2
- package/src/extensibility/gjc-plugins/registry.ts +13 -2
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
- package/src/extensibility/gjc-plugins/schema.ts +222 -39
- package/src/extensibility/hooks/loader.ts +154 -10
- package/src/extensibility/runtime-skill-discovery.ts +257 -60
- package/src/extensibility/skill-management.ts +368 -0
- package/src/extensibility/skills.ts +52 -16
- package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
- package/src/gjc-runtime/launch-worktree.ts +56 -2
- package/src/gjc-runtime/linux-proc.ts +7 -1
- package/src/gjc-runtime/ralplan-runtime.ts +100 -164
- package/src/gjc-runtime/session-state-lock.ts +992 -0
- package/src/gjc-runtime/session-state-sidecar.ts +644 -142
- package/src/gjc-runtime/state-writer.ts +39 -14
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
- package/src/gjc-runtime/workflow-settings.ts +596 -0
- package/src/goals/tools/goal-tool.ts +6 -1
- package/src/hooks/events.ts +291 -0
- package/src/hooks/native-skill-hook.ts +4 -0
- package/src/hooks/normalize.ts +369 -0
- package/src/hooks/skill-state.ts +165 -46
- package/src/internal-urls/docs-index.generated.ts +40 -32
- package/src/main.ts +32 -5
- package/src/migrate/mcp-mapper.ts +25 -2
- package/src/modes/acp/abort-scope.ts +29 -0
- package/src/modes/acp/acp-agent.ts +423 -98
- package/src/modes/components/assistant-message.ts +6 -1
- package/src/modes/components/customization/customization-dashboard.ts +443 -0
- package/src/modes/components/customization/import-wizard.ts +372 -0
- package/src/modes/components/customization/index.ts +1 -0
- package/src/modes/components/gajae-pet-widget.ts +539 -213
- package/src/modes/components/irc-sidebar.ts +11 -0
- package/src/modes/components/iterm-pet-transport.ts +585 -0
- package/src/modes/components/oauth-selector.ts +277 -86
- package/src/modes/components/pet-capability.ts +39 -40
- package/src/modes/components/settings-selector.ts +9 -0
- package/src/modes/components/status-line/gh.ts +45 -1
- package/src/modes/components/status-line/segments.ts +16 -1
- package/src/modes/components/tool-status-header.ts +13 -3
- package/src/modes/controllers/command-controller.ts +20 -8
- package/src/modes/controllers/extension-ui-controller.ts +56 -257
- package/src/modes/controllers/input-controller.ts +10 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
- package/src/modes/controllers/selector-controller.ts +177 -72
- package/src/modes/controllers/stt-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +88 -16
- package/src/modes/print-mode.ts +35 -6
- package/src/modes/runtime-init.ts +21 -68
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/injected-user-submission.ts +25 -0
- package/src/modes/utils/terminal-bell.ts +15 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/critic.md +1 -1
- package/src/prompts/system/rlm-research.md +1 -1
- package/src/prompts/system/system-prompt.md +0 -2
- package/src/prompts/tools/bash.md +7 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +6 -6
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/skill-discovery.md +4 -2
- package/src/runtime-mcp/client.ts +427 -23
- package/src/runtime-mcp/config.ts +37 -5
- package/src/runtime-mcp/manager-pool.test.ts +56 -0
- package/src/runtime-mcp/manager.ts +52 -2
- package/src/runtime-mcp/oauth-discovery.ts +13 -0
- package/src/runtime-mcp/oauth-flow.ts +46 -1
- package/src/runtime-mcp/pool-key.ts +5 -1
- package/src/runtime-mcp/pool.test.ts +9 -0
- package/src/runtime-mcp/protocol.test.ts +353 -0
- package/src/runtime-mcp/protocol.ts +624 -0
- package/src/runtime-mcp/scope-config.ts +78 -0
- package/src/runtime-mcp/tool-bridge.ts +44 -10
- package/src/runtime-mcp/transports/http.ts +84 -13
- package/src/runtime-mcp/types.ts +123 -12
- package/src/sdk/acp/adapter.ts +328 -97
- package/src/sdk/broker/broker.ts +160 -741
- package/src/sdk/broker/ensure.ts +9 -1
- package/src/sdk/broker/lifecycle.ts +510 -116
- package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
- package/src/sdk/broker/process-incarnation.ts +19 -0
- package/src/sdk/broker/session-index.ts +516 -117
- package/src/sdk/broker/startup-failure.ts +28 -5
- package/src/sdk/broker/transport.ts +1 -13
- package/src/sdk/bus/chat-daemon-cli.ts +4 -2
- package/src/sdk/bus/chat-daemon-control.ts +67 -6
- package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
- package/src/sdk/bus/config.ts +13 -0
- package/src/sdk/bus/conversation-store.ts +59 -171
- package/src/sdk/bus/daemon-paths.ts +20 -0
- package/src/sdk/bus/discord-conversation.ts +2 -0
- package/src/sdk/bus/discord-daemon.ts +524 -126
- package/src/sdk/bus/discord-live-provider.ts +206 -79
- package/src/sdk/bus/existing-thread-readiness.ts +4 -97
- package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
- package/src/sdk/bus/index.ts +2480 -456
- package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
- package/src/sdk/bus/lifecycle-commands.ts +2 -2
- package/src/sdk/bus/managed-daemon.ts +2 -145
- package/src/sdk/bus/notification-service.ts +196 -0
- package/src/sdk/bus/operator-runtime.ts +1 -1
- package/src/sdk/bus/prompt-reconciliation.ts +24 -15
- package/src/sdk/bus/reconciliation-store.ts +553 -24
- package/src/sdk/bus/session-control.ts +49 -3
- package/src/sdk/bus/slack-conversation.ts +4 -0
- package/src/sdk/bus/slack-daemon.ts +733 -134
- package/src/sdk/bus/slack-live-provider.ts +65 -15
- package/src/sdk/bus/slack-provider.ts +4 -0
- package/src/sdk/bus/slack-thread-binding.ts +9 -104
- package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
- package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
- package/src/sdk/bus/telegram-daemon-control.ts +3 -13
- package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
- package/src/sdk/bus/telegram-daemon.ts +2552 -2280
- package/src/sdk/bus/telegram-reference.ts +1 -226
- package/src/sdk/bus/telegram-setup.ts +2 -2
- package/src/sdk/bus/threaded-render.ts +1 -0
- package/src/sdk/bus/topic-registry.ts +279 -146
- package/src/sdk/cli/rows.ts +2 -35
- package/src/sdk/cli/session-cli.ts +776 -700
- package/src/sdk/client/client.ts +892 -1
- package/src/sdk/host/control/dispatch.ts +93 -58
- package/src/sdk/host/control/index.ts +1 -1
- package/src/sdk/host/control/operations.ts +18 -10
- package/src/sdk/host/control/runtime-gate.ts +40 -0
- package/src/sdk/host/host.ts +63 -12
- package/src/sdk/host/query/handlers.ts +66 -7
- package/src/sdk/host/reverse-leases.ts +2 -2
- package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
- package/src/sdk/host/session-runtime.test.ts +2787 -34
- package/src/sdk/host/session-runtime.ts +1489 -101
- package/src/sdk/host/surface-policy.ts +12 -8
- package/src/sdk/host/types.ts +1 -1
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
- package/src/sdk/host/websocket-transport.ts +85 -50
- package/src/sdk/index.ts +2 -1
- package/src/sdk/lifecycle/broker-client.ts +53 -0
- package/src/sdk/lifecycle/client.ts +234 -0
- package/src/sdk/lifecycle/index.ts +3 -0
- package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
- package/src/sdk/lifecycle/service.ts +720 -0
- package/src/sdk/mcp/server.ts +226 -188
- package/src/sdk/prompt-deadline-lease.ts +55 -0
- package/src/sdk/prompt-deadline-manager.ts +148 -0
- package/src/sdk/prompt-status.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +272 -129
- package/src/sdk/protocol/operation-registry.ts +71 -8
- package/src/sdk/receipt-state.ts +43 -0
- package/src/sdk/reconciliation-extensions.ts +14 -0
- package/src/sdk/router/index.ts +1 -0
- package/src/sdk/router/session-router.ts +1658 -0
- package/src/sdk/session-list.ts +93 -0
- package/src/sdk/session-reconnect.ts +45 -1
- package/src/sdk/session.ts +716 -120
- package/src/sdk/transport/serve-cli.ts +18 -34
- package/src/session/account-inventory.ts +603 -0
- package/src/session/agent-session.ts +2857 -582
- package/src/session/agent-storage.ts +35 -0
- package/src/session/blob-store.ts +32 -1
- package/src/session/coordinator-tool-label.ts +40 -0
- package/src/session/internal/managed-session-scope.ts +4 -0
- package/src/session/internal/managed-session-storage.ts +173 -12
- package/src/session/messages.ts +1 -1
- package/src/session/secure-token-file.ts +80 -0
- package/src/session/session-manager.ts +730 -58
- package/src/session/session-storage.ts +25 -16
- package/src/session/startup-auth-config.ts +297 -0
- package/src/session/terminal-abort.ts +1253 -0
- package/src/session/yield-queue.ts +57 -15
- package/src/session-import/claude.ts +334 -0
- package/src/session-import/codex.ts +1033 -0
- package/src/session-import/command.ts +77 -0
- package/src/session-import/detect.ts +133 -0
- package/src/session-import/index.ts +7 -0
- package/src/session-import/provider-codex.ts +327 -0
- package/src/session-import/provider-service.ts +457 -0
- package/src/session-import/redact.ts +128 -0
- package/src/session-import/service.ts +1264 -0
- package/src/session-import/types.ts +148 -0
- package/src/setup/paseo/check.ts +213 -0
- package/src/setup/paseo/install-saga.ts +293 -0
- package/src/setup/paseo/json-publisher.ts +263 -0
- package/src/setup/paseo/orchestration-preferences.ts +86 -0
- package/src/setup/paseo/paseo-ownership.ts +224 -0
- package/src/setup/paseo/paseo-setup.ts +253 -0
- package/src/setup/paseo/provider-config.ts +139 -0
- package/src/setup/paseo/remove.ts +183 -0
- package/src/setup/paseo/result-types.ts +79 -0
- package/src/setup/paseo/setup-deps.ts +175 -0
- package/src/setup/paseo/skills-bridge.ts +260 -0
- package/src/skill-state/active-state.ts +21 -11
- package/src/slash-commands/acp-builtins.ts +9 -1
- package/src/slash-commands/builtin-registry.ts +98 -16
- package/src/slash-commands/helpers/credential-switch.ts +143 -0
- package/src/slash-commands/helpers/mcp.ts +9 -3
- package/src/slash-commands/helpers/usage-report.ts +61 -56
- package/src/slash-commands/types.ts +7 -2
- package/src/task/executor.ts +30 -11
- package/src/task/index.ts +66 -7
- package/src/task/render.ts +23 -9
- package/src/tools/ask-answer-registry.ts +23 -7
- package/src/tools/ask.ts +8 -4
- package/src/tools/bash-sleep-advisory.ts +115 -0
- package/src/tools/bash.ts +98 -18
- package/src/tools/browser/attach.ts +71 -3
- package/src/tools/browser/launch.ts +86 -13
- package/src/tools/browser/profile-discovery.ts +57 -7
- package/src/tools/browser/render.ts +8 -1
- package/src/tools/browser.ts +130 -21
- package/src/tools/computer.enforcement.test.ts +1 -1
- package/src/tools/computer.ts +3 -2
- package/src/tools/cron.ts +12 -1
- package/src/tools/image-gen.ts +27 -16
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +33 -6
- package/src/tools/monitor.ts +41 -2
- package/src/tools/skill-discovery.ts +21 -29
- package/src/tools/subagent.ts +142 -24
- package/src/tools/tool-catalog.generated.ts +6 -6
- package/src/tools/ultragoal-ask-guard.ts +10 -3
- package/src/utils/herdr-pane.ts +345 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/scrapers/index.ts +6 -4
- package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
- package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
- package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
- package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
- package/dist/types/runtime/lazy-service.test.d.ts +0 -1
- package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
- package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
- package/dist/types/runtime-mcp/config.test.d.ts +0 -1
- package/dist/types/runtime-mcp/http.test.d.ts +0 -1
- package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
- package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
- package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
- package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
- package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
- package/dist/types/sdk/elevation/audit.d.ts +0 -93
- package/dist/types/sdk/elevation/capability.d.ts +0 -3
- package/dist/types/sdk/elevation/digest.d.ts +0 -72
- package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
- package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
- package/dist/types/sdk/elevation/owner.d.ts +0 -48
- package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
- package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
- package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
- package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
- package/dist/types/session/auth-broker-config.d.ts +0 -13
- package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
- package/dist/types/skills/skills.test.d.ts +0 -1
- package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
- package/dist/types/task/token-log.test.d.ts +0 -1
- package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
- package/dist/types/tools/descriptors.test.d.ts +0 -1
- package/scripts/g005-daemon-smoke.ts +0 -180
- package/scripts/g011-daemon-path-smoke.ts +0 -153
- package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
- package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
- package/src/sdk/bus/telegram-cli.ts +0 -212
- package/src/sdk/elevation/allowlist.ts +0 -54
- package/src/sdk/elevation/audit.ts +0 -208
- package/src/sdk/elevation/capability.ts +0 -41
- package/src/sdk/elevation/digest.ts +0 -129
- package/src/sdk/elevation/dispatch-receipt.ts +0 -184
- package/src/sdk/elevation/grant-ledger.ts +0 -1142
- package/src/sdk/elevation/owner.ts +0 -109
- package/src/session/auth-broker-config.ts +0 -109
- /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
- /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
- /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { randomBytes
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as fsSync from "node:fs";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { createInterface } from "node:readline/promises";
|
|
5
4
|
import { getAgentDir } from "@gajae-code/utils";
|
|
6
5
|
import { ensureBroker } from "../broker/ensure";
|
|
7
6
|
import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} from "../
|
|
16
|
-
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status
|
|
17
|
-
import { validateAdapterControl } from "../protocol/adapter-validation";
|
|
7
|
+
import { SdkClientError } from "../client";
|
|
8
|
+
import { createBrokerSessionLifecycleService } from "../lifecycle/broker-client";
|
|
9
|
+
import type {
|
|
10
|
+
SessionLifecycleMutationRequest,
|
|
11
|
+
SessionLifecycleOperation,
|
|
12
|
+
SessionLifecycleSavedSession,
|
|
13
|
+
SessionLifecycleSavedSessionIdentity,
|
|
14
|
+
} from "../lifecycle/service";
|
|
15
|
+
import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../prompt-status";
|
|
16
|
+
import { validateAdapterControl, validateAdapterSecretFields } from "../protocol/adapter-validation";
|
|
18
17
|
import { adapterDispositionError, findOperation, type OperationKind } from "../protocol/operation-registry";
|
|
18
|
+
import { type SessionAttachment, SessionRouter, SessionRouterError, type SessionRouterFrame } from "../router";
|
|
19
|
+
import { SessionListTraversalError, sessionListPageFromResponse, traverseSessionList } from "../session-list";
|
|
19
20
|
import {
|
|
20
21
|
type SdkCheckpointRecordV1,
|
|
21
22
|
type SdkRetentionGapV1,
|
|
@@ -24,54 +25,79 @@ import {
|
|
|
24
25
|
SESSION_ROWS_VERSION,
|
|
25
26
|
stripSecretFields,
|
|
26
27
|
toCheckpointRecordV1,
|
|
27
|
-
toOfflineSessionRowV1,
|
|
28
28
|
toRetentionGapV1,
|
|
29
29
|
toSessionRowV1,
|
|
30
30
|
toTailItemV1,
|
|
31
31
|
} from "./rows";
|
|
32
32
|
|
|
33
|
-
export type SdkSessionCliAction =
|
|
33
|
+
export type SdkSessionCliAction =
|
|
34
|
+
| "list"
|
|
35
|
+
| "inspect"
|
|
36
|
+
| "send"
|
|
37
|
+
| "status"
|
|
38
|
+
| "tail"
|
|
39
|
+
| "raw"
|
|
40
|
+
| "control"
|
|
41
|
+
| "query"
|
|
42
|
+
| "global";
|
|
34
43
|
export type SdkSessionCliRawKind = "control" | "query" | "global";
|
|
35
44
|
|
|
36
45
|
export interface SdkSessionCliArgs {
|
|
37
46
|
action?: string;
|
|
38
|
-
/** Raw dispatch kind when action is "raw" (control | query | global). */
|
|
39
47
|
rawAction?: string;
|
|
40
48
|
sessionId?: string;
|
|
41
|
-
/** Caller-chosen operation reference (ULID) for `send`; required target for `status`. */
|
|
42
49
|
opRef?: string;
|
|
43
50
|
operation?: string;
|
|
44
51
|
query?: string;
|
|
45
|
-
/** Convenience `send` body: forwarded as the `text` field of turn.prompt. */
|
|
46
52
|
text?: string;
|
|
47
53
|
jsonInput?: string;
|
|
48
54
|
jsonInputFile?: string;
|
|
49
55
|
jsonInputStdin?: boolean;
|
|
50
56
|
idempotencyKey?: string;
|
|
51
|
-
elevationRequestId?: string;
|
|
52
|
-
operationKind?: OperationKind;
|
|
53
57
|
confirm?: boolean;
|
|
54
|
-
/** Raw query continuation cursor, or a saved checkpoint token to resume `tail` from (re-minted per connection). */
|
|
55
58
|
cursor?: string;
|
|
56
|
-
showEndpointCredential?: boolean;
|
|
57
|
-
yes?: boolean;
|
|
58
|
-
/** `send --wait`: poll turn.prompt_status until terminal or the wait window elapses. */
|
|
59
59
|
wait?: boolean;
|
|
60
|
-
/** Bounded wait for `send --wait`, `status`, and live `tail` follow. */
|
|
61
60
|
timeoutMs?: number;
|
|
62
|
-
/** `tail --strict`: fail closed on retention gaps (exit 1 retention_gap). */
|
|
63
61
|
strict?: boolean;
|
|
64
|
-
/** `tail --until-idle`: exit once the observed event stream reaches a terminal turn state. */
|
|
65
62
|
untilIdle?: boolean;
|
|
66
|
-
/** `tail --all-events`: include every event-ring kind instead of the tail subset. */
|
|
67
63
|
allEvents?: boolean;
|
|
68
64
|
repo?: string;
|
|
69
65
|
agentDir?: string;
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
type JsonRecord = Record<string, unknown>;
|
|
69
|
+
type LifecycleMutationOperation = Exclude<SessionLifecycleOperation, "session.list">;
|
|
70
|
+
type TailExitReason = "idle" | "close";
|
|
71
|
+
export interface RetainedTranscriptTailReader {
|
|
72
|
+
readonly size: number;
|
|
73
|
+
readRange(start: number, end: number): Promise<Uint8Array>;
|
|
74
|
+
}
|
|
75
|
+
|
|
73
76
|
const SECRET_FIELD = /(?:secret|token|password|credential|authorization|api[_-]?key)/i;
|
|
74
|
-
const
|
|
77
|
+
const SDK_SESSION_CLI_LIFECYCLE_ACTOR = { id: "gjc-sdk-session-cli", namespace: "sdk:session-cli" } as const;
|
|
78
|
+
const ROUTER_START_TIMEOUT_MS = 10_000;
|
|
79
|
+
const ROUTER_STOP_TIMEOUT_MS = 5_000;
|
|
80
|
+
const TAIL_STATUS_POLL_MS = 100;
|
|
81
|
+
const TAIL_OFFLINE_MAX_ENTRIES = 200;
|
|
82
|
+
const TAIL_OFFLINE_SCAN_CHUNK_BYTES = 64 * 1024;
|
|
83
|
+
const TAIL_OFFLINE_MAX_SCAN_BYTES = 4 * 1024 * 1024;
|
|
84
|
+
const TAIL_OFFLINE_MAX_SCANNED_LINES = 4_096;
|
|
85
|
+
const TAIL_OFFLINE_MAX_LINE_BYTES = 256 * 1024;
|
|
86
|
+
const transcriptDecoder = new TextDecoder("utf-8", { fatal: true });
|
|
87
|
+
|
|
88
|
+
const TERMINAL_TURN_KINDS = new Set(["turn_end", "agent_end", "agent_failed"]);
|
|
89
|
+
const CLOSE_EVENT_KINDS = new Set(["session_closed", "session_terminated"]);
|
|
90
|
+
const DEFAULT_TAIL_KINDS = new Set([
|
|
91
|
+
"session_ready",
|
|
92
|
+
"session_prepared",
|
|
93
|
+
"session_closed",
|
|
94
|
+
"session_terminated",
|
|
95
|
+
"turn_start",
|
|
96
|
+
"turn_end",
|
|
97
|
+
"agent_start",
|
|
98
|
+
"agent_end",
|
|
99
|
+
"agent_failed",
|
|
100
|
+
]);
|
|
75
101
|
|
|
76
102
|
class SdkSessionCliError extends Error {
|
|
77
103
|
constructor(
|
|
@@ -84,17 +110,42 @@ class SdkSessionCliError extends Error {
|
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
|
|
113
|
+
class RetainedTranscriptTailError extends Error {
|
|
114
|
+
constructor(
|
|
115
|
+
readonly reason: "unavailable" | "corrupt" | "line_limit" | "scan_limit" | "changed",
|
|
116
|
+
message: string,
|
|
117
|
+
) {
|
|
118
|
+
super(message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
87
122
|
function writeJson(value: unknown): void {
|
|
88
123
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
89
124
|
}
|
|
90
125
|
|
|
126
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
127
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function object(value: unknown): JsonRecord | undefined {
|
|
131
|
+
return isRecord(value) ? value : undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function arrayOf(value: unknown): unknown[] {
|
|
135
|
+
return Array.isArray(value) ? value : [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resultObject(response: unknown): JsonRecord | undefined {
|
|
139
|
+
const record = object(response);
|
|
140
|
+
return object(record?.result);
|
|
141
|
+
}
|
|
142
|
+
|
|
91
143
|
function parseInput(raw: string | undefined, source: string): JsonRecord {
|
|
92
144
|
if (raw === undefined) return {};
|
|
93
145
|
try {
|
|
94
146
|
const value: unknown = JSON.parse(raw);
|
|
95
|
-
if (!value
|
|
96
|
-
|
|
97
|
-
return value as JsonRecord;
|
|
147
|
+
if (!isRecord(value)) throw new SdkSessionCliError("invalid_input", `${source} must be a JSON object.`, 2);
|
|
148
|
+
return value;
|
|
98
149
|
} catch (error) {
|
|
99
150
|
if (error instanceof SdkSessionCliError) throw error;
|
|
100
151
|
throw new SdkSessionCliError("invalid_json", `${source} must contain valid JSON.`, 2);
|
|
@@ -103,14 +154,10 @@ function parseInput(raw: string | undefined, source: string): JsonRecord {
|
|
|
103
154
|
|
|
104
155
|
function containsSecretField(value: unknown): boolean {
|
|
105
156
|
if (Array.isArray(value)) return value.some(containsSecretField);
|
|
106
|
-
if (!value
|
|
157
|
+
if (!isRecord(value)) return false;
|
|
107
158
|
return Object.entries(value).some(([key, nested]) => SECRET_FIELD.test(key) || containsSecretField(nested));
|
|
108
159
|
}
|
|
109
160
|
|
|
110
|
-
function object(value: unknown): JsonRecord | undefined {
|
|
111
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
161
|
async function inputFromArgs(args: SdkSessionCliArgs): Promise<JsonRecord> {
|
|
115
162
|
const sources = [
|
|
116
163
|
args.jsonInput !== undefined,
|
|
@@ -155,12 +202,13 @@ function isEndpointOperation(operation: string): boolean {
|
|
|
155
202
|
return operation === "session.get_endpoint";
|
|
156
203
|
}
|
|
157
204
|
|
|
158
|
-
function
|
|
205
|
+
function isLifecycleOperation(operation: string): operation is LifecycleMutationOperation {
|
|
159
206
|
return (
|
|
160
|
-
isEndpointOperation(operation) ||
|
|
161
207
|
operation === "session.create" ||
|
|
162
208
|
operation === "session.fork" ||
|
|
163
|
-
operation === "session.resume"
|
|
209
|
+
operation === "session.resume" ||
|
|
210
|
+
operation === "session.close" ||
|
|
211
|
+
operation === "session.delete"
|
|
164
212
|
);
|
|
165
213
|
}
|
|
166
214
|
|
|
@@ -171,156 +219,164 @@ function cliOperationError(kind: OperationKind, operation: string): { code: stri
|
|
|
171
219
|
if (row?.adapterDispositions.daemonCli === "prohibited")
|
|
172
220
|
return {
|
|
173
221
|
code: error.code,
|
|
174
|
-
message: `${operation} is unavailable through the
|
|
222
|
+
message: `${operation} is unavailable through the SDK session CLI.`,
|
|
175
223
|
};
|
|
176
224
|
return error;
|
|
177
225
|
}
|
|
178
226
|
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
operation === "session.fork" ||
|
|
183
|
-
operation === "session.resume" ||
|
|
184
|
-
operation === "session.close" ||
|
|
185
|
-
operation === "session.delete"
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function confirmEndpointCredentialOutput(): Promise<boolean> {
|
|
190
|
-
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
227
|
+
async function bounded<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
228
|
+
let timer: NodeJS.Timeout | undefined;
|
|
229
|
+
const timeout = Promise.withResolvers<never>();
|
|
191
230
|
try {
|
|
192
|
-
|
|
231
|
+
timer = setTimeout(() => timeout.reject(new SdkClientError("timeout", message)), timeoutMs);
|
|
232
|
+
return await Promise.race([promise, timeout.promise]);
|
|
193
233
|
} finally {
|
|
194
|
-
|
|
234
|
+
if (timer) clearTimeout(timer);
|
|
195
235
|
}
|
|
196
236
|
}
|
|
197
237
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!discovery) throw new SdkSessionCliError("broker_unavailable", "SDK broker discovery is unavailable.", 1);
|
|
202
|
-
return await SdkClient.connect(discovery.url, discovery.token);
|
|
238
|
+
function reportRouterCleanupFailure(error: unknown): void {
|
|
239
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
240
|
+
process.stderr.write(`SDK session Router cleanup failed: ${message}\n`);
|
|
203
241
|
}
|
|
204
242
|
|
|
205
|
-
async function
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
243
|
+
async function withRouter<T>(
|
|
244
|
+
agentDir: string,
|
|
245
|
+
action: (router: SessionRouter) => Promise<T>,
|
|
246
|
+
onFrame?: (attachment: SessionAttachment, frame: SessionRouterFrame) => void,
|
|
247
|
+
): Promise<T> {
|
|
248
|
+
const router = new SessionRouter({
|
|
249
|
+
agentDir,
|
|
250
|
+
...(onFrame === undefined ? {} : { deps: { onFrame } }),
|
|
251
|
+
});
|
|
252
|
+
let result!: T;
|
|
253
|
+
let actionFailed = false;
|
|
254
|
+
let actionError: unknown;
|
|
255
|
+
try {
|
|
256
|
+
await bounded(router.start(), ROUTER_START_TIMEOUT_MS, "SDK session Router startup timed out.");
|
|
257
|
+
result = await action(router);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
actionFailed = true;
|
|
260
|
+
actionError = error;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
await bounded(router.stop(), ROUTER_STOP_TIMEOUT_MS, "SDK session Router shutdown timed out.");
|
|
264
|
+
} catch (error) {
|
|
265
|
+
reportRouterCleanupFailure(error);
|
|
266
|
+
}
|
|
267
|
+
if (actionFailed) throw actionError;
|
|
268
|
+
return result;
|
|
209
269
|
}
|
|
210
270
|
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
271
|
+
function attachmentFor(router: SessionRouter, sessionId: string): SessionAttachment {
|
|
272
|
+
const attachment = router.attachment(sessionId);
|
|
273
|
+
if (!attachment)
|
|
274
|
+
throw new SdkSessionCliError(
|
|
275
|
+
"session_unavailable",
|
|
276
|
+
`SDK session ${sessionId} is unavailable through the session Router.`,
|
|
277
|
+
1,
|
|
278
|
+
);
|
|
279
|
+
return attachment;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function throwResponseFailure(response: unknown): void {
|
|
283
|
+
const record = object(response);
|
|
284
|
+
if (record?.ok !== false) return;
|
|
285
|
+
const failure = object(record.error);
|
|
286
|
+
throw new SdkSessionCliError(
|
|
287
|
+
typeof failure?.code === "string" ? failure.code : "unavailable",
|
|
288
|
+
typeof failure?.message === "string" ? failure.message : "SDK request failed.",
|
|
289
|
+
1,
|
|
290
|
+
failure,
|
|
219
291
|
);
|
|
220
292
|
}
|
|
221
293
|
|
|
222
|
-
async function paginatedSessionList(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
294
|
+
async function paginatedSessionList(
|
|
295
|
+
router: SessionRouter,
|
|
296
|
+
input: JsonRecord = {},
|
|
297
|
+
requestKey = `${SDK_SESSION_CLI_LIFECYCLE_ACTOR.namespace}:session.list`,
|
|
298
|
+
): Promise<unknown> {
|
|
299
|
+
try {
|
|
300
|
+
const pages = await traverseSessionList(
|
|
301
|
+
input,
|
|
302
|
+
async pageInput => {
|
|
303
|
+
const response = object(await router.listBrokerSessions(pageInput, requestKey));
|
|
304
|
+
if (response?.ok === false) {
|
|
305
|
+
const failure = object(response.error);
|
|
306
|
+
throw new SdkClientError(
|
|
307
|
+
typeof failure?.code === "string" ? failure.code : "broker_error",
|
|
308
|
+
typeof failure?.message === "string" ? failure.message : "session.list failed",
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return response;
|
|
312
|
+
},
|
|
313
|
+
response => sessionListPageFromResponse(response),
|
|
230
314
|
);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
typeof failure?.code === "string" ? failure.code : "broker_error",
|
|
236
|
-
typeof failure?.message === "string" ? failure.message : "session.list failed",
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
const listing = object(response?.result) ?? response;
|
|
240
|
-
if (listing) {
|
|
241
|
-
for (const [key, value] of Object.entries(listing)) {
|
|
315
|
+
const aggregate: JsonRecord = {};
|
|
316
|
+
const sessions: unknown[] = [];
|
|
317
|
+
for (const { page } of pages) {
|
|
318
|
+
for (const [key, value] of Object.entries(page)) {
|
|
242
319
|
if (key !== "sessions" && key !== "continuationCursor") aggregate[key] = value;
|
|
243
320
|
}
|
|
244
|
-
|
|
245
|
-
const nextCursor =
|
|
246
|
-
typeof listing.continuationCursor === "string" && listing.continuationCursor.length > 0
|
|
247
|
-
? listing.continuationCursor
|
|
248
|
-
: undefined;
|
|
249
|
-
if (nextCursor) {
|
|
250
|
-
cursor = nextCursor;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
321
|
+
sessions.push(...page.sessions);
|
|
253
322
|
}
|
|
254
323
|
const result = { ...aggregate, sessions };
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
throw new SdkClientError("protocol_error", "session.list exceeded the page budget.");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Broker-bound session endpoint resolution (C10): the broker validates the
|
|
262
|
-
* indexed session against its durable endpoint record and returns the
|
|
263
|
-
* credential, which the CLI uses to connect and never renders. Both clients
|
|
264
|
-
* (broker + session) are returned so callers close exactly what they opened.
|
|
265
|
-
*/
|
|
266
|
-
async function connectSessionViaBroker(
|
|
267
|
-
agentDir: string,
|
|
268
|
-
sessionId: string,
|
|
269
|
-
): Promise<{ broker: SdkClient; session: SdkClient; endpoint: unknown }> {
|
|
270
|
-
const broker = await connectBroker(agentDir);
|
|
271
|
-
try {
|
|
272
|
-
const response = await broker.global("session.get_endpoint", { sessionId });
|
|
273
|
-
const endpoint = (response as { result?: unknown } | undefined)?.result;
|
|
274
|
-
const url = (endpoint as { url?: unknown } | undefined)?.url;
|
|
275
|
-
const token = (endpoint as { token?: unknown } | undefined)?.token;
|
|
276
|
-
if (typeof url !== "string" || !url || typeof token !== "string")
|
|
277
|
-
throw new SdkSessionCliError(
|
|
278
|
-
"session_unavailable",
|
|
279
|
-
`SDK endpoint for session ${sessionId} is unavailable.`,
|
|
280
|
-
1,
|
|
281
|
-
);
|
|
282
|
-
const session = await SdkClient.connect(url, token);
|
|
283
|
-
return { broker, session, endpoint };
|
|
324
|
+
const firstResponse = pages[0]?.response;
|
|
325
|
+
return firstResponse && Object.hasOwn(firstResponse, "result") ? { ...firstResponse, result } : result;
|
|
284
326
|
} catch (error) {
|
|
285
|
-
|
|
327
|
+
if (error instanceof SessionListTraversalError) throw new SdkClientError("protocol_error", error.message);
|
|
286
328
|
throw error;
|
|
287
329
|
}
|
|
288
330
|
}
|
|
289
331
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
332
|
+
type SessionRows = {
|
|
333
|
+
indexSeq?: number;
|
|
334
|
+
warnings: unknown[];
|
|
335
|
+
sessions: SdkSessionRowV1[];
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
async function sessionRows(agentDir: string): Promise<SessionRows> {
|
|
339
|
+
await ensureBroker({ agentDir });
|
|
340
|
+
return await withRouter(agentDir, async router => {
|
|
341
|
+
const response = await paginatedSessionList(router);
|
|
342
|
+
const result = resultObject(response) ?? {};
|
|
343
|
+
let sessions: SdkSessionRowV1[];
|
|
344
|
+
try {
|
|
345
|
+
sessions = arrayOf(result.sessions).map(toSessionRowV1);
|
|
346
|
+
} catch {
|
|
347
|
+
throw new SdkClientError("protocol_error", "session.list returned a malformed session row.");
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
...(typeof result.indexSeq === "number" ? { indexSeq: result.indexSeq } : {}),
|
|
351
|
+
warnings: arrayOf(result.warnings),
|
|
352
|
+
sessions,
|
|
353
|
+
};
|
|
354
|
+
});
|
|
295
355
|
}
|
|
296
356
|
|
|
297
|
-
function
|
|
298
|
-
|
|
357
|
+
async function runList(agentDir: string): Promise<unknown> {
|
|
358
|
+
const listing = await sessionRows(agentDir);
|
|
359
|
+
return {
|
|
360
|
+
ok: true,
|
|
361
|
+
result: {
|
|
362
|
+
version: SESSION_ROWS_VERSION,
|
|
363
|
+
source: "broker",
|
|
364
|
+
...(listing.indexSeq === undefined ? {} : { indexSeq: listing.indexSeq }),
|
|
365
|
+
sessions: listing.sessions,
|
|
366
|
+
warnings: listing.warnings,
|
|
367
|
+
},
|
|
368
|
+
};
|
|
299
369
|
}
|
|
300
370
|
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
"session_terminated",
|
|
309
|
-
"turn_start",
|
|
310
|
-
"turn_end",
|
|
311
|
-
"agent_start",
|
|
312
|
-
"agent_end",
|
|
313
|
-
"agent_failed",
|
|
314
|
-
]);
|
|
315
|
-
const TAIL_OFFLINE_MAX_ENTRIES = 200;
|
|
316
|
-
const TAIL_STATUS_POLL_MS = 100;
|
|
371
|
+
async function runInspect(agentDir: string, sessionId: string): Promise<unknown> {
|
|
372
|
+
const listing = await sessionRows(agentDir);
|
|
373
|
+
const session = listing.sessions.find(candidate => candidate.sessionId === sessionId);
|
|
374
|
+
if (!session)
|
|
375
|
+
throw new SdkSessionCliError("session_unavailable", `Session ${sessionId} is not indexed by the broker.`, 1);
|
|
376
|
+
return { ok: true, result: { version: SESSION_ROWS_VERSION, source: "broker", session } };
|
|
377
|
+
}
|
|
317
378
|
|
|
318
|
-
/**
|
|
319
|
-
* Time-ordered operation reference (ULID): 48-bit millisecond timestamp plus
|
|
320
|
-
* 80 bits of randomness, Crockford base32, lowercase. The ref is used as the
|
|
321
|
-
* `turn.prompt` clientRef so `send`/`status` correlate without a broker round
|
|
322
|
-
* trip, and matches the plan's durable op-ref ledger shape (C7).
|
|
323
|
-
*/
|
|
379
|
+
/** Creates a lowercase ULID operation reference for prompt reconciliation. */
|
|
324
380
|
export function createOperationRef(now: number = Date.now()): string {
|
|
325
381
|
const crockford = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
326
382
|
let random = 0n;
|
|
@@ -332,8 +388,7 @@ export function createOperationRef(now: number = Date.now()): string {
|
|
|
332
388
|
}
|
|
333
389
|
|
|
334
390
|
function clientRefFromInput(input: JsonRecord): string | undefined {
|
|
335
|
-
|
|
336
|
-
return raw;
|
|
391
|
+
return typeof input.clientRef === "string" ? input.clientRef.trim() : undefined;
|
|
337
392
|
}
|
|
338
393
|
|
|
339
394
|
function assertClientRef(clientRef: string): void {
|
|
@@ -345,122 +400,131 @@ function assertClientRef(clientRef: string): void {
|
|
|
345
400
|
);
|
|
346
401
|
}
|
|
347
402
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
sessions,
|
|
366
|
-
warnings: arrayOf(result.warnings),
|
|
367
|
-
},
|
|
368
|
-
};
|
|
369
|
-
} finally {
|
|
370
|
-
await client.close();
|
|
371
|
-
}
|
|
403
|
+
async function requestControl(
|
|
404
|
+
router: SessionRouter,
|
|
405
|
+
sessionId: string,
|
|
406
|
+
operation: string,
|
|
407
|
+
input: JsonRecord,
|
|
408
|
+
args: SdkSessionCliArgs,
|
|
409
|
+
): Promise<JsonRecord> {
|
|
410
|
+
const attachment = attachmentFor(router, sessionId);
|
|
411
|
+
const response = await router.request(
|
|
412
|
+
sessionId,
|
|
413
|
+
{ type: "control_request", operation, input, confirm: args.confirm === true },
|
|
414
|
+
attachment.generation,
|
|
415
|
+
attachment,
|
|
416
|
+
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
417
|
+
);
|
|
418
|
+
throwResponseFailure(response);
|
|
419
|
+
return response;
|
|
372
420
|
}
|
|
373
421
|
|
|
374
|
-
async function
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (!endpoint)
|
|
397
|
-
throw new SdkSessionCliError(
|
|
398
|
-
"session_unavailable",
|
|
399
|
-
`SDK endpoint for session ${sessionId} is unavailable.`,
|
|
400
|
-
1,
|
|
401
|
-
);
|
|
402
|
-
return {
|
|
403
|
-
ok: true,
|
|
404
|
-
result: { version: SESSION_ROWS_VERSION, source: "offline", session: toOfflineSessionRowV1(endpoint) },
|
|
405
|
-
};
|
|
406
|
-
}
|
|
422
|
+
async function requestQuery(
|
|
423
|
+
router: SessionRouter,
|
|
424
|
+
sessionId: string,
|
|
425
|
+
query: string,
|
|
426
|
+
input: JsonRecord,
|
|
427
|
+
args: SdkSessionCliArgs,
|
|
428
|
+
): Promise<JsonRecord> {
|
|
429
|
+
const attachment = attachmentFor(router, sessionId);
|
|
430
|
+
const response = await router.request(
|
|
431
|
+
sessionId,
|
|
432
|
+
{
|
|
433
|
+
type: "query_request",
|
|
434
|
+
query,
|
|
435
|
+
input,
|
|
436
|
+
...(args.cursor === undefined ? {} : { cursor: args.cursor }),
|
|
437
|
+
},
|
|
438
|
+
attachment.generation,
|
|
439
|
+
attachment,
|
|
440
|
+
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
441
|
+
);
|
|
442
|
+
throwResponseFailure(response);
|
|
443
|
+
return response;
|
|
407
444
|
}
|
|
408
445
|
|
|
409
|
-
|
|
410
|
-
|
|
446
|
+
/** Exported for tests: each status poll must stay inside the caller's wait window. */
|
|
447
|
+
export async function waitForTerminalStatus(
|
|
448
|
+
router: SessionRouter,
|
|
449
|
+
sessionId: string,
|
|
411
450
|
clientRef: string,
|
|
412
451
|
timeoutMs: number | undefined,
|
|
413
452
|
): Promise<{ terminal: boolean; status: string; detail: unknown }> {
|
|
414
453
|
const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
|
|
454
|
+
let status = "unknown";
|
|
455
|
+
let detail: unknown = {};
|
|
415
456
|
for (;;) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
457
|
+
// Each poll is bounded by what is left of the caller's wait window, twice over.
|
|
458
|
+
// The request budget bounds the reply, and `bounded` bounds the whole poll: a
|
|
459
|
+
// disconnected transport reconnects before the reply timer is even installed,
|
|
460
|
+
// so without the outer bound the router's independent reconnect budget could
|
|
461
|
+
// still dispatch the query after the caller's window closed.
|
|
462
|
+
const remainingMs = deadline === undefined ? undefined : Math.max(1, deadline - Date.now());
|
|
463
|
+
const poll = requestQuery(
|
|
464
|
+
router,
|
|
465
|
+
sessionId,
|
|
466
|
+
"turn.result",
|
|
467
|
+
{ kind: "prompt", clientRef },
|
|
468
|
+
remainingMs === undefined ? {} : { timeoutMs: remainingMs },
|
|
469
|
+
);
|
|
470
|
+
let response: JsonRecord;
|
|
471
|
+
try {
|
|
472
|
+
response =
|
|
473
|
+
remainingMs === undefined
|
|
474
|
+
? await poll
|
|
475
|
+
: await bounded(poll, remainingMs, `Prompt ${clientRef} status poll exceeded the wait window.`);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// An elapsed wait window is the documented `wait_timeout` outcome with the
|
|
478
|
+
// last observed status, not a transport failure. Anything else — and any
|
|
479
|
+
// failure with window left — is a real error the caller must see.
|
|
480
|
+
if (deadline !== undefined && Date.now() >= deadline && isWaitWindowFailure(error))
|
|
481
|
+
return { terminal: false, status, detail };
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
const result = resultObject(response) ?? {};
|
|
485
|
+
status = typeof result.status === "string" ? result.status : "unknown";
|
|
486
|
+
detail = result;
|
|
419
487
|
if (status === "terminal_ok" || status === "failed") return { terminal: true, status, detail: result };
|
|
420
|
-
if (deadline !== undefined && Date.now() >= deadline) return { terminal: false, status, detail:
|
|
488
|
+
if (deadline !== undefined && Date.now() >= deadline) return { terminal: false, status, detail: result };
|
|
421
489
|
await Bun.sleep(TAIL_STATUS_POLL_MS);
|
|
422
490
|
}
|
|
423
491
|
}
|
|
424
492
|
|
|
493
|
+
/**
|
|
494
|
+
* True for the failures a poll produces when the wait window itself ends it:
|
|
495
|
+
* the outer bound, the request reply timer, and the uncertain send the transport
|
|
496
|
+
* reports when a request that was already on the wire is abandoned.
|
|
497
|
+
*/
|
|
498
|
+
function isWaitWindowFailure(error: unknown): boolean {
|
|
499
|
+
if (error instanceof SdkClientError) return error.code === "timeout" || error.code === "uncertain_after_send";
|
|
500
|
+
return error instanceof SdkSessionCliError && error.code === "timeout";
|
|
501
|
+
}
|
|
502
|
+
|
|
425
503
|
async function runSend(agentDir: string, sessionId: string, args: SdkSessionCliArgs): Promise<unknown> {
|
|
426
|
-
const generatedRef = createOperationRef();
|
|
427
504
|
const input = await inputFromArgs(args);
|
|
428
505
|
if (args.text !== undefined && Object.keys(input).length > 0)
|
|
429
506
|
throw new SdkSessionCliError("usage", "Use either --text or one JSON input source for the prompt, not both.", 2);
|
|
430
|
-
const promptInput: JsonRecord = args.text
|
|
507
|
+
const promptInput: JsonRecord = args.text === undefined ? { ...input } : { text: args.text };
|
|
431
508
|
const inputRef = clientRefFromInput(promptInput);
|
|
432
|
-
const clientRef = inputRef ?? args.opRef?.trim() ??
|
|
509
|
+
const clientRef = inputRef ?? args.opRef?.trim() ?? createOperationRef();
|
|
433
510
|
if (args.opRef !== undefined && inputRef !== undefined && inputRef !== args.opRef.trim())
|
|
434
511
|
throw new SdkSessionCliError("usage", "--op-ref must match the clientRef in the JSON input.", 2);
|
|
435
512
|
assertClientRef(clientRef);
|
|
436
513
|
if (inputRef === undefined) promptInput.clientRef = clientRef;
|
|
437
514
|
const invalid = validateAdapterControl("turn.prompt", promptInput);
|
|
438
515
|
if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
operation: "turn.prompt",
|
|
444
|
-
input: promptInput,
|
|
445
|
-
confirm: args.confirm === true,
|
|
446
|
-
});
|
|
447
|
-
if (isRecord(response) && response.ok === false) {
|
|
448
|
-
const responseError = isRecord(response.error) ? response.error : {};
|
|
449
|
-
throw new SdkSessionCliError(
|
|
450
|
-
typeof responseError.code === "string" ? responseError.code : "unavailable",
|
|
451
|
-
typeof responseError.message === "string" ? responseError.message : "Prompt dispatch failed.",
|
|
452
|
-
1,
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
const receipt = resultObject(response) ?? response;
|
|
516
|
+
await ensureBroker({ agentDir });
|
|
517
|
+
|
|
518
|
+
return await withRouter(agentDir, async router => {
|
|
519
|
+
const response = await requestControl(router, sessionId, "turn.prompt", promptInput, args);
|
|
456
520
|
const result: JsonRecord = {
|
|
457
521
|
version: SESSION_ROWS_VERSION,
|
|
458
522
|
operationRef: clientRef,
|
|
459
523
|
status: "accepted",
|
|
460
|
-
receipt,
|
|
524
|
+
receipt: resultObject(response) ?? response,
|
|
461
525
|
};
|
|
462
526
|
if (args.wait === true) {
|
|
463
|
-
const outcome = await waitForTerminalStatus(
|
|
527
|
+
const outcome = await waitForTerminalStatus(router, sessionId, clientRef, args.timeoutMs ?? 30_000);
|
|
464
528
|
if (!outcome.terminal)
|
|
465
529
|
throw new SdkSessionCliError(
|
|
466
530
|
"wait_timeout",
|
|
@@ -471,34 +535,32 @@ async function runSend(agentDir: string, sessionId: string, args: SdkSessionCliA
|
|
|
471
535
|
result.status = outcome.status;
|
|
472
536
|
result.statusDetail = outcome.detail;
|
|
473
537
|
}
|
|
474
|
-
return { ok: true, result
|
|
475
|
-
}
|
|
476
|
-
await session.close();
|
|
477
|
-
await broker.close();
|
|
478
|
-
}
|
|
538
|
+
return { ok: true, result };
|
|
539
|
+
});
|
|
479
540
|
}
|
|
480
541
|
|
|
481
|
-
async function runStatus(
|
|
542
|
+
async function runStatus(
|
|
543
|
+
agentDir: string,
|
|
544
|
+
sessionId: string,
|
|
545
|
+
opRef: string,
|
|
546
|
+
args: SdkSessionCliArgs,
|
|
547
|
+
): Promise<unknown> {
|
|
482
548
|
assertClientRef(opRef);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const response = await
|
|
486
|
-
const status = resultObject(response);
|
|
487
|
-
const
|
|
488
|
-
const raw = typeof record.status === "string" ? record.status : "unknown";
|
|
549
|
+
await ensureBroker({ agentDir });
|
|
550
|
+
return await withRouter(agentDir, async router => {
|
|
551
|
+
const response = await requestQuery(router, sessionId, "turn.result", { kind: "prompt", clientRef: opRef }, args);
|
|
552
|
+
const status = resultObject(response) ?? {};
|
|
553
|
+
const raw = typeof status.status === "string" ? status.status : "unknown";
|
|
489
554
|
return {
|
|
490
555
|
ok: true,
|
|
491
|
-
result:
|
|
556
|
+
result: {
|
|
492
557
|
version: SESSION_ROWS_VERSION,
|
|
493
558
|
operationRef: opRef,
|
|
494
|
-
status
|
|
559
|
+
status,
|
|
495
560
|
summary: { completed: raw === "terminal_ok" || raw === "failed" },
|
|
496
|
-
}
|
|
561
|
+
},
|
|
497
562
|
};
|
|
498
|
-
}
|
|
499
|
-
await session.close();
|
|
500
|
-
await broker.close();
|
|
501
|
-
}
|
|
563
|
+
});
|
|
502
564
|
}
|
|
503
565
|
|
|
504
566
|
type CheckpointExtraction = {
|
|
@@ -513,9 +575,6 @@ function extractCheckpoint(response: unknown): CheckpointExtraction {
|
|
|
513
575
|
const gap = toRetentionGapV1(result.gap);
|
|
514
576
|
if (gap !== undefined) return { gap };
|
|
515
577
|
const record = toCheckpointRecordV1(result.checkpoint ?? result);
|
|
516
|
-
// The Q30 result's `cursor`/`checkpointToken` is the signed checkpoint
|
|
517
|
-
// cursor: `cursor` is the legacy alias kept for wire compatibility, and the
|
|
518
|
-
// signed token is the resume artifact (`--cursor`) consumed by Q01.
|
|
519
578
|
const cursor =
|
|
520
579
|
typeof result.cursor === "string" && result.cursor
|
|
521
580
|
? result.cursor
|
|
@@ -526,8 +585,9 @@ function extractCheckpoint(response: unknown): CheckpointExtraction {
|
|
|
526
585
|
}
|
|
527
586
|
|
|
528
587
|
function extractTranscriptPage(response: unknown): { items: unknown[]; complete: boolean; cursor?: string } {
|
|
529
|
-
const
|
|
530
|
-
|
|
588
|
+
const record = object(response);
|
|
589
|
+
const page = object(record?.page) ?? object(resultObject(response)?.page);
|
|
590
|
+
if (!page) return { items: arrayOf(resultObject(response)?.items), complete: true };
|
|
531
591
|
return {
|
|
532
592
|
items: arrayOf(page.items),
|
|
533
593
|
complete: page.complete === true,
|
|
@@ -535,20 +595,9 @@ function extractTranscriptPage(response: unknown): { items: unknown[]; complete:
|
|
|
535
595
|
};
|
|
536
596
|
}
|
|
537
597
|
|
|
538
|
-
function eventKindOf(frame: Record<string, unknown>): string | undefined {
|
|
539
|
-
if (typeof frame.kind === "string" && frame.kind) return frame.kind;
|
|
540
|
-
if (typeof frame.name === "string" && frame.name) return frame.name;
|
|
541
|
-
return undefined;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
598
|
function tailItemKey(item: SdkTailItemV1): string {
|
|
545
|
-
// Ring events carry a synchronous generation/seq identity; keying on it (not
|
|
546
|
-
// kind+optional id) keeps repeated same-kind no-id events distinct and
|
|
547
|
-
// dedupes the replay/live overlap exactly once.
|
|
548
599
|
if (item.generation !== undefined && item.seq !== undefined)
|
|
549
600
|
return `${item.kind}\u0000${item.generation}\u0000${item.seq}`;
|
|
550
|
-
// Retained transcript entries have stable ids (or none); never silently drop
|
|
551
|
-
// distinct identity-less items, so fall back to the payload itself.
|
|
552
601
|
if (item.id !== undefined) return `${item.kind}\u0000${item.id}`;
|
|
553
602
|
return `${item.kind}\u0000${JSON.stringify(item.payload)}`;
|
|
554
603
|
}
|
|
@@ -569,7 +618,7 @@ function mergeTailItems(
|
|
|
569
618
|
|
|
570
619
|
function eventGapToRetentionGap(
|
|
571
620
|
value: unknown,
|
|
572
|
-
frame:
|
|
621
|
+
frame: JsonRecord,
|
|
573
622
|
record: SdkCheckpointRecordV1 | undefined,
|
|
574
623
|
): SdkRetentionGapV1 | undefined {
|
|
575
624
|
const existing = toRetentionGapV1(value);
|
|
@@ -592,325 +641,399 @@ function eventGapToRetentionGap(
|
|
|
592
641
|
return undefined;
|
|
593
642
|
}
|
|
594
643
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
...bufferedLiveEvents,
|
|
617
|
-
];
|
|
618
|
-
const gap = eventGapToRetentionGap(replayFrame.gap, replayFrame, record);
|
|
619
|
-
resolve({ events, ...(gap !== undefined ? { gap } : {}), closed: false });
|
|
620
|
-
});
|
|
621
|
-
const offFailed = session.onReconnectFailed(() => resolve({ events: [], closed: true }));
|
|
622
|
-
const timer = setTimeout(
|
|
623
|
-
() => reject(new SdkSessionCliError("tail_timeout", "Timed out waiting for the event replay response.", 1)),
|
|
624
|
-
timeoutMs,
|
|
644
|
+
function publicationSequence(publicationId: string): number | undefined {
|
|
645
|
+
const parts = publicationId.split(":");
|
|
646
|
+
const value = Number(parts.at(-1));
|
|
647
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function tailItemFromRouterFrame(frame: SessionRouterFrame): SdkTailItemV1 | undefined {
|
|
651
|
+
if (frame.publicationId === undefined || frame.name === undefined) return undefined;
|
|
652
|
+
const seq = publicationSequence(frame.publicationId);
|
|
653
|
+
return toTailItemV1(
|
|
654
|
+
{
|
|
655
|
+
kind: frame.name,
|
|
656
|
+
...(frame.generation === undefined ? {} : { generation: frame.generation }),
|
|
657
|
+
...(seq === undefined ? {} : { seq }),
|
|
658
|
+
payload: frame.body,
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
kind: frame.name,
|
|
662
|
+
...(frame.generation === undefined ? {} : { generation: frame.generation }),
|
|
663
|
+
...(seq === undefined ? {} : { seq }),
|
|
664
|
+
},
|
|
625
665
|
);
|
|
626
|
-
try {
|
|
627
|
-
session.send({
|
|
628
|
-
type: "event_replay",
|
|
629
|
-
id,
|
|
630
|
-
// Resume strictly after the checkpoint sequence: the checkpoint event
|
|
631
|
-
// was already observed, so re-emitting it would let a terminal
|
|
632
|
-
// checkpoint event satisfy --until-idle without any new turn.
|
|
633
|
-
...(record !== undefined ? { sinceGeneration: record.generation, sinceSeq: record.seq } : {}),
|
|
634
|
-
});
|
|
635
|
-
return await promise;
|
|
636
|
-
} finally {
|
|
637
|
-
clearTimeout(timer);
|
|
638
|
-
off();
|
|
639
|
-
offFailed();
|
|
640
|
-
}
|
|
641
666
|
}
|
|
642
667
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
});
|
|
667
|
-
const offClosed = session.onReconnectFailed(() => resolve({ reason: "reconnect_failed" }));
|
|
668
|
-
const offReconnect = session.onReconnect(() => resolve({ reason: "reconnect" }));
|
|
669
|
-
const timer = setTimeout(() => resolve({ reason: "timeout" }), timeoutMs);
|
|
668
|
+
function retainedTranscriptUnavailable(): RetainedTranscriptTailError {
|
|
669
|
+
return new RetainedTranscriptTailError("unavailable", "Retained transcript history is unavailable.");
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function retainedTranscriptOpenFlags(): number {
|
|
673
|
+
const noFollow = process.platform === "win32" ? 0 : fsSync.constants.O_NOFOLLOW;
|
|
674
|
+
if (process.platform !== "win32" && !noFollow) throw retainedTranscriptUnavailable();
|
|
675
|
+
return (
|
|
676
|
+
fsSync.constants.O_RDONLY | noFollow | (process.platform === "win32" ? 0 : (fsSync.constants.O_NONBLOCK ?? 0))
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function retainedTranscriptIdentityMismatch(): RetainedTranscriptTailError {
|
|
681
|
+
return new RetainedTranscriptTailError(
|
|
682
|
+
"changed",
|
|
683
|
+
"Retained transcript history no longer matches the Broker-selected identity; refusing to replay it.",
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function matchesRetainedTranscriptIdentity(
|
|
688
|
+
identity: SessionLifecycleSavedSessionIdentity,
|
|
689
|
+
descriptor: fsSync.BigIntStats,
|
|
690
|
+
): boolean {
|
|
670
691
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
692
|
+
return (
|
|
693
|
+
descriptor.isFile() &&
|
|
694
|
+
descriptor.dev === BigInt(identity.dev) &&
|
|
695
|
+
descriptor.ino === BigInt(identity.ino) &&
|
|
696
|
+
descriptor.nlink === BigInt(identity.nlink) &&
|
|
697
|
+
descriptor.size === BigInt(identity.size) &&
|
|
698
|
+
descriptor.mtimeMs === BigInt(identity.mtimeMs) &&
|
|
699
|
+
descriptor.mtimeNs === BigInt(identity.mtimeNs) &&
|
|
700
|
+
descriptor.ctimeNs === BigInt(identity.ctimeNs)
|
|
701
|
+
);
|
|
702
|
+
} catch {
|
|
703
|
+
return false;
|
|
678
704
|
}
|
|
679
705
|
}
|
|
680
706
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
707
|
+
function retainedTranscriptCorrupt(): RetainedTranscriptTailError {
|
|
708
|
+
return new RetainedTranscriptTailError(
|
|
709
|
+
"corrupt",
|
|
710
|
+
"Retained transcript history contains unparseable entries; refusing to replay corrupted history.",
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export async function scanRetainedTranscriptTail(reader: RetainedTranscriptTailReader): Promise<unknown[]> {
|
|
715
|
+
if (!Number.isSafeInteger(reader.size) || reader.size < 0) throw retainedTranscriptUnavailable();
|
|
716
|
+
const entries: unknown[] = [];
|
|
717
|
+
let position = reader.size;
|
|
718
|
+
let scannedBytes = 0;
|
|
719
|
+
let scannedLines = 0;
|
|
720
|
+
let trailingFragment = new Uint8Array();
|
|
721
|
+
while (position > 0 && entries.length < TAIL_OFFLINE_MAX_ENTRIES) {
|
|
722
|
+
const remainingBytes = TAIL_OFFLINE_MAX_SCAN_BYTES - scannedBytes;
|
|
723
|
+
if (remainingBytes <= 0)
|
|
724
|
+
throw new RetainedTranscriptTailError(
|
|
725
|
+
"scan_limit",
|
|
726
|
+
"Retained transcript history exceeds the bounded tail replay limit.",
|
|
698
727
|
);
|
|
728
|
+
const length = Math.min(TAIL_OFFLINE_SCAN_CHUNK_BYTES, position, remainingBytes);
|
|
729
|
+
const start = position - length;
|
|
730
|
+
let chunk: Uint8Array;
|
|
731
|
+
try {
|
|
732
|
+
chunk = await reader.readRange(start, position);
|
|
733
|
+
} catch {
|
|
734
|
+
throw retainedTranscriptUnavailable();
|
|
735
|
+
}
|
|
736
|
+
if (chunk.byteLength !== length) throw retainedTranscriptUnavailable();
|
|
737
|
+
scannedBytes += chunk.byteLength;
|
|
738
|
+
const combined = new Uint8Array(chunk.byteLength + trailingFragment.byteLength);
|
|
739
|
+
combined.set(chunk);
|
|
740
|
+
combined.set(trailingFragment, chunk.byteLength);
|
|
741
|
+
|
|
742
|
+
let complete = combined;
|
|
743
|
+
let partial = new Uint8Array();
|
|
744
|
+
if (start > 0) {
|
|
745
|
+
const firstNewline = combined.indexOf(0x0a);
|
|
746
|
+
if (firstNewline === -1) {
|
|
747
|
+
if (combined.byteLength > TAIL_OFFLINE_MAX_LINE_BYTES)
|
|
748
|
+
throw new RetainedTranscriptTailError(
|
|
749
|
+
"line_limit",
|
|
750
|
+
"Retained transcript history exceeds the bounded tail replay limit.",
|
|
751
|
+
);
|
|
752
|
+
trailingFragment = combined;
|
|
753
|
+
position = start;
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
partial = combined.subarray(0, firstNewline);
|
|
757
|
+
complete = combined.subarray(firstNewline + 1);
|
|
699
758
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const line =
|
|
706
|
-
|
|
759
|
+
|
|
760
|
+
let lineEnd = complete.byteLength;
|
|
761
|
+
while (lineEnd > 0 && entries.length < TAIL_OFFLINE_MAX_ENTRIES) {
|
|
762
|
+
const newline = complete.lastIndexOf(0x0a, lineEnd - 1);
|
|
763
|
+
const lineStart = newline < 0 ? 0 : newline + 1;
|
|
764
|
+
const line = complete.subarray(lineStart, lineEnd);
|
|
765
|
+
lineEnd = newline < 0 ? 0 : newline;
|
|
766
|
+
scannedLines++;
|
|
767
|
+
if (scannedLines > TAIL_OFFLINE_MAX_SCANNED_LINES)
|
|
768
|
+
throw new RetainedTranscriptTailError(
|
|
769
|
+
"scan_limit",
|
|
770
|
+
"Retained transcript history exceeds the bounded tail replay limit.",
|
|
771
|
+
);
|
|
772
|
+
if (line.byteLength === 0) continue;
|
|
773
|
+
if (line.byteLength > TAIL_OFFLINE_MAX_LINE_BYTES)
|
|
774
|
+
throw new RetainedTranscriptTailError(
|
|
775
|
+
"line_limit",
|
|
776
|
+
"Retained transcript history exceeds the bounded tail replay limit.",
|
|
777
|
+
);
|
|
707
778
|
try {
|
|
708
|
-
|
|
779
|
+
entries.push(JSON.parse(transcriptDecoder.decode(line)));
|
|
709
780
|
} catch {
|
|
710
|
-
|
|
781
|
+
throw retainedTranscriptCorrupt();
|
|
711
782
|
}
|
|
712
783
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
784
|
+
if (entries.length === TAIL_OFFLINE_MAX_ENTRIES) break;
|
|
785
|
+
if (start > 0) {
|
|
786
|
+
if (partial.byteLength > TAIL_OFFLINE_MAX_LINE_BYTES)
|
|
787
|
+
throw new RetainedTranscriptTailError(
|
|
788
|
+
"line_limit",
|
|
789
|
+
"Retained transcript history exceeds the bounded tail replay limit.",
|
|
790
|
+
);
|
|
791
|
+
trailingFragment = partial;
|
|
792
|
+
}
|
|
793
|
+
position = start;
|
|
794
|
+
}
|
|
795
|
+
return entries.reverse();
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
async function readRetainedTranscriptTail(savedSession: SessionLifecycleSavedSession): Promise<unknown[]> {
|
|
799
|
+
let descriptor: fs.FileHandle | undefined;
|
|
800
|
+
try {
|
|
801
|
+
descriptor = await fs.open(savedSession.path, retainedTranscriptOpenFlags());
|
|
802
|
+
const before = await descriptor.stat({ bigint: true });
|
|
803
|
+
if (!matchesRetainedTranscriptIdentity(savedSession.identity, before)) throw retainedTranscriptIdentityMismatch();
|
|
804
|
+
if (before.size > BigInt(Number.MAX_SAFE_INTEGER)) throw retainedTranscriptUnavailable();
|
|
805
|
+
// Bind every range read to the opened descriptor so a later pathname replacement
|
|
806
|
+
// cannot change the retained history selected by the lifecycle lookup.
|
|
807
|
+
const file = Bun.file(descriptor.fd);
|
|
808
|
+
const entries = await scanRetainedTranscriptTail({
|
|
809
|
+
size: Number(before.size),
|
|
810
|
+
readRange: async (start, end) => new Uint8Array(await file.slice(start, end).arrayBuffer()),
|
|
811
|
+
});
|
|
812
|
+
const after = await descriptor.stat({ bigint: true });
|
|
813
|
+
if (
|
|
814
|
+
!matchesRetainedTranscriptIdentity(savedSession.identity, after) ||
|
|
815
|
+
before.dev !== after.dev ||
|
|
816
|
+
before.ino !== after.ino ||
|
|
817
|
+
before.size !== after.size ||
|
|
818
|
+
before.mtimeNs !== after.mtimeNs ||
|
|
819
|
+
before.ctimeNs !== after.ctimeNs
|
|
820
|
+
)
|
|
821
|
+
throw new RetainedTranscriptTailError(
|
|
822
|
+
"changed",
|
|
823
|
+
"Retained transcript history changed while reading; refusing to replay it.",
|
|
722
824
|
);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
result: {
|
|
728
|
-
version: SESSION_ROWS_VERSION,
|
|
729
|
-
source: "offline",
|
|
730
|
-
session: row,
|
|
731
|
-
items,
|
|
732
|
-
terminal: true,
|
|
733
|
-
},
|
|
734
|
-
};
|
|
825
|
+
return entries;
|
|
826
|
+
} catch (error) {
|
|
827
|
+
if (error instanceof RetainedTranscriptTailError) throw error;
|
|
828
|
+
throw retainedTranscriptUnavailable();
|
|
735
829
|
} finally {
|
|
736
|
-
await
|
|
830
|
+
if (descriptor !== undefined) await descriptor.close().catch(() => {});
|
|
737
831
|
}
|
|
738
832
|
}
|
|
739
833
|
|
|
740
|
-
|
|
834
|
+
async function offlineTailReplay(
|
|
741
835
|
repo: string,
|
|
742
836
|
agentDir: string,
|
|
743
837
|
sessionId: string,
|
|
744
|
-
|
|
838
|
+
row: SdkSessionRowV1,
|
|
745
839
|
): Promise<unknown> {
|
|
746
|
-
const
|
|
747
|
-
|
|
840
|
+
const lifecycle = createBrokerSessionLifecycleService(agentDir);
|
|
841
|
+
const outcome = await lifecycle.list({
|
|
842
|
+
actor: SDK_SESSION_CLI_LIFECYCLE_ACTOR,
|
|
843
|
+
capability: "session.list",
|
|
844
|
+
target: { cwd: repo, resolveSessionId: sessionId },
|
|
845
|
+
});
|
|
846
|
+
if (!outcome.ok)
|
|
847
|
+
throw new SdkSessionCliError(outcome.error.code, outcome.error.message, 1, { certainty: outcome.certainty });
|
|
848
|
+
const savedSession = outcome.result.savedSession;
|
|
849
|
+
if (!savedSession)
|
|
850
|
+
throw new SdkSessionCliError(
|
|
851
|
+
"session_unavailable",
|
|
852
|
+
`Session ${sessionId} is stopped and has no retained transcript replay.`,
|
|
853
|
+
1,
|
|
854
|
+
);
|
|
855
|
+
let entries: unknown[];
|
|
748
856
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
857
|
+
entries = await readRetainedTranscriptTail(savedSession);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
const retained = error instanceof RetainedTranscriptTailError ? error : retainedTranscriptUnavailable();
|
|
860
|
+
throw new SdkSessionCliError("retention_gap", retained.message, 1, {
|
|
861
|
+
code: "retention_gap",
|
|
862
|
+
reason: retained.reason,
|
|
863
|
+
});
|
|
755
864
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
865
|
+
return {
|
|
866
|
+
ok: true,
|
|
867
|
+
result: {
|
|
868
|
+
version: SESSION_ROWS_VERSION,
|
|
869
|
+
source: "offline",
|
|
870
|
+
session: row,
|
|
871
|
+
items: entries.map((entry, index) => toTailItemV1(entry, { kind: "transcript", seq: index })),
|
|
872
|
+
terminal: true,
|
|
873
|
+
},
|
|
874
|
+
};
|
|
875
|
+
}
|
|
760
876
|
|
|
877
|
+
async function runLiveTail(
|
|
878
|
+
agentDir: string,
|
|
879
|
+
sessionId: string,
|
|
880
|
+
row: SdkSessionRowV1,
|
|
881
|
+
args: SdkSessionCliArgs,
|
|
882
|
+
): Promise<unknown> {
|
|
761
883
|
const include = (kind: string): boolean =>
|
|
762
884
|
kind === "transcript" || args.allEvents === true || DEFAULT_TAIL_KINDS.has(kind);
|
|
763
885
|
const items: SdkTailItemV1[] = [];
|
|
764
886
|
const seen = new Set<string>();
|
|
765
887
|
let checkpoint: SdkCheckpointRecordV1 | undefined;
|
|
766
888
|
let gap: SdkRetentionGapV1 | undefined;
|
|
767
|
-
|
|
889
|
+
let liveReason: TailExitReason | undefined;
|
|
890
|
+
let resolveLive: ((reason: TailExitReason) => void) | undefined;
|
|
768
891
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
if (row.live === false && brokerAbsent(error) === false)
|
|
778
|
-
return await offlineTailReplay(repo, agentDir, sessionId, row);
|
|
779
|
-
throw error;
|
|
892
|
+
const recordLiveFrame = (attachment: SessionAttachment, frame: SessionRouterFrame): void => {
|
|
893
|
+
if (attachment.sessionId !== sessionId) return;
|
|
894
|
+
const item = tailItemFromRouterFrame(frame);
|
|
895
|
+
if (!item) return;
|
|
896
|
+
mergeTailItems(items, seen, [item], include);
|
|
897
|
+
if (args.untilIdle === true && TERMINAL_TURN_KINDS.has(item.kind)) {
|
|
898
|
+
liveReason = "idle";
|
|
899
|
+
resolveLive?.("idle");
|
|
780
900
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
// connection-owned pin; direct cross-connection cursor consumption remains rejected.
|
|
785
|
-
const checkpointResponse = await session.query(
|
|
786
|
-
"session.checkpoint",
|
|
787
|
-
args.cursor === undefined ? {} : { checkpointToken: args.cursor },
|
|
788
|
-
);
|
|
789
|
-
const extraction = extractCheckpoint(checkpointResponse);
|
|
790
|
-
checkpoint = extraction.record;
|
|
791
|
-
gap = extraction.gap;
|
|
792
|
-
cursor = extraction.cursor;
|
|
793
|
-
if (gap !== undefined && args.strict === true)
|
|
794
|
-
throw new SdkSessionCliError(
|
|
795
|
-
"retention_gap",
|
|
796
|
-
"Retained history is missing entries before the checkpoint (strict mode).",
|
|
797
|
-
1,
|
|
798
|
-
gap,
|
|
799
|
-
);
|
|
800
|
-
|
|
801
|
-
// Continue paginating the retained transcript from the checkpoint cursor
|
|
802
|
-
// (the resume page above may itself be incomplete).
|
|
803
|
-
while (cursor !== undefined) {
|
|
804
|
-
const page = extractTranscriptPage(await session.query("transcript.list", {}, cursor));
|
|
805
|
-
mergeTailItems(
|
|
806
|
-
items,
|
|
807
|
-
seen,
|
|
808
|
-
page.items.map(item => toTailItemV1(item, { kind: "transcript" })),
|
|
809
|
-
include,
|
|
810
|
-
);
|
|
811
|
-
if (page.complete || !page.cursor) break;
|
|
812
|
-
cursor = page.cursor;
|
|
901
|
+
if (CLOSE_EVENT_KINDS.has(item.kind)) {
|
|
902
|
+
liveReason = "close";
|
|
903
|
+
resolveLive?.("close");
|
|
813
904
|
}
|
|
905
|
+
};
|
|
814
906
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
source: "session",
|
|
826
|
-
session: row,
|
|
827
|
-
...(checkpoint !== undefined ? { checkpoint } : {}),
|
|
828
|
-
...(gap !== undefined ? { gap } : {}),
|
|
829
|
-
items,
|
|
830
|
-
terminal: true,
|
|
907
|
+
return await withRouter(
|
|
908
|
+
agentDir,
|
|
909
|
+
async router => {
|
|
910
|
+
const attachment = attachmentFor(router, sessionId);
|
|
911
|
+
const checkpointResponse = await router.request(
|
|
912
|
+
sessionId,
|
|
913
|
+
{
|
|
914
|
+
type: "query_request",
|
|
915
|
+
query: "session.checkpoint",
|
|
916
|
+
input: args.cursor === undefined ? {} : { checkpointToken: args.cursor },
|
|
831
917
|
},
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
|
|
918
|
+
attachment.generation,
|
|
919
|
+
attachment,
|
|
920
|
+
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
921
|
+
);
|
|
922
|
+
throwResponseFailure(checkpointResponse);
|
|
923
|
+
const extraction = extractCheckpoint(checkpointResponse);
|
|
924
|
+
checkpoint = extraction.record;
|
|
925
|
+
gap = extraction.gap;
|
|
926
|
+
if (gap !== undefined && args.strict === true)
|
|
839
927
|
throw new SdkSessionCliError(
|
|
840
|
-
"
|
|
841
|
-
"
|
|
928
|
+
"retention_gap",
|
|
929
|
+
"Retained history is missing entries before the checkpoint (strict mode).",
|
|
842
930
|
1,
|
|
931
|
+
gap,
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
let cursor = extraction.cursor;
|
|
935
|
+
while (cursor !== undefined) {
|
|
936
|
+
const response = await router.request(
|
|
937
|
+
sessionId,
|
|
938
|
+
{ type: "query_request", query: "transcript.list", input: {}, cursor },
|
|
939
|
+
attachment.generation,
|
|
940
|
+
attachment,
|
|
941
|
+
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
843
942
|
);
|
|
844
|
-
|
|
845
|
-
|
|
943
|
+
throwResponseFailure(response);
|
|
944
|
+
const page = extractTranscriptPage(response);
|
|
945
|
+
mergeTailItems(
|
|
946
|
+
items,
|
|
947
|
+
seen,
|
|
948
|
+
page.items.map(item => toTailItemV1(item, { kind: "transcript" })),
|
|
949
|
+
include,
|
|
950
|
+
);
|
|
951
|
+
if (page.complete || page.cursor === undefined) break;
|
|
952
|
+
cursor = page.cursor;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const replayResponse = await router.request(
|
|
956
|
+
sessionId,
|
|
957
|
+
{
|
|
958
|
+
type: "event_replay",
|
|
959
|
+
...(checkpoint === undefined
|
|
960
|
+
? {}
|
|
961
|
+
: { sinceGeneration: checkpoint.generation, sinceSeq: checkpoint.seq }),
|
|
962
|
+
},
|
|
963
|
+
attachment.generation,
|
|
964
|
+
attachment,
|
|
965
|
+
args.timeoutMs === undefined ? undefined : { timeoutMs: args.timeoutMs },
|
|
966
|
+
);
|
|
967
|
+
throwResponseFailure(replayResponse);
|
|
968
|
+
const replay = object(replayResponse) ?? {};
|
|
969
|
+
const replayGap = eventGapToRetentionGap(replay.gap, replay, checkpoint);
|
|
970
|
+
if (replayGap !== undefined) {
|
|
971
|
+
gap = replayGap;
|
|
846
972
|
if (args.strict === true)
|
|
847
973
|
throw new SdkSessionCliError(
|
|
848
974
|
"retention_gap",
|
|
849
975
|
"The event ring dropped entries before the checkpoint (strict mode).",
|
|
850
976
|
1,
|
|
851
|
-
|
|
977
|
+
replayGap,
|
|
852
978
|
);
|
|
853
979
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (args.untilIdle === true &&
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
"reconnect_failed",
|
|
874
|
-
"Tail could not restore replay continuity after reconnect.",
|
|
875
|
-
1,
|
|
980
|
+
const replayItems = arrayOf(replay.events).map(event => toTailItemV1(event, { kind: "event" }));
|
|
981
|
+
mergeTailItems(items, seen, replayItems, include);
|
|
982
|
+
if (args.untilIdle === true && replayItems.some(item => TERMINAL_TURN_KINDS.has(item.kind)))
|
|
983
|
+
liveReason = "idle";
|
|
984
|
+
if (liveReason === undefined) {
|
|
985
|
+
const completion = Promise.withResolvers<TailExitReason>();
|
|
986
|
+
resolveLive = completion.resolve;
|
|
987
|
+
const timeoutMs = args.timeoutMs ?? 10_000;
|
|
988
|
+
const timer = setTimeout(
|
|
989
|
+
() =>
|
|
990
|
+
completion.reject(
|
|
991
|
+
new SdkSessionCliError(
|
|
992
|
+
"tail_timeout",
|
|
993
|
+
"Tail did not reach an exit condition within the wait window.",
|
|
994
|
+
1,
|
|
995
|
+
{ sessionId, timeoutMs },
|
|
996
|
+
),
|
|
997
|
+
),
|
|
998
|
+
timeoutMs,
|
|
876
999
|
);
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
};
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1000
|
+
try {
|
|
1001
|
+
liveReason = await completion.promise;
|
|
1002
|
+
} finally {
|
|
1003
|
+
clearTimeout(timer);
|
|
1004
|
+
resolveLive = undefined;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
return {
|
|
1008
|
+
ok: true,
|
|
1009
|
+
result: {
|
|
1010
|
+
version: SESSION_ROWS_VERSION,
|
|
1011
|
+
source: "session",
|
|
1012
|
+
session: row,
|
|
1013
|
+
...(checkpoint === undefined ? {} : { checkpoint }),
|
|
1014
|
+
...(gap === undefined ? {} : { gap }),
|
|
1015
|
+
items,
|
|
1016
|
+
terminal: liveReason === "idle" || liveReason === "close",
|
|
1017
|
+
},
|
|
1018
|
+
};
|
|
1019
|
+
},
|
|
1020
|
+
recordLiveFrame,
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export async function runTail(
|
|
1025
|
+
repo: string,
|
|
1026
|
+
agentDir: string,
|
|
1027
|
+
sessionId: string,
|
|
1028
|
+
args: SdkSessionCliArgs,
|
|
1029
|
+
): Promise<unknown> {
|
|
1030
|
+
const row = (await sessionRows(agentDir)).sessions.find(candidate => candidate.sessionId === sessionId);
|
|
1031
|
+
if (!row)
|
|
1032
|
+
throw new SdkSessionCliError("session_unavailable", `Session ${sessionId} is not indexed by the broker.`, 1);
|
|
1033
|
+
if (row.deleted)
|
|
1034
|
+
throw new SdkSessionCliError("session_deleted", `Session ${sessionId} was deleted and has no tail.`, 1);
|
|
1035
|
+
if (!row.live || row.terminalUncertain === true) return await offlineTailReplay(repo, agentDir, sessionId, row);
|
|
1036
|
+
return await runLiveTail(agentDir, sessionId, row, args);
|
|
914
1037
|
}
|
|
915
1038
|
|
|
916
1039
|
async function runRawControl(
|
|
@@ -922,18 +1045,8 @@ async function runRawControl(
|
|
|
922
1045
|
): Promise<unknown> {
|
|
923
1046
|
const invalid = validateAdapterControl(operation, input);
|
|
924
1047
|
if (invalid) throw new SdkSessionCliError(invalid.code, invalid.message, 2);
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
return await broker.global("session.control", {
|
|
928
|
-
sessionId,
|
|
929
|
-
operation,
|
|
930
|
-
input,
|
|
931
|
-
confirm: args.confirm === true,
|
|
932
|
-
...(args.elevationRequestId === undefined ? {} : { elevationRequestId: args.elevationRequestId }),
|
|
933
|
-
});
|
|
934
|
-
} finally {
|
|
935
|
-
await broker.close();
|
|
936
|
-
}
|
|
1048
|
+
await ensureBroker({ agentDir });
|
|
1049
|
+
return await withRouter(agentDir, async router => await requestControl(router, sessionId, operation, input, args));
|
|
937
1050
|
}
|
|
938
1051
|
|
|
939
1052
|
async function runRawQuery(
|
|
@@ -943,13 +1056,41 @@ async function runRawQuery(
|
|
|
943
1056
|
input: JsonRecord,
|
|
944
1057
|
args: SdkSessionCliArgs,
|
|
945
1058
|
): Promise<unknown> {
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1059
|
+
await ensureBroker({ agentDir });
|
|
1060
|
+
return await withRouter(agentDir, async router => await requestQuery(router, sessionId, operation, input, args));
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function lifecycleMutationRequest(
|
|
1064
|
+
operation: LifecycleMutationOperation,
|
|
1065
|
+
input: JsonRecord,
|
|
1066
|
+
requestKey: string,
|
|
1067
|
+
timeoutMs: number | undefined,
|
|
1068
|
+
): SessionLifecycleMutationRequest {
|
|
1069
|
+
const base = {
|
|
1070
|
+
actor: SDK_SESSION_CLI_LIFECYCLE_ACTOR,
|
|
1071
|
+
requestKey,
|
|
1072
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
1073
|
+
};
|
|
1074
|
+
if (operation === "session.create" || operation === "session.fork") {
|
|
1075
|
+
if (typeof input.cwd !== "string" || input.cwd.length === 0)
|
|
1076
|
+
throw new SdkSessionCliError("invalid_input", `${operation} requires a string cwd in the input payload.`, 2);
|
|
1077
|
+
const target = input as JsonRecord & { cwd: string };
|
|
1078
|
+
return operation === "session.create"
|
|
1079
|
+
? { ...base, operation, capability: operation, target }
|
|
1080
|
+
: { ...base, operation, capability: operation, target };
|
|
952
1081
|
}
|
|
1082
|
+
if (typeof input.sessionId !== "string" || input.sessionId.length === 0)
|
|
1083
|
+
throw new SdkSessionCliError(
|
|
1084
|
+
"invalid_input",
|
|
1085
|
+
`${operation} requires a string sessionId in the input payload.`,
|
|
1086
|
+
2,
|
|
1087
|
+
);
|
|
1088
|
+
const target = input as JsonRecord & { sessionId: string };
|
|
1089
|
+
return operation === "session.resume"
|
|
1090
|
+
? { ...base, operation, capability: operation, target }
|
|
1091
|
+
: operation === "session.close"
|
|
1092
|
+
? { ...base, operation, capability: operation, target }
|
|
1093
|
+
: { ...base, operation, capability: "session.delete", target };
|
|
953
1094
|
}
|
|
954
1095
|
|
|
955
1096
|
async function runRawGlobal(
|
|
@@ -958,81 +1099,30 @@ async function runRawGlobal(
|
|
|
958
1099
|
input: JsonRecord,
|
|
959
1100
|
args: SdkSessionCliArgs,
|
|
960
1101
|
): Promise<unknown> {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
const client = await connectBroker(agentDir);
|
|
965
|
-
try {
|
|
966
|
-
try {
|
|
967
|
-
const timeoutMs = lifecycleRequestTimeoutMs(operation, input);
|
|
968
|
-
const response = await client.global(operation, input, {
|
|
969
|
-
idempotencyKey,
|
|
970
|
-
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
971
|
-
...(args.elevationRequestId === undefined ? {} : { elevationRequestId: args.elevationRequestId }),
|
|
972
|
-
});
|
|
973
|
-
return args.showEndpointCredential ? response : stripSecretFields(response);
|
|
974
|
-
} catch (error) {
|
|
975
|
-
if (
|
|
976
|
-
isLifecycleOperation(operation) &&
|
|
977
|
-
error instanceof SdkClientError &&
|
|
978
|
-
error.code === "uncertain_after_send" &&
|
|
979
|
-
error.details &&
|
|
980
|
-
typeof error.details === "object"
|
|
981
|
-
)
|
|
982
|
-
return stripSecretFields(await client.lookupLifecycle(error.details as SdkSentRecord));
|
|
983
|
-
throw error;
|
|
984
|
-
}
|
|
985
|
-
} finally {
|
|
986
|
-
await client.close();
|
|
1102
|
+
if (operation === "session.list") {
|
|
1103
|
+
await ensureBroker({ agentDir });
|
|
1104
|
+
return await withRouter(agentDir, async router => await paginatedSessionList(router, input));
|
|
987
1105
|
}
|
|
1106
|
+
if (!isLifecycleOperation(operation))
|
|
1107
|
+
throw new SdkSessionCliError("unknown_operation", `Unknown global operation: ${operation}`, 1);
|
|
1108
|
+
if (!args.idempotencyKey)
|
|
1109
|
+
throw new SdkSessionCliError("invalid_input", "--idempotency-key is required for lifecycle operations.", 2);
|
|
1110
|
+
const lifecycle = createBrokerSessionLifecycleService(agentDir);
|
|
1111
|
+
const timeoutMs = lifecycleRequestTimeoutMs(operation, input);
|
|
1112
|
+
const response = await lifecycle.execute(lifecycleMutationRequest(operation, input, args.idempotencyKey, timeoutMs));
|
|
1113
|
+
throwResponseFailure(response);
|
|
1114
|
+
return response;
|
|
988
1115
|
}
|
|
989
1116
|
|
|
990
|
-
|
|
991
|
-
if (
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
);
|
|
997
|
-
const kind = args.operationKind;
|
|
998
|
-
if (kind !== "control" && kind !== "global")
|
|
999
|
-
throw new SdkSessionCliError("usage", "elevate requires --kind control|global.", 2);
|
|
1000
|
-
const operation = requireValue(args.operation, "--op");
|
|
1001
|
-
const input = await inputFromArgs(args);
|
|
1002
|
-
const broker = await connectBroker(agentDir);
|
|
1003
|
-
try {
|
|
1004
|
-
const issued = await broker.global("elevation.issue", {
|
|
1005
|
-
sessionId,
|
|
1006
|
-
operation: { kind, sdkId: operation },
|
|
1007
|
-
input,
|
|
1008
|
-
requester: { source: "broker_connection", connectionId: `operator-cli:${process.pid}` },
|
|
1009
|
-
});
|
|
1010
|
-
if (isRecord(issued) && issued.ok === false) return issued;
|
|
1011
|
-
const result = resultObject(issued);
|
|
1012
|
-
const elevationRequestId = typeof result?.elevationRequestId === "string" ? result.elevationRequestId : undefined;
|
|
1013
|
-
const presentedDigest = typeof result?.requestDigest === "string" ? result.requestDigest : undefined;
|
|
1014
|
-
if (!elevationRequestId || !presentedDigest)
|
|
1015
|
-
throw new SdkSessionCliError("unavailable", "Broker returned a malformed elevation request.", 1);
|
|
1016
|
-
const directory = path.join(agentDir, "sdk", "elevation", "operator");
|
|
1017
|
-
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
1018
|
-
const target = path.join(directory, `${elevationRequestId}.json`);
|
|
1019
|
-
const temporary = `${target}.${randomUUID()}.tmp`;
|
|
1020
|
-
await Bun.write(
|
|
1021
|
-
temporary,
|
|
1022
|
-
`${JSON.stringify({ version: 1, elevationRequestId, answer: "approve", presentedDigest, createdAt: Date.now() })}\n`,
|
|
1023
|
-
);
|
|
1024
|
-
await fs.chmod(temporary, 0o600);
|
|
1025
|
-
await fs.rename(temporary, target);
|
|
1026
|
-
return { ok: true, result: { elevationRequestId, requestDigest: presentedDigest, state: "approval_submitted" } };
|
|
1027
|
-
} finally {
|
|
1028
|
-
await broker.close();
|
|
1029
|
-
}
|
|
1117
|
+
function rawKind(action: string, args: SdkSessionCliArgs): SdkSessionCliRawKind | undefined {
|
|
1118
|
+
if (action === "raw")
|
|
1119
|
+
return args.rawAction === "control" || args.rawAction === "query" || args.rawAction === "global"
|
|
1120
|
+
? args.rawAction
|
|
1121
|
+
: undefined;
|
|
1122
|
+
return action === "control" || action === "query" || action === "global" ? action : undefined;
|
|
1030
1123
|
}
|
|
1031
1124
|
|
|
1032
|
-
/**
|
|
1033
|
-
* Runs the `gjc sdk session` command family: semantic verbs (list, inspect,
|
|
1034
|
-
* send, status, tail) plus the explicit raw control/query/global hatch.
|
|
1035
|
-
*/
|
|
1125
|
+
/** Runs the broker-bound `gjc sdk session` command family without exposing endpoint credentials. */
|
|
1036
1126
|
export async function runSdkSessionCli(
|
|
1037
1127
|
args: SdkSessionCliArgs,
|
|
1038
1128
|
writeOutput: (value: unknown) => void = writeJson,
|
|
@@ -1048,99 +1138,85 @@ export async function runSdkSessionCli(
|
|
|
1048
1138
|
action !== "send" &&
|
|
1049
1139
|
action !== "status" &&
|
|
1050
1140
|
action !== "tail" &&
|
|
1051
|
-
action !== "
|
|
1052
|
-
action !== "
|
|
1141
|
+
action !== "raw" &&
|
|
1142
|
+
action !== "control" &&
|
|
1143
|
+
action !== "query" &&
|
|
1144
|
+
action !== "global"
|
|
1053
1145
|
)
|
|
1054
1146
|
throw new SdkSessionCliError(
|
|
1055
1147
|
"usage",
|
|
1056
|
-
"Expected one of: list, inspect, send, status, tail,
|
|
1148
|
+
"Expected one of: list, inspect, send, status, tail, raw (control|query|global).",
|
|
1057
1149
|
2,
|
|
1058
1150
|
);
|
|
1059
|
-
const repo = args.repo ?? process.cwd();
|
|
1060
1151
|
const agentDir = args.agentDir ?? getAgentDir();
|
|
1061
|
-
|
|
1062
1152
|
if (action === "list") {
|
|
1063
|
-
writeOutput(await runList(agentDir));
|
|
1153
|
+
writeOutput(stripSecretFields(await runList(agentDir)));
|
|
1064
1154
|
return;
|
|
1065
1155
|
}
|
|
1066
|
-
const sessionId =
|
|
1067
|
-
action === "raw" && args.rawAction === "global" ? undefined : requireValue(args.sessionId, "<sessionId>");
|
|
1068
1156
|
if (action === "inspect") {
|
|
1069
|
-
writeOutput(await runInspect(
|
|
1157
|
+
writeOutput(stripSecretFields(await runInspect(agentDir, requireValue(args.sessionId, "<sessionId>"))));
|
|
1070
1158
|
return;
|
|
1071
1159
|
}
|
|
1072
1160
|
if (action === "send") {
|
|
1073
|
-
writeOutput(await runSend(agentDir, requireValue(sessionId, "<sessionId>"), args));
|
|
1161
|
+
writeOutput(stripSecretFields(await runSend(agentDir, requireValue(args.sessionId, "<sessionId>"), args)));
|
|
1074
1162
|
return;
|
|
1075
1163
|
}
|
|
1076
1164
|
if (action === "status") {
|
|
1077
1165
|
writeOutput(
|
|
1078
|
-
|
|
1166
|
+
stripSecretFields(
|
|
1167
|
+
await runStatus(
|
|
1168
|
+
agentDir,
|
|
1169
|
+
requireValue(args.sessionId, "<sessionId>"),
|
|
1170
|
+
requireValue(args.opRef, "<opRef>"),
|
|
1171
|
+
args,
|
|
1172
|
+
),
|
|
1173
|
+
),
|
|
1079
1174
|
);
|
|
1080
1175
|
return;
|
|
1081
1176
|
}
|
|
1082
1177
|
if (action === "tail") {
|
|
1083
|
-
writeOutput(
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1178
|
+
writeOutput(
|
|
1179
|
+
stripSecretFields(
|
|
1180
|
+
await runTail(args.repo ?? process.cwd(), agentDir, requireValue(args.sessionId, "<sessionId>"), args),
|
|
1181
|
+
),
|
|
1182
|
+
);
|
|
1088
1183
|
return;
|
|
1089
1184
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
const operation =
|
|
1094
|
-
rawAction === "query" ? requireValue(args.query, "--query") : requireValue(args.operation, "--op");
|
|
1095
|
-
const kind: OperationKind = rawAction === "query" ? "query" : rawAction === "global" ? "global" : "control";
|
|
1185
|
+
|
|
1186
|
+
const kind = rawKind(requireValue(action, "<verb>"), args);
|
|
1187
|
+
if (!kind) throw new SdkSessionCliError("usage", "raw requires one of: control, query, global.", 2);
|
|
1188
|
+
const operation = kind === "query" ? requireValue(args.query, "--query") : requireValue(args.operation, "--op");
|
|
1096
1189
|
const dispositionError = cliOperationError(kind, operation);
|
|
1097
1190
|
if (dispositionError) throw new SdkSessionCliError(dispositionError.code, dispositionError.message, 1);
|
|
1098
|
-
if (isEndpointOperation(operation)
|
|
1191
|
+
if (isEndpointOperation(operation))
|
|
1099
1192
|
throw new SdkSessionCliError(
|
|
1100
1193
|
"endpoint_credential_forbidden",
|
|
1101
|
-
"session.get_endpoint
|
|
1102
|
-
1,
|
|
1103
|
-
);
|
|
1104
|
-
if (
|
|
1105
|
-
isCredentialBearingOperation(operation) &&
|
|
1106
|
-
args.showEndpointCredential &&
|
|
1107
|
-
process.stdout.isTTY &&
|
|
1108
|
-
!args.yes &&
|
|
1109
|
-
!(await confirmEndpointCredentialOutput())
|
|
1110
|
-
)
|
|
1111
|
-
throw new SdkSessionCliError(
|
|
1112
|
-
"endpoint_credential_confirmation_required",
|
|
1113
|
-
"Endpoint credential output was not confirmed.",
|
|
1194
|
+
"session.get_endpoint is not available through the SDK session CLI.",
|
|
1114
1195
|
1,
|
|
1115
1196
|
);
|
|
1116
1197
|
const input = await inputFromArgs(args);
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (rawAction === "control") {
|
|
1122
|
-
writeOutput(await runRawControl(agentDir, requireValue(sessionId, "<sessionId>"), operation, input, args));
|
|
1198
|
+
const secretError = validateAdapterSecretFields(operation, input);
|
|
1199
|
+
if (secretError) throw new SdkSessionCliError(secretError.code, secretError.message, 2);
|
|
1200
|
+
if (kind === "global") {
|
|
1201
|
+
writeOutput(stripSecretFields(await runRawGlobal(agentDir, operation, input, args)));
|
|
1123
1202
|
return;
|
|
1124
1203
|
}
|
|
1125
|
-
|
|
1204
|
+
const sessionId = requireValue(args.sessionId, "<sessionId>");
|
|
1205
|
+
writeOutput(
|
|
1206
|
+
stripSecretFields(
|
|
1207
|
+
kind === "control"
|
|
1208
|
+
? await runRawControl(agentDir, sessionId, operation, input, args)
|
|
1209
|
+
: await runRawQuery(agentDir, sessionId, operation, input, args),
|
|
1210
|
+
),
|
|
1211
|
+
);
|
|
1126
1212
|
} catch (error) {
|
|
1127
|
-
if (brokerAbsent(error)) {
|
|
1128
|
-
writeOutput({ ok: false, error: { code: "broker_unavailable", message: "SDK broker is unavailable." } });
|
|
1129
|
-
setExitCode(1);
|
|
1130
|
-
return;
|
|
1131
|
-
}
|
|
1132
1213
|
const cliError =
|
|
1133
1214
|
error instanceof SdkSessionCliError
|
|
1134
1215
|
? error
|
|
1135
|
-
: error instanceof
|
|
1136
|
-
? new SdkSessionCliError(
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
1,
|
|
1140
|
-
(error.details as { details?: unknown } | undefined)?.details,
|
|
1141
|
-
)
|
|
1142
|
-
: error instanceof SdkDiscoveryError
|
|
1143
|
-
? new SdkSessionCliError(error.code, error.message, 1)
|
|
1216
|
+
: error instanceof SessionRouterError
|
|
1217
|
+
? new SdkSessionCliError(error.phase, error.message, 1)
|
|
1218
|
+
: error instanceof SdkClientError
|
|
1219
|
+
? new SdkSessionCliError(error.code, error.message, 1, error.details)
|
|
1144
1220
|
: new SdkSessionCliError(
|
|
1145
1221
|
"operation_failed",
|
|
1146
1222
|
error instanceof Error ? error.message : "SDK operation failed.",
|
|
@@ -1151,7 +1227,7 @@ export async function runSdkSessionCli(
|
|
|
1151
1227
|
error: {
|
|
1152
1228
|
code: cliError.code,
|
|
1153
1229
|
message: cliError.message,
|
|
1154
|
-
...(cliError.details ? { details: stripSecretFields(cliError.details) }
|
|
1230
|
+
...(cliError.details === undefined ? {} : { details: stripSecretFields(cliError.details) }),
|
|
1155
1231
|
},
|
|
1156
1232
|
});
|
|
1157
1233
|
setExitCode(cliError.exitCode);
|