@gajae-code/coding-agent 0.10.1 → 0.11.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 +70 -2
- package/dist/types/append-only-mode.d.ts +1 -1
- package/dist/types/cli/args.d.ts +4 -5
- package/dist/types/cli/daemon-cli.d.ts +12 -1
- package/dist/types/cli/notify-cli.d.ts +21 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +39 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/notify.d.ts +28 -1
- package/dist/types/commands/sdk.d.ts +33 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
- package/dist/types/config/model-profile-activation.d.ts +35 -15
- package/dist/types/config/model-profiles.d.ts +11 -5
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +39 -6
- package/dist/types/config/model-selector-value.d.ts +17 -0
- package/dist/types/config/models-config-schema.d.ts +10 -6
- package/dist/types/config/settings-schema.d.ts +133 -6
- package/dist/types/config/settings.d.ts +63 -35
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
- package/dist/types/coordinator-mcp/server.d.ts +6 -49
- package/dist/types/daemon/builtin.d.ts +6 -5
- package/dist/types/daemon/control-types.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +12 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +10 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -3
- package/dist/types/extensibility/extensions/types.d.ts +141 -16
- package/dist/types/extensibility/hooks/types.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
- package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
- package/dist/types/harness-control-plane/operate.d.ts +5 -5
- package/dist/types/harness-control-plane/owner.d.ts +18 -4
- package/dist/types/harness-control-plane/receipts.d.ts +0 -1
- package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
- package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
- package/dist/types/harness-control-plane/types.d.ts +3 -4
- package/dist/types/lsp/index.d.ts +1 -1
- package/dist/types/main.d.ts +32 -38
- package/dist/types/modes/acp/acp-agent.d.ts +224 -53
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -15
- package/dist/types/modes/acp/permission-mode.d.ts +4 -0
- package/dist/types/modes/acp/startup-options.d.ts +6 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
- package/dist/types/modes/components/pet-capability.d.ts +37 -0
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +15 -4
- package/dist/types/modes/components/welcome.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
- package/dist/types/modes/index.d.ts +0 -4
- package/dist/types/modes/interactive-mode.d.ts +5 -1
- package/dist/types/modes/runtime-init.d.ts +2 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
- package/dist/types/modes/types.d.ts +16 -1
- package/dist/types/runtime-mcp/client.d.ts +2 -0
- package/dist/types/runtime-mcp/config.d.ts +5 -1
- package/dist/types/runtime-mcp/manager.d.ts +8 -1
- package/dist/types/runtime-mcp/types.d.ts +4 -0
- package/dist/types/sdk/acp/adapter.d.ts +67 -0
- package/dist/types/sdk/acp/index.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +53 -0
- package/dist/types/sdk/broker/discovery.d.ts +30 -0
- package/dist/types/sdk/broker/ensure.d.ts +33 -0
- package/dist/types/sdk/broker/identity.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +8 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
- package/dist/types/sdk/broker/runtime.d.ts +33 -0
- package/dist/types/sdk/broker/session-index.d.ts +57 -0
- package/dist/types/sdk/broker/state-version.d.ts +8 -0
- package/dist/types/sdk/broker/transport.d.ts +9 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
- package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
- package/dist/types/sdk/bus/config.d.ts +274 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
- package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
- package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
- package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
- package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
- package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
- package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
- package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
- package/dist/types/sdk/bus/session-control.d.ts +83 -0
- package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
- package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
- package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
- package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
- package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/session-cli.d.ts +19 -0
- package/dist/types/sdk/client/client.d.ts +1 -0
- package/dist/types/sdk/client/discovery.d.ts +27 -0
- package/dist/types/sdk/client/index.d.ts +2 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
- package/dist/types/sdk/host/control/index.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +65 -0
- package/dist/types/sdk/host/events.d.ts +35 -0
- package/dist/types/sdk/host/host.d.ts +35 -0
- package/dist/types/sdk/host/index.d.ts +4 -0
- package/dist/types/sdk/host/query/cursor.d.ts +62 -0
- package/dist/types/sdk/host/query/handlers.d.ts +70 -0
- package/dist/types/sdk/host/query/index.d.ts +3 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
- package/dist/types/sdk/host/types.d.ts +32 -0
- package/dist/types/sdk/index.d.ts +6 -0
- package/dist/types/sdk/lifecycle-session.d.ts +14 -0
- package/dist/types/sdk/mcp/index.d.ts +1 -0
- package/dist/types/sdk/mcp/server.d.ts +37 -0
- package/dist/types/sdk/models.d.ts +44 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
- package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
- package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
- package/dist/types/sdk/startup-capability.d.ts +50 -0
- package/dist/types/session/agent-session.d.ts +114 -21
- package/dist/types/session/default-model-selection.d.ts +25 -0
- package/dist/types/session/fallback-chain-controller.d.ts +52 -0
- package/dist/types/session/session-manager.d.ts +58 -1
- package/dist/types/setup/credential-auto-import.d.ts +36 -6
- package/dist/types/tools/ask-answer-registry.d.ts +1 -1
- package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
- package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
- package/dist/types/tools/browser/launch.d.ts +22 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
- package/dist/types/tools/browser/profile-posture.d.ts +37 -0
- package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
- package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
- package/dist/types/tools/browser/registry.d.ts +19 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +8 -8
- package/package.json +34 -13
- package/scripts/build-sdk-package-smoke.ts +94 -0
- package/scripts/capture-notifications-settings-showcase.ts +435 -0
- package/scripts/compile-args.ts +4 -2
- package/scripts/g005-daemon-smoke.ts +4 -5
- package/scripts/g011-daemon-path-smoke.ts +5 -5
- package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
- package/scripts/generate-sdk-operation-inventory.ts +867 -0
- package/scripts/generate-telegram-baseline-manifest.ts +128 -0
- package/scripts/manifest-schema.ts +124 -0
- package/scripts/run-test-manifest.ts +153 -0
- package/scripts/run-threat-model-check.ts +157 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
- package/scripts/verify-gjc-sdk-rename.ts +90 -0
- package/src/append-only-mode.ts +1 -1
- package/src/cli/args.ts +43 -12
- package/src/cli/auth-gateway-cli.ts +2 -2
- package/src/cli/config-cli.ts +2 -1
- package/src/cli/daemon-cli.ts +63 -14
- package/src/cli/list-models.ts +1 -1
- package/src/cli/notify-cli.ts +288 -286
- package/src/cli.ts +21 -2
- package/src/commands/daemon.ts +50 -6
- package/src/commands/harness.ts +93 -36
- package/src/commands/launch.ts +5 -2
- package/src/commands/mcp-serve.ts +19 -12
- package/src/commands/notify.ts +36 -3
- package/src/commands/sdk.ts +426 -0
- package/src/commit/agentic/agent.ts +1 -0
- package/src/config/atomic-yaml-patch.ts +423 -0
- package/src/config/model-profile-activation.ts +240 -89
- package/src/config/model-profiles.ts +37 -34
- package/src/config/model-registry.ts +51 -26
- package/src/config/model-resolver.ts +138 -51
- package/src/config/model-selector-value.ts +50 -0
- package/src/config/models-config-schema.ts +14 -21
- package/src/config/settings-schema.ts +128 -10
- package/src/config/settings.ts +1120 -158
- package/src/coordinator-mcp/model-preset.ts +74 -20
- package/src/coordinator-mcp/server.ts +1597 -3052
- package/src/daemon/builtin.ts +8 -5
- package/src/daemon/control-types.ts +1 -1
- package/src/defaults/gjc/skills/team/SKILL.md +4 -4
- package/src/discovery/mcp-json.ts +327 -20
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/compact-handler.ts +3 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -2
- package/src/extensibility/extensions/loader.ts +69 -5
- package/src/extensibility/extensions/runner.ts +112 -4
- package/src/extensibility/extensions/types.ts +176 -17
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/slash-commands.ts +3 -3
- package/src/gjc-runtime/launch-tmux.ts +170 -86
- package/src/gjc-runtime/psmux-detect.ts +82 -4
- package/src/gjc-runtime/session-state-sidecar.ts +41 -31
- package/src/gjc-runtime/team-runtime.ts +54 -35
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +5 -2
- package/src/gjc-runtime/tmux-sessions.ts +19 -13
- package/src/gjc-runtime/ultragoal-guard.ts +53 -6
- package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
- package/src/gjc-runtime/windows-powershell-command.ts +58 -0
- package/src/harness-control-plane/operate.ts +9 -9
- package/src/harness-control-plane/owner.ts +283 -39
- package/src/harness-control-plane/receipts.ts +0 -1
- package/src/harness-control-plane/sdk-transport.ts +395 -0
- package/src/harness-control-plane/session-transport.ts +94 -0
- package/src/harness-control-plane/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +36 -37
- package/src/lsp/client.ts +42 -6
- package/src/lsp/index.ts +2 -2
- package/src/main.ts +299 -204
- package/src/modes/DESIGN.md +258 -0
- package/src/modes/acp/acp-agent.ts +1228 -2612
- package/src/modes/acp/acp-client-bridge.ts +2 -20
- package/src/modes/acp/acp-event-mapper.ts +18 -2
- package/src/modes/acp/acp-mode.ts +8 -36
- package/src/modes/acp/permission-mode.ts +25 -0
- package/src/modes/acp/startup-options.ts +6 -0
- package/src/modes/components/agent-dashboard.ts +37 -17
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/gajae-pet-widget.ts +169 -20
- package/src/modes/components/model-selector.ts +93 -35
- package/src/modes/components/notifications-settings-editor.ts +1379 -0
- package/src/modes/components/pet-capability.ts +115 -0
- package/src/modes/components/pet-selector.ts +4 -6
- package/src/modes/components/settings-selector.ts +86 -11
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/event-controller.ts +11 -16
- package/src/modes/controllers/extension-ui-controller.ts +357 -12
- package/src/modes/controllers/input-controller.ts +43 -13
- package/src/modes/controllers/selector-controller.ts +671 -44
- package/src/modes/index.ts +0 -22
- package/src/modes/interactive-mode.ts +108 -24
- package/src/modes/runtime-init.ts +298 -6
- package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
- package/src/modes/shared/agent-wire/event-contract.ts +7 -8
- package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
- package/src/modes/shared/agent-wire/event-observation.ts +16 -7
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
- package/src/modes/types.ts +16 -1
- package/src/runtime-mcp/client.ts +94 -19
- package/src/runtime-mcp/config.ts +27 -15
- package/src/runtime-mcp/manager.ts +343 -105
- package/src/runtime-mcp/oauth-flow.ts +42 -12
- package/src/runtime-mcp/transports/http.ts +49 -19
- package/src/runtime-mcp/transports/stdio.ts +47 -28
- package/src/runtime-mcp/types.ts +10 -0
- package/src/sdk/acp/adapter.ts +418 -0
- package/src/sdk/acp/index.ts +2 -0
- package/src/sdk/broker/broker.ts +642 -0
- package/src/sdk/broker/discovery.ts +94 -0
- package/src/sdk/broker/ensure.ts +249 -0
- package/src/sdk/broker/identity.ts +42 -0
- package/src/sdk/broker/index.ts +8 -0
- package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
- package/src/sdk/broker/internal-source.bunfig.toml +1 -0
- package/src/sdk/broker/lifecycle-ledger.ts +286 -0
- package/src/sdk/broker/lifecycle.ts +2157 -0
- package/src/sdk/broker/process-incarnation.ts +159 -0
- package/src/sdk/broker/runtime.ts +164 -0
- package/src/sdk/broker/session-index.ts +318 -0
- package/src/sdk/broker/state-version.ts +23 -0
- package/src/sdk/broker/transport.ts +125 -0
- package/src/sdk/bus/chat-command-policy.ts +107 -0
- package/src/sdk/bus/chat-daemon-cli.ts +214 -0
- package/src/sdk/bus/chat-daemon-control.ts +713 -0
- package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
- package/src/sdk/bus/chat-effect-journal.ts +353 -0
- package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
- package/src/sdk/bus/config.ts +433 -0
- package/src/sdk/bus/conversation-store.ts +391 -0
- package/src/sdk/bus/discord-conversation.ts +85 -0
- package/src/sdk/bus/discord-daemon.ts +2275 -0
- package/src/sdk/bus/discord-live-provider.ts +521 -0
- package/src/sdk/bus/discord-provider.ts +56 -0
- package/src/{notifications → sdk/bus}/html-format.ts +2 -2
- package/src/sdk/bus/index.ts +3943 -0
- package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
- package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
- package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
- package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
- package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
- package/src/sdk/bus/natives-augment.d.ts +38 -0
- package/src/sdk/bus/notification-orchestration.ts +535 -0
- package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
- package/src/sdk/bus/session-control.ts +333 -0
- package/src/sdk/bus/slack-conversation.ts +74 -0
- package/src/sdk/bus/slack-daemon.ts +2144 -0
- package/src/sdk/bus/slack-live-provider.ts +380 -0
- package/src/sdk/bus/slack-provider.ts +76 -0
- package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
- package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
- package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
- package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
- package/src/sdk/bus/telegram-setup.ts +663 -0
- package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/session-cli.ts +275 -0
- package/src/sdk/client/client.ts +1 -0
- package/src/sdk/client/discovery.ts +108 -0
- package/src/sdk/client/index.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +367 -0
- package/src/sdk/host/control/index.ts +10 -0
- package/src/sdk/host/control/operations.ts +63 -0
- package/src/sdk/host/events.ts +91 -0
- package/src/sdk/host/host.ts +296 -0
- package/src/sdk/host/index.ts +4 -0
- package/src/sdk/host/query/cursor.ts +189 -0
- package/src/sdk/host/query/handlers.ts +533 -0
- package/src/sdk/host/query/index.ts +3 -0
- package/src/sdk/host/query/revision-store.ts +1126 -0
- package/src/sdk/host/reverse-leases.ts +325 -0
- package/src/sdk/host/types.ts +29 -0
- package/src/sdk/index.ts +13 -0
- package/src/sdk/lifecycle-session.ts +39 -0
- package/src/sdk/mcp/index.ts +1 -0
- package/src/sdk/mcp/server.ts +332 -0
- package/src/sdk/models.ts +168 -0
- package/src/sdk/protocol/adapter-validation.ts +48 -0
- package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
- package/src/sdk/protocol/operation-registry.ts +312 -0
- package/src/{sdk.ts → sdk/session.ts} +339 -212
- package/src/sdk/startup-capability.ts +189 -0
- package/src/session/agent-session.ts +2470 -866
- package/src/session/default-model-selection.ts +93 -0
- package/src/session/fallback-chain-controller.ts +136 -0
- package/src/session/session-manager.ts +447 -13
- package/src/session/session-storage.ts +8 -1
- package/src/setup/credential-auto-import.ts +290 -35
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
- package/src/slash-commands/builtin-registry.ts +33 -30
- package/src/task/executor.ts +62 -13
- package/src/tools/ask-answer-registry.ts +1 -1
- package/src/tools/ask.ts +13 -8
- package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
- package/src/tools/browser/benchmark/detector-report.ts +157 -0
- package/src/tools/browser/benchmark/run-suite.ts +39 -0
- package/src/tools/browser/launch.ts +124 -19
- package/src/tools/browser/profile-discovery.ts +63 -0
- package/src/tools/browser/profile-posture.ts +59 -0
- package/src/tools/browser/profile-reuse.ts +102 -0
- package/src/tools/browser/profile-warmup.ts +109 -0
- package/src/tools/browser/registry.ts +87 -11
- package/src/tools/browser/tab-protocol.ts +1 -0
- package/src/tools/browser/tab-supervisor.ts +1 -0
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +6 -0
- package/src/tools/index.ts +8 -8
- package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
- package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
- package/src/tools/telegram-send.ts +2 -2
- package/vendor/insane-search/MANIFEST.json +2 -1
- package/vendor/insane-search/engine/bias_check.py +4 -1
- package/vendor/insane-search/engine/phase0.py +26 -1
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
- package/dist/types/modes/bridge/auth.d.ts +0 -12
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
- package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
- package/dist/types/modes/bridge/event-stream.d.ts +0 -8
- package/dist/types/modes/rpc/host-tools.d.ts +0 -1
- package/dist/types/modes/rpc/host-uris.d.ts +0 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
- package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
- package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
- package/dist/types/notifications/config.d.ts +0 -132
- package/src/harness-control-plane/rpc-adapter.ts +0 -290
- package/src/modes/bridge/auth.ts +0 -41
- package/src/modes/bridge/bridge-client-bridge.ts +0 -47
- package/src/modes/bridge/bridge-mode.ts +0 -675
- package/src/modes/bridge/bridge-ui-context.ts +0 -200
- package/src/modes/bridge/event-stream.ts +0 -70
- package/src/modes/rpc/host-tools.ts +0 -1
- package/src/modes/rpc/host-uris.ts +0 -1
- package/src/modes/rpc/rpc-client.ts +0 -1166
- package/src/modes/rpc/rpc-mode.ts +0 -1029
- package/src/modes/rpc/rpc-socket-security.ts +0 -115
- package/src/modes/rpc/rpc-types.ts +0 -637
- package/src/modes/shared/agent-wire/command-contract.ts +0 -18
- package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
- package/src/modes/shared/agent-wire/command-validation.ts +0 -181
- package/src/modes/shared/agent-wire/handshake.ts +0 -157
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
- package/src/modes/shared/agent-wire/protocol.ts +0 -46
- package/src/modes/shared/agent-wire/responses.ts +0 -17
- package/src/modes/shared/agent-wire/scopes.ts +0 -94
- package/src/modes/shared/agent-wire/session-registry.ts +0 -109
- package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
- package/src/modes/shared/agent-wire/ui-result.ts +0 -48
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
- package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
- package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
- package/src/notifications/config.ts +0 -219
- package/src/notifications/index.ts +0 -1835
- package/src/notifications/telegram-daemon-cli.ts +0 -243
- package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
- /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
- /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
- /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
- /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
- /package/src/{notifications → sdk/bus}/engine.ts +0 -0
- /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
- /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
- /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
- /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
- /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
|
@@ -1,1029 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RPC mode: Headless operation with JSON stdin/stdout protocol.
|
|
3
|
-
*
|
|
4
|
-
* Used for embedding the agent in other applications.
|
|
5
|
-
* Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
|
|
6
|
-
*
|
|
7
|
-
* Protocol:
|
|
8
|
-
* - Commands: JSON objects with `type` field, optional `id` for correlation
|
|
9
|
-
* - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
|
|
10
|
-
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
|
-
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { $pickenv, isKnownSinkPeerClosedError, logger, readLines, Snowflake } from "@gajae-code/utils";
|
|
15
|
-
import type {
|
|
16
|
-
ExtensionUIContext,
|
|
17
|
-
ExtensionUIDialogOptions,
|
|
18
|
-
ExtensionWidgetOptions,
|
|
19
|
-
} from "../../extensibility/extensions";
|
|
20
|
-
import { workflowGatePath } from "../../gjc-runtime/session-layout";
|
|
21
|
-
import { type Theme, theme } from "../../modes/theme/theme";
|
|
22
|
-
import type { AgentSession } from "../../session/agent-session";
|
|
23
|
-
import { initializeExtensions } from "../runtime-init";
|
|
24
|
-
import { dispatchRpcCommand } from "../shared/agent-wire/command-dispatch";
|
|
25
|
-
import {
|
|
26
|
-
AgentWireCompactEventEncoder,
|
|
27
|
-
AgentWireFrameSequencer,
|
|
28
|
-
toAgentWireCompactEventFrame,
|
|
29
|
-
toAgentWireEventFrame,
|
|
30
|
-
} from "../shared/agent-wire/event-envelope";
|
|
31
|
-
import { rpcError as error, rpcSuccess } from "../shared/agent-wire/responses";
|
|
32
|
-
import { registerRpcSession, unregisterRpcSession } from "../shared/agent-wire/session-registry";
|
|
33
|
-
import { defaultAuditPath, UnattendedAuditLog } from "../shared/agent-wire/unattended-audit";
|
|
34
|
-
import { modelSupportsTokenCostMetrics, UnattendedSessionControlPlane } from "../shared/agent-wire/unattended-session";
|
|
35
|
-
import { FileGateStore } from "../shared/agent-wire/workflow-gate-broker";
|
|
36
|
-
import { isRpcHostToolResult, isRpcHostToolUpdate, RpcHostToolBridge } from "./host-tools";
|
|
37
|
-
import { isRpcHostUriResult, RpcHostUriBridge } from "./host-uris";
|
|
38
|
-
import { prepareRpcSocketPath, verifyRpcSocketAfterListen } from "./rpc-socket-security";
|
|
39
|
-
import type {
|
|
40
|
-
RpcCommand,
|
|
41
|
-
RpcExtensionUIRequest,
|
|
42
|
-
RpcExtensionUIResponse,
|
|
43
|
-
RpcHostToolCallRequest,
|
|
44
|
-
RpcHostToolCancelRequest,
|
|
45
|
-
RpcHostUriCancelRequest,
|
|
46
|
-
RpcHostUriRequest,
|
|
47
|
-
RpcResponse,
|
|
48
|
-
} from "./rpc-types";
|
|
49
|
-
|
|
50
|
-
// Re-export types for consumers
|
|
51
|
-
export type * from "./rpc-types";
|
|
52
|
-
|
|
53
|
-
export type PendingExtensionRequest = {
|
|
54
|
-
resolve: (response: RpcExtensionUIResponse) => void;
|
|
55
|
-
reject: (error: Error) => void;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
type RpcOutput = (
|
|
59
|
-
obj:
|
|
60
|
-
| RpcResponse
|
|
61
|
-
| RpcExtensionUIRequest
|
|
62
|
-
| RpcHostToolCallRequest
|
|
63
|
-
| RpcHostToolCancelRequest
|
|
64
|
-
| RpcHostUriRequest
|
|
65
|
-
| RpcHostUriCancelRequest
|
|
66
|
-
| object,
|
|
67
|
-
) => Promise<void>;
|
|
68
|
-
type StdioTransportState = "open" | "terminalizing" | "closed";
|
|
69
|
-
|
|
70
|
-
function sinkErrorCode(error: unknown): string | undefined {
|
|
71
|
-
if (error === null || (typeof error !== "object" && typeof error !== "function")) return undefined;
|
|
72
|
-
try {
|
|
73
|
-
const code = Reflect.get(error, "code");
|
|
74
|
-
return typeof code === "string" ? code : undefined;
|
|
75
|
-
} catch {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function parseValueDialogResponse(
|
|
81
|
-
response: RpcExtensionUIResponse,
|
|
82
|
-
dialogOptions: ExtensionUIDialogOptions | undefined,
|
|
83
|
-
): string | undefined {
|
|
84
|
-
if ("cancelled" in response && response.cancelled) {
|
|
85
|
-
if (response.timedOut) dialogOptions?.onTimeout?.();
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
if ("value" in response) return response.value;
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function shouldEmitRpcTitlesForTest(): boolean {
|
|
93
|
-
const raw = $pickenv("GJC_RPC_EMIT_TITLE", "PI_RPC_EMIT_TITLE");
|
|
94
|
-
if (!raw) return false;
|
|
95
|
-
const normalized = raw.trim().toLowerCase();
|
|
96
|
-
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const shouldEmitRpcTitles = shouldEmitRpcTitlesForTest;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Cancellation commands bypass the ordered serial chain because they must
|
|
103
|
-
* interrupt in-flight work — they cannot wait behind the very command they are
|
|
104
|
-
* meant to abort.
|
|
105
|
-
*/
|
|
106
|
-
export const RPC_CANCELLATION_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
|
|
107
|
-
"abort",
|
|
108
|
-
"abort_bash",
|
|
109
|
-
"abort_retry",
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Safe read-only commands that bypass the ordered serial chain so they never
|
|
114
|
-
* head-of-line-block behind a long-running ordered command like
|
|
115
|
-
* `bash`/`compact`/`handoff`/`login` (#606, issue 13 — the partial fix only
|
|
116
|
-
* fast-laned cancellation).
|
|
117
|
-
*
|
|
118
|
-
* Every command listed here has a dispatch handler that is **fully synchronous
|
|
119
|
-
* and side-effect-free**: on the single-threaded event loop it runs to
|
|
120
|
-
* completion between the await points of any in-flight ordered command, reading
|
|
121
|
-
* live state without mutating it. Because such a read performs no causal write,
|
|
122
|
-
* jumping ahead of an earlier *queued* ordered command is observably harmless —
|
|
123
|
-
* there is no state change to reorder. Read payloads are additionally
|
|
124
|
-
* snapshotted inside the handler (e.g. `get_messages` returns a shallow copy of
|
|
125
|
-
* `session.messages`) so a fast-lane read can never serialize a half-mutated
|
|
126
|
-
* array that an ordered turn/compaction is rewriting in place.
|
|
127
|
-
*
|
|
128
|
-
* Deliberately excluded (kept ordered): every async/long command and every
|
|
129
|
-
* mutating command. In particular the control-flag setters (`set_thinking_level`,
|
|
130
|
-
* `cycle_thinking_level`, `set_steering_mode`, `set_follow_up_mode`,
|
|
131
|
-
* `set_interrupt_mode`, `set_auto_compaction`, `set_auto_retry`) stay ordered.
|
|
132
|
-
* Their handlers are synchronous, so fast-laning one ahead of an already-queued
|
|
133
|
-
* `prompt`/`bash` would apply the new mode *before* that earlier command runs —
|
|
134
|
-
* the earlier command would then observe the later setter's value, a
|
|
135
|
-
* causal-order (arrival-order) regression. Mutations therefore stay on the
|
|
136
|
-
* chain, and new command types default to ordered (fail-safe).
|
|
137
|
-
*/
|
|
138
|
-
export const RPC_SAFE_READ_CONTROL_COMMANDS: ReadonlySet<RpcCommand["type"]> = new Set<RpcCommand["type"]>([
|
|
139
|
-
// Pure synchronous reads — snapshot live state at processing time, never mutate.
|
|
140
|
-
"get_state",
|
|
141
|
-
"get_session_stats",
|
|
142
|
-
"get_available_models",
|
|
143
|
-
"get_branch_messages",
|
|
144
|
-
"get_last_assistant_text",
|
|
145
|
-
"get_messages",
|
|
146
|
-
"get_login_providers",
|
|
147
|
-
"get_pending_workflow_gates",
|
|
148
|
-
]);
|
|
149
|
-
|
|
150
|
-
/** True when a command may bypass the ordered serial chain and run immediately. */
|
|
151
|
-
export function isFastLaneRpcCommand(type: RpcCommand["type"]): boolean {
|
|
152
|
-
return RPC_CANCELLATION_COMMANDS.has(type) || RPC_SAFE_READ_CONTROL_COMMANDS.has(type);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Schedules inbound RPC commands: fast-lane commands run immediately while
|
|
157
|
-
* everything else runs through a serial chain so causal order is preserved. The
|
|
158
|
-
* read loop never blocks, which is what lets a fast-lane command reach a
|
|
159
|
-
* long-running ordered command instead of being head-of-line-blocked behind it.
|
|
160
|
-
*/
|
|
161
|
-
export function createRpcCommandScheduler(
|
|
162
|
-
run: (command: RpcCommand) => Promise<void>,
|
|
163
|
-
track: (task: Promise<void>) => void,
|
|
164
|
-
): { dispatch: (command: RpcCommand) => void; close: () => void } {
|
|
165
|
-
let orderedChain: Promise<void> = Promise.resolve();
|
|
166
|
-
let accepting = true;
|
|
167
|
-
return {
|
|
168
|
-
dispatch(command: RpcCommand): void {
|
|
169
|
-
if (!accepting) return;
|
|
170
|
-
if (isFastLaneRpcCommand(command.type)) {
|
|
171
|
-
track(run(command));
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
orderedChain = orderedChain.then(async () => {
|
|
175
|
-
if (accepting) await run(command);
|
|
176
|
-
});
|
|
177
|
-
track(orderedChain);
|
|
178
|
-
},
|
|
179
|
-
close(): void {
|
|
180
|
-
accepting = false;
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function auditOutcomeFor(event: string): "accepted" | "rejected" | "denied" | "exceeded" | "aborted" | "info" {
|
|
186
|
-
if (event.includes("denied")) return "denied";
|
|
187
|
-
if (event.includes("exceeded")) return "exceeded";
|
|
188
|
-
if (event.includes("abort")) return "aborted";
|
|
189
|
-
if (event.includes("rejected") || event.includes("conflict")) return "rejected";
|
|
190
|
-
if (event.includes("accepted") || event.includes("negotiated") || event.includes("emitted")) return "accepted";
|
|
191
|
-
return "info";
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Thrown by prepareRpcSocketPath when the --listen path has a live owner;
|
|
195
|
-
// re-exported here because main.ts catches it at the RPC launch boundary.
|
|
196
|
-
export { RpcListenRefusedError } from "./rpc-socket-security";
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Probe whether a unix-domain socket path has a live server accepting
|
|
200
|
-
* connections. Returns `true` when a connection succeeds (a previous owner is
|
|
201
|
-
* still alive), and returns `false` only for known missing/stale endpoints
|
|
202
|
-
* (ENOENT / ECONNREFUSED). Unexpected probe failures fail closed as "alive" so
|
|
203
|
-
* `--listen` startup refuses to unlink a path it could not safely classify.
|
|
204
|
-
*/
|
|
205
|
-
export async function isUnixSocketAlive(socketPath: string): Promise<boolean> {
|
|
206
|
-
try {
|
|
207
|
-
const socket = await Bun.connect({
|
|
208
|
-
unix: socketPath,
|
|
209
|
-
socket: { data() {}, open() {}, error() {}, close() {} },
|
|
210
|
-
});
|
|
211
|
-
socket.end();
|
|
212
|
-
return true;
|
|
213
|
-
} catch (err) {
|
|
214
|
-
const code = err && typeof err === "object" ? (err as { code?: unknown }).code : undefined;
|
|
215
|
-
if (code === "ENOENT" || code === "ECONNREFUSED") return false;
|
|
216
|
-
logger.warn("RPC --listen socket probe failed closed", {
|
|
217
|
-
socketPath,
|
|
218
|
-
code: typeof code === "string" ? code : undefined,
|
|
219
|
-
error: err instanceof Error ? err.message : String(err),
|
|
220
|
-
});
|
|
221
|
-
return true;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function requestRpcEditor(
|
|
226
|
-
pendingRequests: Map<string, PendingExtensionRequest>,
|
|
227
|
-
output: RpcOutput,
|
|
228
|
-
title: string,
|
|
229
|
-
prefill?: string,
|
|
230
|
-
dialogOptions?: ExtensionUIDialogOptions,
|
|
231
|
-
editorOptions?: { promptStyle?: boolean },
|
|
232
|
-
): Promise<string | undefined> {
|
|
233
|
-
if (dialogOptions?.signal?.aborted) return Promise.resolve(undefined);
|
|
234
|
-
|
|
235
|
-
const id = Snowflake.next() as string;
|
|
236
|
-
const { promise, resolve, reject } = Promise.withResolvers<string | undefined>();
|
|
237
|
-
let settled = false;
|
|
238
|
-
|
|
239
|
-
const cleanup = () => {
|
|
240
|
-
dialogOptions?.signal?.removeEventListener("abort", onAbort);
|
|
241
|
-
pendingRequests.delete(id);
|
|
242
|
-
};
|
|
243
|
-
const finish = (value: string | undefined) => {
|
|
244
|
-
if (settled) return;
|
|
245
|
-
settled = true;
|
|
246
|
-
cleanup();
|
|
247
|
-
resolve(value);
|
|
248
|
-
};
|
|
249
|
-
const fail = (error: Error) => {
|
|
250
|
-
if (settled) return;
|
|
251
|
-
settled = true;
|
|
252
|
-
cleanup();
|
|
253
|
-
reject(error);
|
|
254
|
-
};
|
|
255
|
-
const onAbort = () => {
|
|
256
|
-
output({
|
|
257
|
-
type: "extension_ui_request",
|
|
258
|
-
id: Snowflake.next() as string,
|
|
259
|
-
method: "cancel",
|
|
260
|
-
targetId: id,
|
|
261
|
-
} as RpcExtensionUIRequest);
|
|
262
|
-
finish(undefined);
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
dialogOptions?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
266
|
-
pendingRequests.set(id, {
|
|
267
|
-
resolve: response => {
|
|
268
|
-
if ("cancelled" in response && response.cancelled) {
|
|
269
|
-
finish(undefined);
|
|
270
|
-
} else if ("value" in response) {
|
|
271
|
-
finish(response.value);
|
|
272
|
-
} else {
|
|
273
|
-
finish(undefined);
|
|
274
|
-
}
|
|
275
|
-
},
|
|
276
|
-
reject: fail,
|
|
277
|
-
});
|
|
278
|
-
output({
|
|
279
|
-
type: "extension_ui_request",
|
|
280
|
-
id,
|
|
281
|
-
method: "editor",
|
|
282
|
-
title,
|
|
283
|
-
prefill,
|
|
284
|
-
promptStyle: editorOptions?.promptStyle,
|
|
285
|
-
} as RpcExtensionUIRequest);
|
|
286
|
-
return promise;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Run in RPC mode.
|
|
290
|
-
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
291
|
-
*/
|
|
292
|
-
export async function runRpcMode(
|
|
293
|
-
session: AgentSession,
|
|
294
|
-
setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
295
|
-
options?: { listen?: string },
|
|
296
|
-
): Promise<never> {
|
|
297
|
-
// Signal to RPC clients that the server is ready to accept commands
|
|
298
|
-
// Suppress terminal notifications: they write \x07 (BEL) or OSC sequences directly to
|
|
299
|
-
// process.stdout with no newline, which the reader merges with the next JSON line and
|
|
300
|
-
// breaks JSON.parse. In RPC mode stdout is the JSON protocol channel — nothing else
|
|
301
|
-
// may write there.
|
|
302
|
-
process.env.PI_NOTIFICATIONS = "off";
|
|
303
|
-
|
|
304
|
-
// Frames route to stdout for stdio and to the active client for --listen. A
|
|
305
|
-
// socket server begins with no peer, so it must never leak frames to stdout.
|
|
306
|
-
const noopSink = (_line: string): void => {};
|
|
307
|
-
let stdioTransportState: StdioTransportState = options?.listen ? "closed" : "open";
|
|
308
|
-
let beginStdioTerminalization: (() => void) | undefined;
|
|
309
|
-
const stdioFatalFailure = Promise.withResolvers<never>();
|
|
310
|
-
void stdioFatalFailure.promise.catch(() => {});
|
|
311
|
-
let hasStdioWriteFailure = false;
|
|
312
|
-
let stdioWriteFailure: unknown;
|
|
313
|
-
let stdioPeerClosed = false;
|
|
314
|
-
const pendingStdioWrites = new Set<{
|
|
315
|
-
completion: Promise<void>;
|
|
316
|
-
settle: (failed: boolean, failure?: unknown) => void;
|
|
317
|
-
}>();
|
|
318
|
-
const settlePendingStdioWrites = (failed: boolean, failure?: unknown): void => {
|
|
319
|
-
for (const pending of [...pendingStdioWrites]) pending.settle(failed, failure);
|
|
320
|
-
};
|
|
321
|
-
const waitForStdioWrites = async (): Promise<void> => {
|
|
322
|
-
while (pendingStdioWrites.size > 0) {
|
|
323
|
-
await Promise.allSettled([...pendingStdioWrites].map(pending => pending.completion));
|
|
324
|
-
}
|
|
325
|
-
};
|
|
326
|
-
const handleStdioWriteFailure = (failure: unknown): boolean => {
|
|
327
|
-
const code = sinkErrorCode(failure);
|
|
328
|
-
if (
|
|
329
|
-
isKnownSinkPeerClosedError(failure) &&
|
|
330
|
-
(code === "EPIPE" || (stdioPeerClosed && code === "ERR_STREAM_DESTROYED"))
|
|
331
|
-
) {
|
|
332
|
-
stdioPeerClosed = true;
|
|
333
|
-
if (stdioTransportState === "open") stdioTransportState = "terminalizing";
|
|
334
|
-
settlePendingStdioWrites(false);
|
|
335
|
-
beginStdioTerminalization?.();
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
if (!hasStdioWriteFailure) {
|
|
339
|
-
hasStdioWriteFailure = true;
|
|
340
|
-
stdioWriteFailure = failure;
|
|
341
|
-
stdioTransportState = "terminalizing";
|
|
342
|
-
stdioFatalFailure.reject(failure);
|
|
343
|
-
settlePendingStdioWrites(true, failure);
|
|
344
|
-
beginStdioTerminalization?.();
|
|
345
|
-
}
|
|
346
|
-
return true;
|
|
347
|
-
};
|
|
348
|
-
const onStdioError = (failure: Error): void => {
|
|
349
|
-
handleStdioWriteFailure(failure);
|
|
350
|
-
};
|
|
351
|
-
if (!options?.listen) process.stdout.on("error", onStdioError);
|
|
352
|
-
const writeStdioFrame = (line: string): Promise<void> => {
|
|
353
|
-
if (stdioTransportState !== "open") return Promise.resolve();
|
|
354
|
-
const completion = Promise.withResolvers<void>();
|
|
355
|
-
let settled = false;
|
|
356
|
-
let pending: { completion: Promise<void>; settle: (failed: boolean, failure?: unknown) => void };
|
|
357
|
-
pending = {
|
|
358
|
-
completion: completion.promise,
|
|
359
|
-
settle: (failed, failure) => {
|
|
360
|
-
if (settled) return;
|
|
361
|
-
settled = true;
|
|
362
|
-
pendingStdioWrites.delete(pending);
|
|
363
|
-
if (failed) completion.reject(failure);
|
|
364
|
-
else completion.resolve();
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
pendingStdioWrites.add(pending);
|
|
368
|
-
try {
|
|
369
|
-
process.stdout.write(line, writeError => {
|
|
370
|
-
if (settled) return;
|
|
371
|
-
if (writeError) pending.settle(handleStdioWriteFailure(writeError), writeError);
|
|
372
|
-
else pending.settle(false);
|
|
373
|
-
});
|
|
374
|
-
} catch (failure) {
|
|
375
|
-
pending.settle(handleStdioWriteFailure(failure), failure);
|
|
376
|
-
}
|
|
377
|
-
return completion.promise;
|
|
378
|
-
};
|
|
379
|
-
let frameSink: (line: string) => void | Promise<void> = options?.listen ? noopSink : writeStdioFrame;
|
|
380
|
-
const output: RpcOutput = obj => {
|
|
381
|
-
if (!options?.listen && stdioTransportState !== "open") return Promise.resolve();
|
|
382
|
-
let emission: Promise<void>;
|
|
383
|
-
try {
|
|
384
|
-
emission = Promise.resolve(frameSink(`${JSON.stringify(obj)}\n`));
|
|
385
|
-
} catch (failure) {
|
|
386
|
-
emission = Promise.reject(failure);
|
|
387
|
-
if (!options?.listen) handleStdioWriteFailure(failure);
|
|
388
|
-
}
|
|
389
|
-
void emission.catch(() => {});
|
|
390
|
-
return emission;
|
|
391
|
-
};
|
|
392
|
-
const emitRpcTitles = shouldEmitRpcTitles();
|
|
393
|
-
const rpcCapabilities = { compactMessageUpdate: false };
|
|
394
|
-
const decodeError = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
395
|
-
|
|
396
|
-
const pendingExtensionRequests = new Map<string, PendingExtensionRequest>();
|
|
397
|
-
const hostToolBridge = new RpcHostToolBridge(output);
|
|
398
|
-
const hostUriBridge = new RpcHostUriBridge(output);
|
|
399
|
-
const auditLog = new UnattendedAuditLog(defaultAuditPath(session.sessionId, session.sessionManager.getCwd()), {
|
|
400
|
-
redactAnswers: true,
|
|
401
|
-
});
|
|
402
|
-
const recordAudit = (event: { event: string; [key: string]: unknown }) => {
|
|
403
|
-
const payload =
|
|
404
|
-
typeof event.payload === "object" && event.payload !== null
|
|
405
|
-
? (event.payload as Record<string, unknown>)
|
|
406
|
-
: undefined;
|
|
407
|
-
const gateId =
|
|
408
|
-
typeof event.gate_id === "string"
|
|
409
|
-
? event.gate_id
|
|
410
|
-
: typeof payload?.gate_id === "string"
|
|
411
|
-
? payload.gate_id
|
|
412
|
-
: undefined;
|
|
413
|
-
auditLog.record({
|
|
414
|
-
run_id: session.sessionId,
|
|
415
|
-
session_id: session.sessionId,
|
|
416
|
-
actor: typeof event.actor === "string" ? event.actor : undefined,
|
|
417
|
-
event: event.event,
|
|
418
|
-
outcome: auditOutcomeFor(event.event),
|
|
419
|
-
dedupe_key: `${event.event}:${gateId ?? "run"}:${JSON.stringify(payload ?? event)}`,
|
|
420
|
-
gate_id: gateId,
|
|
421
|
-
stage: typeof event.stage === "string" ? (event.stage as never) : undefined,
|
|
422
|
-
kind: typeof event.kind === "string" ? (event.kind as never) : undefined,
|
|
423
|
-
scope: typeof payload?.scope === "string" ? payload.scope : undefined,
|
|
424
|
-
action: typeof payload?.action === "string" ? payload.action : undefined,
|
|
425
|
-
budget: event.event === "budget_exceeded" ? (payload as never) : undefined,
|
|
426
|
-
answer_hash: typeof event.answer_hash === "string" ? event.answer_hash : undefined,
|
|
427
|
-
error: payload && event.event.endsWith("denied") ? payload : undefined,
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
// Unattended control plane (#318/#319/#323/G011): routes negotiate_unattended +
|
|
431
|
-
// workflow_gate_response and lets skill runtimes emit gates over RPC.
|
|
432
|
-
const gateStore = new FileGateStore(
|
|
433
|
-
workflowGatePath(session.sessionManager.getCwd(), session.sessionId, session.sessionId),
|
|
434
|
-
);
|
|
435
|
-
const unattendedControlPlane = new UnattendedSessionControlPlane({
|
|
436
|
-
runId: session.sessionId,
|
|
437
|
-
sessionId: session.sessionId,
|
|
438
|
-
emitFrame: gate => {
|
|
439
|
-
void output(gate);
|
|
440
|
-
},
|
|
441
|
-
store: gateStore,
|
|
442
|
-
audit: recordAudit,
|
|
443
|
-
providerSupportsTokenCostMetrics: modelSupportsTokenCostMetrics(session.model),
|
|
444
|
-
getUsageSnapshot: () => {
|
|
445
|
-
const stats = session.getSessionStats();
|
|
446
|
-
return { tokens: stats.tokens.total, costUsd: stats.cost };
|
|
447
|
-
},
|
|
448
|
-
});
|
|
449
|
-
unattendedControlPlane.recover().catch(err => {
|
|
450
|
-
void output(error(undefined, "workflow_gate_recover", err instanceof Error ? err.message : String(err)));
|
|
451
|
-
});
|
|
452
|
-
session.setWorkflowGateEmitter(unattendedControlPlane);
|
|
453
|
-
|
|
454
|
-
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
455
|
-
const shutdownState = { requested: false };
|
|
456
|
-
let detachStdioInput: (() => void) | undefined;
|
|
457
|
-
let closeCommandScheduler: (() => void) | undefined;
|
|
458
|
-
let shutdownPromise: Promise<never> | undefined;
|
|
459
|
-
let shutdownExitCode = 0;
|
|
460
|
-
let suppressShutdownProtocolOutput = false;
|
|
461
|
-
let shutdownDispatchStopped = false;
|
|
462
|
-
// Tracks in-flight non-blocking command handlers so shutdown can drain them.
|
|
463
|
-
const inFlightCommands = new Set<Promise<void>>();
|
|
464
|
-
function shutdown(
|
|
465
|
-
exitCode: number,
|
|
466
|
-
reason: string,
|
|
467
|
-
suppressProtocolOutput = false,
|
|
468
|
-
stopDispatch = true,
|
|
469
|
-
): Promise<never> {
|
|
470
|
-
shutdownExitCode = Math.max(shutdownExitCode, exitCode);
|
|
471
|
-
suppressShutdownProtocolOutput ||= suppressProtocolOutput;
|
|
472
|
-
if (stopDispatch && !shutdownDispatchStopped) {
|
|
473
|
-
shutdownDispatchStopped = true;
|
|
474
|
-
closeCommandScheduler?.();
|
|
475
|
-
detachStdioInput?.();
|
|
476
|
-
}
|
|
477
|
-
if (shutdownPromise) return shutdownPromise;
|
|
478
|
-
shutdownPromise = (async () => {
|
|
479
|
-
// Let in-flight non-blocking commands (bash/compact/handoff) finish and emit
|
|
480
|
-
// their responses before teardown, bounded so a never-resolving login cannot
|
|
481
|
-
// wedge shutdown (issue 13).
|
|
482
|
-
if (inFlightCommands.size > 0) {
|
|
483
|
-
await Promise.race([Promise.allSettled([...inFlightCommands]), Bun.sleep(5000)]);
|
|
484
|
-
}
|
|
485
|
-
if (!options?.listen) await waitForStdioWrites();
|
|
486
|
-
await unregisterRpcSession(session.sessionId).catch(() => {});
|
|
487
|
-
hostToolBridge.rejectAllPending(`${reason} before host tool execution completed`);
|
|
488
|
-
hostUriBridge.clear(`${reason} before host URI request completed`);
|
|
489
|
-
let cleanupFailure: unknown;
|
|
490
|
-
try {
|
|
491
|
-
await session.sessionManager.ensureOnDisk();
|
|
492
|
-
} catch (failure) {
|
|
493
|
-
cleanupFailure = failure;
|
|
494
|
-
}
|
|
495
|
-
try {
|
|
496
|
-
await session.dispose();
|
|
497
|
-
} catch (failure) {
|
|
498
|
-
if (cleanupFailure === undefined) cleanupFailure = failure;
|
|
499
|
-
}
|
|
500
|
-
if (cleanupFailure !== undefined && !suppressShutdownProtocolOutput) {
|
|
501
|
-
await output(error(undefined, "shutdown", decodeError(cleanupFailure))).catch(() => {});
|
|
502
|
-
}
|
|
503
|
-
if (!options?.listen) {
|
|
504
|
-
await waitForStdioWrites();
|
|
505
|
-
process.stdout.removeListener("error", onStdioError);
|
|
506
|
-
stdioTransportState = "closed";
|
|
507
|
-
}
|
|
508
|
-
const terminalFailure = hasStdioWriteFailure ? stdioWriteFailure : cleanupFailure;
|
|
509
|
-
const finalExitCode = terminalFailure !== undefined || hasStdioWriteFailure ? 1 : shutdownExitCode;
|
|
510
|
-
process.exit(finalExitCode);
|
|
511
|
-
return await new Promise<never>(() => {});
|
|
512
|
-
})();
|
|
513
|
-
return shutdownPromise;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Extension UI context that uses the RPC protocol.
|
|
518
|
-
*/
|
|
519
|
-
class RpcExtensionUIContext implements ExtensionUIContext {
|
|
520
|
-
constructor(
|
|
521
|
-
private pendingRequests: Map<string, PendingExtensionRequest>,
|
|
522
|
-
private output: RpcOutput,
|
|
523
|
-
) {}
|
|
524
|
-
|
|
525
|
-
/** Helper for dialog methods with signal/timeout support */
|
|
526
|
-
#createDialogPromise<T>(
|
|
527
|
-
opts: ExtensionUIDialogOptions | undefined,
|
|
528
|
-
defaultValue: T,
|
|
529
|
-
request: Record<string, unknown>,
|
|
530
|
-
parseResponse: (response: RpcExtensionUIResponse) => T,
|
|
531
|
-
): Promise<T> {
|
|
532
|
-
if (opts?.signal?.aborted) return Promise.resolve(defaultValue);
|
|
533
|
-
|
|
534
|
-
const id = Snowflake.next() as string;
|
|
535
|
-
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
536
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
537
|
-
|
|
538
|
-
const cleanup = () => {
|
|
539
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
540
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
541
|
-
this.pendingRequests.delete(id);
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
const onAbort = () => {
|
|
545
|
-
cleanup();
|
|
546
|
-
resolve(defaultValue);
|
|
547
|
-
};
|
|
548
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
549
|
-
|
|
550
|
-
if (opts?.timeout !== undefined) {
|
|
551
|
-
timeoutId = setTimeout(() => {
|
|
552
|
-
opts.onTimeout?.();
|
|
553
|
-
cleanup();
|
|
554
|
-
resolve(defaultValue);
|
|
555
|
-
}, opts.timeout);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
this.pendingRequests.set(id, {
|
|
559
|
-
resolve: (response: RpcExtensionUIResponse) => {
|
|
560
|
-
cleanup();
|
|
561
|
-
resolve(parseResponse(response));
|
|
562
|
-
},
|
|
563
|
-
reject,
|
|
564
|
-
});
|
|
565
|
-
this.output({ type: "extension_ui_request", id, ...request } as RpcExtensionUIRequest);
|
|
566
|
-
return promise;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
select(title: string, options: string[], dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined> {
|
|
570
|
-
return this.#createDialogPromise(
|
|
571
|
-
dialogOptions,
|
|
572
|
-
undefined,
|
|
573
|
-
{ method: "select", title, options, timeout: dialogOptions?.timeout },
|
|
574
|
-
response => parseValueDialogResponse(response, dialogOptions),
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
confirm(title: string, message: string, dialogOptions?: ExtensionUIDialogOptions): Promise<boolean> {
|
|
579
|
-
return this.#createDialogPromise(
|
|
580
|
-
dialogOptions,
|
|
581
|
-
false,
|
|
582
|
-
{ method: "confirm", title, message, timeout: dialogOptions?.timeout },
|
|
583
|
-
response => {
|
|
584
|
-
if ("cancelled" in response && response.cancelled) {
|
|
585
|
-
if (response.timedOut) dialogOptions?.onTimeout?.();
|
|
586
|
-
return false;
|
|
587
|
-
}
|
|
588
|
-
if ("confirmed" in response) return response.confirmed;
|
|
589
|
-
return false;
|
|
590
|
-
},
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
input(
|
|
595
|
-
title: string,
|
|
596
|
-
placeholder?: string,
|
|
597
|
-
dialogOptions?: ExtensionUIDialogOptions,
|
|
598
|
-
): Promise<string | undefined> {
|
|
599
|
-
return this.#createDialogPromise(
|
|
600
|
-
dialogOptions,
|
|
601
|
-
undefined,
|
|
602
|
-
{ method: "input", title, placeholder, timeout: dialogOptions?.timeout },
|
|
603
|
-
response => parseValueDialogResponse(response, dialogOptions),
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
onTerminalInput(): () => void {
|
|
608
|
-
// Raw terminal input not supported in RPC mode
|
|
609
|
-
return () => {};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
notify(message: string, type?: "info" | "warning" | "error"): void {
|
|
613
|
-
// Fire and forget - no response needed
|
|
614
|
-
this.output({
|
|
615
|
-
type: "extension_ui_request",
|
|
616
|
-
id: Snowflake.next() as string,
|
|
617
|
-
method: "notify",
|
|
618
|
-
message,
|
|
619
|
-
notifyType: type,
|
|
620
|
-
} as RpcExtensionUIRequest);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
setStatus(key: string, text: string | undefined): void {
|
|
624
|
-
// Fire and forget - no response needed
|
|
625
|
-
this.output({
|
|
626
|
-
type: "extension_ui_request",
|
|
627
|
-
id: Snowflake.next() as string,
|
|
628
|
-
method: "setStatus",
|
|
629
|
-
statusKey: key,
|
|
630
|
-
statusText: text,
|
|
631
|
-
} as RpcExtensionUIRequest);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
setWorkingMessage(_message?: string): void {
|
|
635
|
-
// Not supported in RPC mode
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
setWidget(key: string, content: unknown, options?: ExtensionWidgetOptions): void {
|
|
639
|
-
// Only support string arrays in RPC mode - factory functions are ignored
|
|
640
|
-
if (content === undefined || Array.isArray(content)) {
|
|
641
|
-
this.output({
|
|
642
|
-
type: "extension_ui_request",
|
|
643
|
-
id: Snowflake.next() as string,
|
|
644
|
-
method: "setWidget",
|
|
645
|
-
widgetKey: key,
|
|
646
|
-
widgetLines: content as string[] | undefined,
|
|
647
|
-
widgetPlacement: options?.placement,
|
|
648
|
-
} as RpcExtensionUIRequest);
|
|
649
|
-
}
|
|
650
|
-
// Component factories are not supported in RPC mode - would need TUI access
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
setFooter(_factory: unknown): void {
|
|
654
|
-
// Custom footer not supported in RPC mode - requires TUI access
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
setHeader(_factory: unknown): void {
|
|
658
|
-
// Custom header not supported in RPC mode - requires TUI access
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
setTitle(title: string): void {
|
|
662
|
-
// Title updates are low-value noise for most RPC hosts; opt in via GJC_RPC_EMIT_TITLE=1.
|
|
663
|
-
if (!emitRpcTitles) return;
|
|
664
|
-
this.output({
|
|
665
|
-
type: "extension_ui_request",
|
|
666
|
-
id: Snowflake.next() as string,
|
|
667
|
-
method: "setTitle",
|
|
668
|
-
title,
|
|
669
|
-
} as RpcExtensionUIRequest);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
async custom(): Promise<never> {
|
|
673
|
-
// Custom UI not supported in RPC mode
|
|
674
|
-
return undefined as never;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
pasteToEditor(text: string): void {
|
|
678
|
-
// Paste handling not supported in RPC mode - falls back to setEditorText
|
|
679
|
-
this.setEditorText(text);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
setEditorText(text: string): void {
|
|
683
|
-
// Fire and forget - host can implement editor control
|
|
684
|
-
this.output({
|
|
685
|
-
type: "extension_ui_request",
|
|
686
|
-
id: Snowflake.next() as string,
|
|
687
|
-
method: "set_editor_text",
|
|
688
|
-
text,
|
|
689
|
-
} as RpcExtensionUIRequest);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
getEditorText(): string {
|
|
693
|
-
// Synchronous method can't wait for RPC response
|
|
694
|
-
// Host should track editor state locally if needed
|
|
695
|
-
return "";
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
async editor(
|
|
699
|
-
title: string,
|
|
700
|
-
prefill?: string,
|
|
701
|
-
dialogOptions?: ExtensionUIDialogOptions,
|
|
702
|
-
editorOptions?: { promptStyle?: boolean },
|
|
703
|
-
): Promise<string | undefined> {
|
|
704
|
-
return requestRpcEditor(this.pendingRequests, this.output, title, prefill, dialogOptions, editorOptions);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
get theme(): Theme {
|
|
708
|
-
return theme;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
getAllThemes(): Promise<{ name: string; path: string | undefined }[]> {
|
|
712
|
-
return Promise.resolve([]);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
getTheme(_name: string): Promise<Theme | undefined> {
|
|
716
|
-
return Promise.resolve(undefined);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
setTheme(_theme: string | Theme): Promise<{ success: boolean; error?: string }> {
|
|
720
|
-
// Theme switching not supported in RPC mode
|
|
721
|
-
return Promise.resolve({ success: false, error: "Theme switching not supported in RPC mode" });
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
getToolsExpanded() {
|
|
725
|
-
// Tool expansion not supported in RPC mode - no TUI
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
setToolsExpanded(_expanded: boolean) {
|
|
730
|
-
// Tool expansion not supported in RPC mode - no TUI
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
setEditorComponent(): void {
|
|
734
|
-
// Custom editor components not supported in RPC mode
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// Wire up UI context for tool execution (ask tool, etc.) and extensions.
|
|
739
|
-
// A single shared instance routes all responses received on stdin to the
|
|
740
|
-
// correct waiting promise regardless of which code path created the request.
|
|
741
|
-
const rpcUiContext = new RpcExtensionUIContext(pendingExtensionRequests, output);
|
|
742
|
-
setToolUIContext?.(rpcUiContext, true);
|
|
743
|
-
|
|
744
|
-
// Set up extensions with RPC-based UI context
|
|
745
|
-
await initializeExtensions(session, {
|
|
746
|
-
reportSendError: (action, err) => {
|
|
747
|
-
output(error(undefined, action, err.message));
|
|
748
|
-
},
|
|
749
|
-
reportRuntimeError: err => {
|
|
750
|
-
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
751
|
-
},
|
|
752
|
-
onShutdown: () => {
|
|
753
|
-
shutdownState.requested = true;
|
|
754
|
-
},
|
|
755
|
-
uiContext: rpcUiContext,
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
// Output all agent events as canonical agent-wire `event` frames (docs/rpc.md):
|
|
759
|
-
// { type:"event", protocol_version, session_id, seq, frame_id, payload:{ event_type, event } }.
|
|
760
|
-
const eventSequencer = new AgentWireFrameSequencer(session.sessionId);
|
|
761
|
-
const compactEventEncoder = new AgentWireCompactEventEncoder(eventSequencer);
|
|
762
|
-
session.subscribe(event => {
|
|
763
|
-
output(
|
|
764
|
-
rpcCapabilities.compactMessageUpdate
|
|
765
|
-
? toAgentWireCompactEventFrame(event, compactEventEncoder)
|
|
766
|
-
: toAgentWireEventFrame(event, eventSequencer),
|
|
767
|
-
);
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// Handle a single command through the shared agent-wire dispatcher so RPC
|
|
771
|
-
// and bridge mode use one command surface.
|
|
772
|
-
const handleCommand = (command: RpcCommand): Promise<RpcResponse> =>
|
|
773
|
-
dispatchRpcCommand(command, {
|
|
774
|
-
session,
|
|
775
|
-
output,
|
|
776
|
-
hostToolRegistry: hostToolBridge,
|
|
777
|
-
hostUriRegistry: hostUriBridge,
|
|
778
|
-
createUiContext: () => new RpcExtensionUIContext(pendingExtensionRequests, output),
|
|
779
|
-
unattendedControlPlane,
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// Fast-lane commands (cancellation + safe read/control, see
|
|
783
|
-
// isFastLaneRpcCommand) bypass the ordered serial chain and run immediately;
|
|
784
|
-
// everything else runs through a serial chain so causal order is preserved
|
|
785
|
-
// (e.g. an ordered `set_model` after `bash` still applies after the bash
|
|
786
|
-
// result) while the read loop itself never blocks — that is what lets a
|
|
787
|
-
// fast-lane command reach a long-running `bash`/`compact`/`handoff`/`login`
|
|
788
|
-
// instead of being head-of-line-blocked behind it (issue 13).
|
|
789
|
-
const runCommand = async (command: RpcCommand): Promise<void> => {
|
|
790
|
-
let response: RpcResponse;
|
|
791
|
-
try {
|
|
792
|
-
response = await handleCommand(command);
|
|
793
|
-
} catch (err) {
|
|
794
|
-
response = error(command.id, command.type, decodeError(err));
|
|
795
|
-
}
|
|
796
|
-
await output(response);
|
|
797
|
-
};
|
|
798
|
-
const trackCommand = (task: Promise<void>): void => {
|
|
799
|
-
inFlightCommands.add(task);
|
|
800
|
-
void task.then(
|
|
801
|
-
() => inFlightCommands.delete(task),
|
|
802
|
-
() => inFlightCommands.delete(task),
|
|
803
|
-
);
|
|
804
|
-
};
|
|
805
|
-
const scheduler = createRpcCommandScheduler(runCommand, trackCommand);
|
|
806
|
-
const dispatchCommand = scheduler.dispatch;
|
|
807
|
-
closeCommandScheduler = scheduler.close;
|
|
808
|
-
let stdioTerminalizationStarted = false;
|
|
809
|
-
beginStdioTerminalization = () => {
|
|
810
|
-
if (options?.listen || stdioTerminalizationStarted) return;
|
|
811
|
-
stdioTerminalizationStarted = true;
|
|
812
|
-
closeCommandScheduler?.();
|
|
813
|
-
detachStdioInput?.();
|
|
814
|
-
void session.abort({ timeoutMs: 5000, cause: "internal", silent: true }).catch(() => {});
|
|
815
|
-
void shutdown(
|
|
816
|
-
hasStdioWriteFailure ? 1 : 0,
|
|
817
|
-
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
|
|
818
|
-
true,
|
|
819
|
-
).catch(() => {});
|
|
820
|
-
};
|
|
821
|
-
if (stdioTransportState !== "open") beginStdioTerminalization();
|
|
822
|
-
|
|
823
|
-
/**
|
|
824
|
-
* Check if shutdown was requested and perform shutdown if so.
|
|
825
|
-
* Called after handling each command when waiting for the next command.
|
|
826
|
-
*/
|
|
827
|
-
async function checkShutdownRequested(): Promise<void> {
|
|
828
|
-
if (!shutdownState.requested) return;
|
|
829
|
-
await shutdown(0, "RPC shutdown requested");
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// Parse + route a single inbound JSONL frame. Shared by the stdio reader and the
|
|
833
|
-
// persistent UDS server so both transports use the same command surface.
|
|
834
|
-
const inputDecoder = new TextDecoder("utf-8", { fatal: false });
|
|
835
|
-
async function handleInboundLine(text: string): Promise<void> {
|
|
836
|
-
if (!options?.listen && stdioTransportState !== "open") return;
|
|
837
|
-
let parsed: unknown;
|
|
838
|
-
let parseFailure: RpcResponse | undefined;
|
|
839
|
-
try {
|
|
840
|
-
parsed = JSON.parse(text);
|
|
841
|
-
} catch (err) {
|
|
842
|
-
parseFailure = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
|
|
843
|
-
}
|
|
844
|
-
if (parseFailure) {
|
|
845
|
-
output(parseFailure);
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
let response: RpcResponse | undefined;
|
|
850
|
-
try {
|
|
851
|
-
if ((parsed as RpcExtensionUIResponse).type === "extension_ui_response") {
|
|
852
|
-
const extensionResponse = parsed as RpcExtensionUIResponse;
|
|
853
|
-
pendingExtensionRequests.get(extensionResponse.id)?.resolve(extensionResponse);
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
if (isRpcHostToolResult(parsed)) {
|
|
857
|
-
hostToolBridge.handleResult(parsed);
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
if (isRpcHostToolUpdate(parsed)) {
|
|
861
|
-
hostToolBridge.handleUpdate(parsed);
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
if (isRpcHostUriResult(parsed)) {
|
|
865
|
-
hostUriBridge.handleResult(parsed);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
if ((parsed as RpcCommand).type === "set_capabilities") {
|
|
869
|
-
// RPC has no startup handshake; clients negotiate optional event-shape capabilities
|
|
870
|
-
// with this explicit command. Defaults remain legacy/full-frame until accepted here.
|
|
871
|
-
const command = parsed as Extract<RpcCommand, { type: "set_capabilities" }>;
|
|
872
|
-
const requested = Array.isArray(command.capabilities) ? command.capabilities : [];
|
|
873
|
-
const acceptedCapabilities = requested.filter(capability => capability === "compact_message_update");
|
|
874
|
-
rpcCapabilities.compactMessageUpdate = acceptedCapabilities.includes("compact_message_update");
|
|
875
|
-
response = rpcSuccess(command.id, "set_capabilities", {
|
|
876
|
-
acceptedCapabilities,
|
|
877
|
-
unsupported: requested.filter(capability => capability !== "compact_message_update"),
|
|
878
|
-
});
|
|
879
|
-
} else {
|
|
880
|
-
// Ordered commands run through a serial chain to preserve causal order; the
|
|
881
|
-
// reader never blocks, so cancellation commands stay responsive even while a
|
|
882
|
-
// long command is in flight (issue 13).
|
|
883
|
-
dispatchCommand(parsed as RpcCommand);
|
|
884
|
-
await checkShutdownRequested();
|
|
885
|
-
}
|
|
886
|
-
} catch (err) {
|
|
887
|
-
response = error(undefined, "parse", `Failed to parse command: ${decodeError(err)}`);
|
|
888
|
-
}
|
|
889
|
-
if (response) output(response);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// Persistent UDS server (issue 09): keep the AgentSession alive across client
|
|
893
|
-
// reconnects instead of exiting on stdin EOF. Frames route to the active client
|
|
894
|
-
// socket; while no client is connected they are dropped (clients resync via
|
|
895
|
-
// get_state/get_messages on reconnect).
|
|
896
|
-
if (options?.listen) {
|
|
897
|
-
const socketPath = options.listen;
|
|
898
|
-
await prepareRpcSocketPath(socketPath);
|
|
899
|
-
await registerRpcSession({
|
|
900
|
-
sessionId: session.sessionId,
|
|
901
|
-
pid: process.pid,
|
|
902
|
-
transport: "socket",
|
|
903
|
-
cwd: session.sessionManager.getCwd(),
|
|
904
|
-
model: session.model?.id,
|
|
905
|
-
startedAt: new Date().toISOString(),
|
|
906
|
-
endpoint: socketPath,
|
|
907
|
-
}).catch(() => {});
|
|
908
|
-
|
|
909
|
-
let currentSocket: Bun.Socket | undefined;
|
|
910
|
-
let buf = "";
|
|
911
|
-
const detachCurrentSocket = (socket: Bun.Socket): void => {
|
|
912
|
-
if (socket !== currentSocket) return;
|
|
913
|
-
currentSocket = undefined;
|
|
914
|
-
frameSink = noopSink;
|
|
915
|
-
};
|
|
916
|
-
const socketIsLocallyTerminal = (socket: Bun.Socket): boolean => socket.readyState <= 0;
|
|
917
|
-
const handleSocketFailure = (socket: Bun.Socket, failure: unknown): void => {
|
|
918
|
-
if (socket !== currentSocket) return;
|
|
919
|
-
const code = sinkErrorCode(failure);
|
|
920
|
-
const peerClosed =
|
|
921
|
-
isKnownSinkPeerClosedError(failure) &&
|
|
922
|
-
(code === "EPIPE" || (code === "ERR_STREAM_DESTROYED" && socketIsLocallyTerminal(socket)));
|
|
923
|
-
detachCurrentSocket(socket);
|
|
924
|
-
if (peerClosed) return;
|
|
925
|
-
void shutdown(1, "RPC socket output failed", true).catch(() => {});
|
|
926
|
-
};
|
|
927
|
-
const server = Bun.listen({
|
|
928
|
-
unix: socketPath,
|
|
929
|
-
socket: {
|
|
930
|
-
open(socket) {
|
|
931
|
-
currentSocket = socket;
|
|
932
|
-
buf = "";
|
|
933
|
-
frameSink = (line: string) => {
|
|
934
|
-
if (socket !== currentSocket) return;
|
|
935
|
-
try {
|
|
936
|
-
const written = socket.write(line);
|
|
937
|
-
if (written >= 0) return;
|
|
938
|
-
if (socketIsLocallyTerminal(socket)) {
|
|
939
|
-
detachCurrentSocket(socket);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
handleSocketFailure(
|
|
943
|
-
socket,
|
|
944
|
-
Object.assign(new Error("RPC socket write failed"), { code: "ERR_STREAM_DESTROYED" }),
|
|
945
|
-
);
|
|
946
|
-
} catch (failure) {
|
|
947
|
-
handleSocketFailure(socket, failure);
|
|
948
|
-
}
|
|
949
|
-
};
|
|
950
|
-
void output({ type: "ready" });
|
|
951
|
-
},
|
|
952
|
-
data(socket, data) {
|
|
953
|
-
if (socket !== currentSocket) return;
|
|
954
|
-
buf += inputDecoder.decode(data);
|
|
955
|
-
while (true) {
|
|
956
|
-
const nl = buf.indexOf("\n");
|
|
957
|
-
if (nl < 0) break;
|
|
958
|
-
const text = buf.slice(0, nl).trim();
|
|
959
|
-
buf = buf.slice(nl + 1);
|
|
960
|
-
if (text) void handleInboundLine(text);
|
|
961
|
-
}
|
|
962
|
-
},
|
|
963
|
-
close(socket) {
|
|
964
|
-
detachCurrentSocket(socket);
|
|
965
|
-
},
|
|
966
|
-
error(socket, failure) {
|
|
967
|
-
handleSocketFailure(socket, failure);
|
|
968
|
-
},
|
|
969
|
-
},
|
|
970
|
-
});
|
|
971
|
-
await verifyRpcSocketAfterListen(socketPath);
|
|
972
|
-
void server;
|
|
973
|
-
|
|
974
|
-
const onSignal = (): void => {
|
|
975
|
-
void shutdown(0, "RPC socket server signal");
|
|
976
|
-
};
|
|
977
|
-
process.on("SIGINT", onSignal);
|
|
978
|
-
process.on("SIGTERM", onSignal);
|
|
979
|
-
// Block until an explicit shutdown (signal/extension) calls process.exit.
|
|
980
|
-
await new Promise<never>(() => {});
|
|
981
|
-
throw new Error("RPC socket server returned unexpectedly");
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
// Register this stdio RPC session so other processes can discover it (issue 10).
|
|
985
|
-
await registerRpcSession({
|
|
986
|
-
sessionId: session.sessionId,
|
|
987
|
-
pid: process.pid,
|
|
988
|
-
transport: "stdio",
|
|
989
|
-
cwd: session.sessionManager.getCwd(),
|
|
990
|
-
model: session.model?.id,
|
|
991
|
-
startedAt: new Date().toISOString(),
|
|
992
|
-
}).catch(() => {});
|
|
993
|
-
|
|
994
|
-
// Listen for JSONL input using Bun's stdin. Parse frame-by-frame so a malformed
|
|
995
|
-
// command reports a parse error without poisoning the whole long-lived RPC session.
|
|
996
|
-
const inputLines = readLines(Bun.stdin.stream());
|
|
997
|
-
const inputIterator = inputLines[Symbol.asyncIterator]();
|
|
998
|
-
detachStdioInput = () => {
|
|
999
|
-
const detached = inputIterator.return?.(undefined);
|
|
1000
|
-
if (detached) void detached.catch(() => {});
|
|
1001
|
-
};
|
|
1002
|
-
let inputFailure: unknown;
|
|
1003
|
-
try {
|
|
1004
|
-
await output({ type: "ready" });
|
|
1005
|
-
while (stdioTransportState === "open") {
|
|
1006
|
-
const next = await Promise.race([inputIterator.next(), stdioFatalFailure.promise]);
|
|
1007
|
-
if (next.done) break;
|
|
1008
|
-
const text = inputDecoder.decode(next.value).trim();
|
|
1009
|
-
if (text) await handleInboundLine(text);
|
|
1010
|
-
}
|
|
1011
|
-
} catch (failure) {
|
|
1012
|
-
inputFailure = failure;
|
|
1013
|
-
} finally {
|
|
1014
|
-
detachStdioInput = undefined;
|
|
1015
|
-
}
|
|
1016
|
-
if (inputFailure !== undefined) {
|
|
1017
|
-
return await shutdown(
|
|
1018
|
-
1,
|
|
1019
|
-
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdin failed",
|
|
1020
|
-
hasStdioWriteFailure,
|
|
1021
|
-
);
|
|
1022
|
-
}
|
|
1023
|
-
if (stdioTransportState === "open") return await shutdown(0, "RPC client disconnected", false, false);
|
|
1024
|
-
return await shutdown(
|
|
1025
|
-
hasStdioWriteFailure ? 1 : 0,
|
|
1026
|
-
hasStdioWriteFailure ? "RPC stdout write failed" : "RPC stdout peer disconnected",
|
|
1027
|
-
true,
|
|
1028
|
-
);
|
|
1029
|
-
}
|