@songsid/agend 2.3.4-beta.5 → 2.3.5-alpha.2
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/README.md +2 -0
- package/README.zh-TW.md +2 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/dist/agent-endpoint.d.ts +5 -1
- package/dist/agent-endpoint.js +12 -2
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/claude-code.d.ts +18 -12
- package/dist/backend/claude-code.js +225 -136
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-package.d.ts +5 -2
- package/dist/backend/codex-package.js +1 -5
- package/dist/backend/codex-package.js.map +1 -1
- package/dist/backend/codex.d.ts +136 -113
- package/dist/backend/codex.js +1271 -1219
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok-model-cache.d.ts +7 -0
- package/dist/backend/grok-model-cache.js +65 -0
- package/dist/backend/grok-model-cache.js.map +1 -0
- package/dist/backend/grok.d.ts +2 -0
- package/dist/backend/grok.js +22 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/native-pane.d.ts +9 -0
- package/dist/backend/native-pane.js +59 -0
- package/dist/backend/native-pane.js.map +1 -0
- package/dist/backend/types.d.ts +7 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +2 -0
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +59 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +16 -0
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +62 -5
- package/dist/daemon.js +760 -240
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +15 -3
- package/dist/delivery-consumed-watch.js +23 -1
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.d.ts +5 -0
- package/dist/delivery-outbox.js +32 -4
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +2 -35
- package/dist/delivery-reconciliation.js +2 -156
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -2
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +125 -2
- package/dist/fleet-manager.js +1157 -27
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-windows-runtime.d.ts +92 -0
- package/dist/fleet-windows-runtime.js +373 -0
- package/dist/fleet-windows-runtime.js.map +1 -0
- package/dist/instance-config-impact.js +4 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +20 -3
- package/dist/instance-lifecycle.js +358 -156
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/local-pane-endpoint.d.ts +46 -0
- package/dist/local-pane-endpoint.js +189 -0
- package/dist/local-pane-endpoint.js.map +1 -0
- package/dist/local-pane-launcher.d.ts +2 -0
- package/dist/local-pane-launcher.js +38 -0
- package/dist/local-pane-launcher.js.map +1 -0
- package/dist/local-pane-stage.ps1 +101 -0
- package/dist/locale.js +34 -0
- package/dist/locale.js.map +1 -1
- package/dist/model-catalog-defaults.d.ts +14 -0
- package/dist/model-catalog-defaults.js +20 -0
- package/dist/model-catalog-defaults.js.map +1 -0
- package/dist/native-agent-admission.d.ts +7 -0
- package/dist/native-agent-admission.js +26 -0
- package/dist/native-agent-admission.js.map +1 -0
- package/dist/native-daemon-client.d.ts +13 -0
- package/dist/native-daemon-client.js +28 -0
- package/dist/native-daemon-client.js.map +1 -0
- package/dist/native-instance-api.d.ts +14 -0
- package/dist/native-instance-api.js +205 -0
- package/dist/native-instance-api.js.map +1 -0
- package/dist/native-instance-input.d.ts +84 -0
- package/dist/native-instance-input.js +54 -0
- package/dist/native-instance-input.js.map +1 -0
- package/dist/native-instance-registry.d.ts +32 -0
- package/dist/native-instance-registry.js +105 -0
- package/dist/native-instance-registry.js.map +1 -0
- package/dist/native-mcp.d.ts +40 -0
- package/dist/native-mcp.js +278 -0
- package/dist/native-mcp.js.map +1 -0
- package/dist/native-model-catalog.d.ts +5 -0
- package/dist/native-model-catalog.js +76 -0
- package/dist/native-model-catalog.js.map +1 -0
- package/dist/native-pane-crash-budget.d.ts +20 -0
- package/dist/native-pane-crash-budget.js +60 -0
- package/dist/native-pane-crash-budget.js.map +1 -0
- package/dist/native-pane-create.d.ts +26 -0
- package/dist/native-pane-create.js +53 -0
- package/dist/native-pane-create.js.map +1 -0
- package/dist/native-pane-lifecycle.d.ts +65 -0
- package/dist/native-pane-lifecycle.js +227 -0
- package/dist/native-pane-lifecycle.js.map +1 -0
- package/dist/native-pane-manager.d.ts +58 -0
- package/dist/native-pane-manager.js +563 -0
- package/dist/native-pane-manager.js.map +1 -0
- package/dist/native-pane-output-log.d.ts +15 -0
- package/dist/native-pane-output-log.js +108 -0
- package/dist/native-pane-output-log.js.map +1 -0
- package/dist/native-pane-reconciliation.d.ts +4 -0
- package/dist/native-pane-reconciliation.js +40 -0
- package/dist/native-pane-reconciliation.js.map +1 -0
- package/dist/native-pane-recovery.d.ts +12 -0
- package/dist/native-pane-recovery.js +10 -0
- package/dist/native-pane-recovery.js.map +1 -0
- package/dist/native-pane-tools.d.ts +2 -0
- package/dist/native-pane-tools.js +14 -0
- package/dist/native-pane-tools.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -0
- package/dist/needs-you-hub.js +1 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +12 -1
- package/dist/needs-you.js +8 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +16 -2
- package/dist/outbound-handlers.js +67 -3
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +38 -0
- package/dist/outbound-schemas.js +12 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-port.d.ts +107 -0
- package/dist/pane-port.js +2 -0
- package/dist/pane-port.js.map +1 -0
- package/dist/runtime-api.d.ts +8 -0
- package/dist/runtime-api.js +96 -0
- package/dist/runtime-api.js.map +1 -0
- package/dist/runtime-assets/@xterm/addon-serialize/LICENSE +21 -0
- package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.js +2 -0
- package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.mjs +19 -0
- package/dist/runtime-assets/@xterm/addon-serialize/package.json +29 -0
- package/dist/runtime-assets/@xterm/headless/LICENSE +21 -0
- package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.js +2 -0
- package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.mjs +46 -0
- package/dist/runtime-assets/@xterm/headless/package.json +29 -0
- package/dist/runtime-assets/node-pty/CONPTY-LICENSE +21 -0
- package/dist/runtime-assets/node-pty/CONPTY-NOTICE.md +503 -0
- package/dist/runtime-assets/node-pty/LICENSE +69 -0
- package/dist/runtime-assets/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/dist/runtime-assets/node-pty/lib/eventEmitter2.js +47 -0
- package/dist/runtime-assets/node-pty/lib/index.js +52 -0
- package/dist/runtime-assets/node-pty/lib/interfaces.js +7 -0
- package/dist/runtime-assets/node-pty/lib/shared/conout.js +11 -0
- package/dist/runtime-assets/node-pty/lib/terminal.js +190 -0
- package/dist/runtime-assets/node-pty/lib/types.js +7 -0
- package/dist/runtime-assets/node-pty/lib/unixTerminal.js +346 -0
- package/dist/runtime-assets/node-pty/lib/utils.js +39 -0
- package/dist/runtime-assets/node-pty/lib/windowsConoutConnection.js +125 -0
- package/dist/runtime-assets/node-pty/lib/windowsPtyAgent.js +320 -0
- package/dist/runtime-assets/node-pty/lib/windowsTerminal.js +199 -0
- package/dist/runtime-assets/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/dist/runtime-assets/node-pty/package.json +64 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/dist/runtime-assets/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/dist/runtime-bundle.d.ts +17 -0
- package/dist/runtime-bundle.js +147 -0
- package/dist/runtime-bundle.js.map +1 -0
- package/dist/runtime-cli.d.ts +2 -0
- package/dist/runtime-cli.js +117 -0
- package/dist/runtime-cli.js.map +1 -0
- package/dist/runtime-client-store.d.ts +49 -0
- package/dist/runtime-client-store.js +292 -0
- package/dist/runtime-client-store.js.map +1 -0
- package/dist/runtime-client.d.ts +100 -0
- package/dist/runtime-client.js +700 -0
- package/dist/runtime-client.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +82 -0
- package/dist/runtime-coordinator.js +864 -0
- package/dist/runtime-coordinator.js.map +1 -0
- package/dist/runtime-pane-assets.d.ts +17 -0
- package/dist/runtime-pane-assets.js +23 -0
- package/dist/runtime-pane-assets.js.map +1 -0
- package/dist/runtime-pane-boot.d.ts +2 -0
- package/dist/runtime-pane-boot.js +24 -0
- package/dist/runtime-pane-boot.js.map +1 -0
- package/dist/runtime-pane-codex-index.d.ts +1 -0
- package/dist/runtime-pane-codex-index.js +47 -0
- package/dist/runtime-pane-codex-index.js.map +1 -0
- package/dist/runtime-pane-config.d.ts +16 -0
- package/dist/runtime-pane-config.js +54 -0
- package/dist/runtime-pane-config.js.map +1 -0
- package/dist/runtime-pane-host.d.ts +43 -0
- package/dist/runtime-pane-host.js +403 -0
- package/dist/runtime-pane-host.js.map +1 -0
- package/dist/runtime-pane-job-owner.d.ts +1 -0
- package/dist/runtime-pane-job-owner.js +29 -0
- package/dist/runtime-pane-job-owner.js.map +1 -0
- package/dist/runtime-pane-job.ps1 +116 -0
- package/dist/runtime-pane-link.d.ts +40 -0
- package/dist/runtime-pane-link.js +246 -0
- package/dist/runtime-pane-link.js.map +1 -0
- package/dist/runtime-pane-mcp-protocol.d.ts +32 -0
- package/dist/runtime-pane-mcp-protocol.js +39 -0
- package/dist/runtime-pane-mcp-protocol.js.map +1 -0
- package/dist/runtime-pane-mcp-relay.d.ts +17 -0
- package/dist/runtime-pane-mcp-relay.js +70 -0
- package/dist/runtime-pane-mcp-relay.js.map +1 -0
- package/dist/runtime-pane-mcp.d.ts +43 -0
- package/dist/runtime-pane-mcp.js +137 -0
- package/dist/runtime-pane-mcp.js.map +1 -0
- package/dist/runtime-pane-native.d.ts +45 -0
- package/dist/runtime-pane-native.js +65 -0
- package/dist/runtime-pane-native.js.map +1 -0
- package/dist/runtime-pane-output.d.ts +19 -0
- package/dist/runtime-pane-output.js +53 -0
- package/dist/runtime-pane-output.js.map +1 -0
- package/dist/runtime-pane-port.d.ts +134 -0
- package/dist/runtime-pane-port.js +537 -0
- package/dist/runtime-pane-port.js.map +1 -0
- package/dist/runtime-pane-private.d.ts +14 -0
- package/dist/runtime-pane-private.js +51 -0
- package/dist/runtime-pane-private.js.map +1 -0
- package/dist/runtime-pane-private.ps1 +14 -0
- package/dist/runtime-pane-process.d.ts +4 -0
- package/dist/runtime-pane-process.js +108 -0
- package/dist/runtime-pane-process.js.map +1 -0
- package/dist/runtime-pane-protocol.d.ts +169 -0
- package/dist/runtime-pane-protocol.js +189 -0
- package/dist/runtime-pane-protocol.js.map +1 -0
- package/dist/runtime-pane-screen.d.ts +19 -0
- package/dist/runtime-pane-screen.js +76 -0
- package/dist/runtime-pane-screen.js.map +1 -0
- package/dist/runtime-pane-service.d.ts +11 -0
- package/dist/runtime-pane-service.js +221 -0
- package/dist/runtime-pane-service.js.map +1 -0
- package/dist/runtime-pane-stdio-protocol.d.ts +54 -0
- package/dist/runtime-pane-stdio-protocol.js +94 -0
- package/dist/runtime-pane-stdio-protocol.js.map +1 -0
- package/dist/runtime-pane-stdio.d.ts +3 -0
- package/dist/runtime-pane-stdio.js +123 -0
- package/dist/runtime-pane-stdio.js.map +1 -0
- package/dist/runtime-pane-store.d.ts +33 -0
- package/dist/runtime-pane-store.js +164 -0
- package/dist/runtime-pane-store.js.map +1 -0
- package/dist/runtime-pane-transcript-protocol.d.ts +43 -0
- package/dist/runtime-pane-transcript-protocol.js +58 -0
- package/dist/runtime-pane-transcript-protocol.js.map +1 -0
- package/dist/runtime-pane-transcript.d.ts +45 -0
- package/dist/runtime-pane-transcript.js +605 -0
- package/dist/runtime-pane-transcript.js.map +1 -0
- package/dist/runtime-pane-worker.d.ts +1 -0
- package/dist/runtime-pane-worker.js +154 -0
- package/dist/runtime-pane-worker.js.map +1 -0
- package/dist/runtime-protocol.d.ts +149 -0
- package/dist/runtime-protocol.js +19 -0
- package/dist/runtime-protocol.js.map +1 -0
- package/dist/runtime-switches.d.ts +79 -0
- package/dist/runtime-switches.js +90 -0
- package/dist/runtime-switches.js.map +1 -0
- package/dist/runtime-transcript-delta-reader.d.ts +23 -0
- package/dist/runtime-transcript-delta-reader.js +74 -0
- package/dist/runtime-transcript-delta-reader.js.map +1 -0
- package/dist/runtime-transcript-source.d.ts +36 -0
- package/dist/runtime-transcript-source.js +189 -0
- package/dist/runtime-transcript-source.js.map +1 -0
- package/dist/runtime-windows-bridge.d.ts +41 -0
- package/dist/runtime-windows-bridge.js +157 -0
- package/dist/runtime-windows-bridge.js.map +1 -0
- package/dist/settings-api.js +58 -0
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +28 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +15 -1
- package/dist/settings-confirmation.js +46 -9
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +23 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +11 -2
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tmux-control.d.ts +2 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +2 -1
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-pane-host.d.ts +26 -0
- package/dist/tmux-pane-host.js +25 -0
- package/dist/tmux-pane-host.js.map +1 -0
- package/dist/topic-commands.js +3 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +1 -1
- package/dist/transcript-delta-reader.js +1 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-monitor.d.ts +3 -0
- package/dist/transcript-monitor.js +22 -46
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-needles.d.ts +35 -0
- package/dist/transcript-needles.js +158 -0
- package/dist/transcript-needles.js.map +1 -0
- package/dist/transcript-records.d.ts +21 -0
- package/dist/transcript-records.js +140 -0
- package/dist/transcript-records.js.map +1 -0
- package/dist/transcript-sources.d.ts +5 -0
- package/dist/transcript-sources.js +8 -79
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/ui/agent-proposals.js +90 -0
- package/dist/ui/agent-runtime.js +106 -0
- package/dist/ui/chat-store.js +13 -2
- package/dist/ui/chat-strings.js +48 -0
- package/dist/ui/cli-picker.js +42 -0
- package/dist/ui/native-instance-settings.js +47 -0
- package/dist/ui/native-pane-recovery.js +45 -0
- package/dist/ui/panel-chat.js +61 -32
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +99 -18
- package/dist/ui/panel-needs.js +152 -3
- package/dist/ui/panel-runtimes.js +231 -0
- package/dist/ui/panel-settings.js +14 -8
- package/dist/ui/panel-windows-agent.js +86 -0
- package/dist/ui/panel-windows-cli.js +143 -0
- package/dist/ui/provider-marks.js +83 -0
- package/dist/ui/runtime-catalog.js +55 -0
- package/dist/ui/settings-catalog.js +37 -10
- package/dist/ui/settings-confirm.js +3 -1
- package/dist/ui/settings-dialogs.js +14 -5
- package/dist/ui/settings-registry.js +5 -0
- package/dist/ui/settings-strings.js +14 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-route.js +6 -2
- package/dist/ui/shared/app-shell.js +3 -2
- package/dist/ui/shared/app-store.js +1 -0
- package/dist/ui/shared/app-stream.js +3 -1
- package/dist/ui/shared/app.css +301 -0
- package/dist/ui/shared/app.js +4 -0
- package/dist/ui/shared/header-tools.js +6 -4
- package/dist/ui/shared/instance-nav.js +20 -4
- package/dist/ui/shared/instance-switch.js +4 -3
- package/dist/ui/shared/ui-menu.js +28 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/windows-agents-nav.js +2 -0
- package/dist/ui/windows-cli-catalog.js +79 -0
- package/dist/update-check.d.ts +8 -2
- package/dist/update-check.js +5 -2
- package/dist/update-check.js.map +1 -1
- package/dist/web-api.d.ts +25 -0
- package/dist/web-api.js +171 -6
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +19 -2
- package/dist/web-chat-history.js +72 -3
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -1
- package/dist/web-shell-routes.js +7 -2
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/windows-agents.d.ts +112 -0
- package/dist/windows-agents.js +463 -0
- package/dist/windows-agents.js.map +1 -0
- package/dist/windows-cli-api.d.ts +23 -0
- package/dist/windows-cli-api.js +158 -0
- package/dist/windows-cli-api.js.map +1 -0
- package/dist/windows-cli-providers.d.ts +115 -0
- package/dist/windows-cli-providers.js +429 -0
- package/dist/windows-cli-providers.js.map +1 -0
- package/dist/windows-cli-worker.d.ts +58 -0
- package/dist/windows-cli-worker.js +479 -0
- package/dist/windows-cli-worker.js.map +1 -0
- package/dist/windows-cli.d.ts +115 -0
- package/dist/windows-cli.js +402 -0
- package/dist/windows-cli.js.map +1 -0
- package/dist/windows-native-mcp.d.ts +19 -0
- package/dist/windows-native-mcp.js +143 -0
- package/dist/windows-native-mcp.js.map +1 -0
- package/dist/windows-pane-mcp.d.ts +26 -0
- package/dist/windows-pane-mcp.js +187 -0
- package/dist/windows-pane-mcp.js.map +1 -0
- package/package.json +9 -3
- package/templates/runtime-assets/conpty-LICENSE +21 -0
- package/templates/runtime-assets/conpty-NOTICE.md +503 -0
- package/templates/runtime-assets/local-pane-stage.ps1 +101 -0
- package/templates/runtime-assets/runtime-pane-job.ps1 +116 -0
- package/templates/runtime-assets/runtime-pane-private.ps1 +14 -0
- package/templates/runtime-assets/xterm-LICENSE +21 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
/** Standalone, outbound-only native runtime. Imports no Fleet, database, bot or Linux native dependency. */
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { readPaneSystemBootId } from "./runtime-pane-boot.js";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { hostname } from "node:os";
|
|
6
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { WindowsCliBridge, nativeWindowsTransport, WINDOWS_CLI_INVENTORY_FILE } from "./windows-cli.js";
|
|
9
|
+
import { validateNativeTask, validWindowsAgentId } from "./windows-cli-worker.js";
|
|
10
|
+
import { validNativeSessionId } from "./windows-cli-providers.js";
|
|
11
|
+
import { runtimeJobDigest, validateRuntimeServer } from "./runtime-client-store.js";
|
|
12
|
+
import { RUNTIME_ROTATE_PATH, runtimeClaimFingerprint } from "./runtime-protocol.js";
|
|
13
|
+
import { validatePaneMcpBody } from "./runtime-pane-mcp-protocol.js";
|
|
14
|
+
import { validateRuntimePaneRequest, validateRuntimePaneShutdown } from "./runtime-pane-protocol.js";
|
|
15
|
+
export { RUNTIME_PANE_MODULES } from "./runtime-pane-assets.js";
|
|
16
|
+
const MAX_FRAME = 256 * 1024;
|
|
17
|
+
const MAX_TOOL = 64 * 1024;
|
|
18
|
+
function object(value) { return !!value && typeof value === "object" && !Array.isArray(value); }
|
|
19
|
+
function message(error) { return error instanceof Error ? error.message.slice(0, 1000) : "Runtime operation failed"; }
|
|
20
|
+
function checkedRun(value) {
|
|
21
|
+
if (!object(value) || Object.keys(value).some(key => !["task", "resumeSessionId", "context", "mcp"].includes(key)))
|
|
22
|
+
throw new Error("Invalid runtime run payload");
|
|
23
|
+
const task = validateNativeTask(value.task);
|
|
24
|
+
if (value.resumeSessionId !== undefined && !validNativeSessionId(task.cli, value.resumeSessionId))
|
|
25
|
+
throw new Error("Invalid native conversation ID");
|
|
26
|
+
if (value.context !== undefined && (typeof value.context !== "string" || value.context.length > 2000 || value.context.includes("\0")))
|
|
27
|
+
throw new Error("Invalid native context");
|
|
28
|
+
if (value.mcp !== undefined && (!object(value.mcp) || Object.keys(value.mcp).some(key => key !== "tools")
|
|
29
|
+
|| !Array.isArray(value.mcp.tools) || !value.mcp.tools.length || value.mcp.tools.length > 64
|
|
30
|
+
|| value.mcp.tools.some(tool => typeof tool !== "string" || !/^[a-z][a-z0-9_]{0,63}$/.test(tool))))
|
|
31
|
+
throw new Error("Invalid scoped MCP request");
|
|
32
|
+
return { task, resumeSessionId: value.resumeSessionId, context: value.context,
|
|
33
|
+
mcp: value.mcp };
|
|
34
|
+
}
|
|
35
|
+
function jsonBody(req) {
|
|
36
|
+
return new Promise((accept, reject) => {
|
|
37
|
+
let text = "", bytes = 0;
|
|
38
|
+
req.setTimeout(10_000, () => { reject(new Error("MCP request timed out")); req.destroy(); });
|
|
39
|
+
req.setEncoding("utf8");
|
|
40
|
+
req.on("data", (part) => { bytes += Buffer.byteLength(part); if (bytes > MAX_TOOL) {
|
|
41
|
+
reject(new Error("MCP request is too large"));
|
|
42
|
+
req.destroy();
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
text += part; });
|
|
46
|
+
req.on("error", reject);
|
|
47
|
+
req.on("end", () => { try {
|
|
48
|
+
accept(JSON.parse(text));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
reject(new Error("Invalid MCP JSON"));
|
|
52
|
+
} });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export class RuntimeClient {
|
|
56
|
+
options;
|
|
57
|
+
bootId = randomUUID();
|
|
58
|
+
bridge;
|
|
59
|
+
socket = null;
|
|
60
|
+
welcomed = false;
|
|
61
|
+
stopped = true;
|
|
62
|
+
closed = false;
|
|
63
|
+
unlock = null;
|
|
64
|
+
reconnectTimer = null;
|
|
65
|
+
reconnectAttempt = 0;
|
|
66
|
+
graceTimer = null;
|
|
67
|
+
pollTimer = null;
|
|
68
|
+
credentials = null;
|
|
69
|
+
startupRotation = null;
|
|
70
|
+
paneHost = null;
|
|
71
|
+
paneOpening = null;
|
|
72
|
+
machinePaneLimit = 4;
|
|
73
|
+
paneEpoch = randomUUID();
|
|
74
|
+
active = new Set();
|
|
75
|
+
snapshots = new Map();
|
|
76
|
+
pendingPaneTools = new Map();
|
|
77
|
+
pendingTools = new Map();
|
|
78
|
+
mcpTokens = new Map();
|
|
79
|
+
mcpServer = null;
|
|
80
|
+
mcpOpening = null;
|
|
81
|
+
mcpPort = 0;
|
|
82
|
+
inventory = null;
|
|
83
|
+
stoppedListeners = new Set();
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.options = options;
|
|
86
|
+
this.bridge = options.bridge ?? new WindowsCliBridge(nativeWindowsTransport, join(options.store.directory, WINDOWS_CLI_INVENTORY_FILE));
|
|
87
|
+
}
|
|
88
|
+
report(text) { this.options.report?.(text); }
|
|
89
|
+
online() { return this.welcomed && this.socket?.readyState === WebSocket.OPEN; }
|
|
90
|
+
waitUntilStopped() { return this.stopped && this.closed ? Promise.resolve() : new Promise(done => this.stoppedListeners.add(done)); }
|
|
91
|
+
async start() {
|
|
92
|
+
if (!this.stopped)
|
|
93
|
+
return;
|
|
94
|
+
let credentials = this.options.store.credentials();
|
|
95
|
+
if (!credentials)
|
|
96
|
+
throw new Error("Pair this runtime before connecting");
|
|
97
|
+
validateRuntimeServer(credentials.server);
|
|
98
|
+
this.stopped = false;
|
|
99
|
+
this.closed = false;
|
|
100
|
+
try {
|
|
101
|
+
const systemBootId = process.platform === "win32" ? await readPaneSystemBootId() : undefined;
|
|
102
|
+
if (this.stopped)
|
|
103
|
+
throw Error("Runtime start was cancelled");
|
|
104
|
+
this.unlock = systemBootId ? this.options.store.lock(this.bootId, systemBootId) : this.options.store.lock(this.bootId);
|
|
105
|
+
if (credentials.pendingCredential) {
|
|
106
|
+
const rotating = this.startupRotation = rotateRuntimeCredentials({ store: this.options.store });
|
|
107
|
+
try {
|
|
108
|
+
credentials = await rotating;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (this.startupRotation === rotating)
|
|
112
|
+
this.startupRotation = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (this.stopped)
|
|
116
|
+
throw Error("Runtime start was cancelled");
|
|
117
|
+
this.options.store.recoverInterrupted();
|
|
118
|
+
this.credentials = credentials;
|
|
119
|
+
// Connect immediately, then publish the last administrator detection from the private profile.
|
|
120
|
+
// A cached read starts no CLI. Only an explicit probe command with force:true performs detection.
|
|
121
|
+
this.connect();
|
|
122
|
+
// A foreground runtime must remain alive while an idle connection is down. Promises and unref'ed retry
|
|
123
|
+
// timers do not keep Node running after the last socket closes; stop() owns and clears this liveness ref.
|
|
124
|
+
this.pollTimer = setInterval(() => this.poll(), this.options.pollMs ?? 250);
|
|
125
|
+
void this.refreshInventory().catch(() => this.report("CLI discovery failed; refresh it from AgEnD after checking the local installation."));
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.stopped = true;
|
|
129
|
+
this.unlock?.();
|
|
130
|
+
this.unlock = null;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
connect() {
|
|
135
|
+
if (this.stopped || !this.credentials)
|
|
136
|
+
return;
|
|
137
|
+
const url = validateRuntimeServer(this.credentials.server);
|
|
138
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
139
|
+
url.pathname = "/runtime/v1/connect";
|
|
140
|
+
const socket = this.socket = this.options.socketFactory?.(url.href, this.credentials.credential)
|
|
141
|
+
?? new WebSocket(url.href, { headers: { Authorization: `Bearer ${this.credentials.credential}` }, followRedirects: false,
|
|
142
|
+
maxPayload: MAX_FRAME, handshakeTimeout: 15_000, perMessageDeflate: false });
|
|
143
|
+
this.welcomed = false;
|
|
144
|
+
this.paneEpoch = randomUUID();
|
|
145
|
+
socket.on("open", () => {
|
|
146
|
+
if (this.stopped || this.socket !== socket) {
|
|
147
|
+
socket.close();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.send({ v: 1, type: "hello", bootId: this.bootId, name: this.credentials.name, platform: process.platform,
|
|
151
|
+
...(process.platform === "win32" || this.options.createPaneHost || this.options.openPaneLink ? { paneProtocol: 1 } : {}),
|
|
152
|
+
...(this.inventory ? { inventory: this.inventory } : {}) });
|
|
153
|
+
});
|
|
154
|
+
socket.on("message", (bytes, binary) => {
|
|
155
|
+
const size = Array.isArray(bytes) ? bytes.reduce((sum, part) => sum + part.byteLength, 0) : bytes.byteLength;
|
|
156
|
+
if (binary || size > MAX_FRAME || this.socket !== socket) {
|
|
157
|
+
socket.close(1008, "Invalid runtime frame");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
void this.receive(bytes.toString()).catch(() => socket.close(1008, "Invalid runtime protocol"));
|
|
161
|
+
});
|
|
162
|
+
socket.on("error", () => { });
|
|
163
|
+
socket.on("unexpected-response", (_req, response) => {
|
|
164
|
+
response.resume();
|
|
165
|
+
if (response.statusCode === 401 || response.statusCode === 403) {
|
|
166
|
+
this.report("Runtime credential was refused. Re-pair after the machine is authorized.");
|
|
167
|
+
void this.stop();
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
socket.terminate();
|
|
171
|
+
});
|
|
172
|
+
socket.on("close", (code, reason) => {
|
|
173
|
+
if (this.socket !== socket)
|
|
174
|
+
return;
|
|
175
|
+
this.socket = null;
|
|
176
|
+
this.welcomed = false;
|
|
177
|
+
this.paneEpoch = randomUUID();
|
|
178
|
+
if (this.paneHost && "connectionChanged" in this.paneHost)
|
|
179
|
+
this.paneHost.connectionChanged();
|
|
180
|
+
this.rejectTools();
|
|
181
|
+
if (this.stopped)
|
|
182
|
+
return;
|
|
183
|
+
if (code === 1008) {
|
|
184
|
+
this.report("Runtime connection was refused, revoked or replaced. Check pairing and protocol compatibility before reconnecting.");
|
|
185
|
+
void this.stop({ retirePanes: reason.toString() === "Machine revoked" });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.report("Disconnected. Existing execution is not replayed; reconnecting…");
|
|
189
|
+
if (!this.graceTimer)
|
|
190
|
+
this.graceTimer = setTimeout(() => {
|
|
191
|
+
this.graceTimer = null;
|
|
192
|
+
for (const id of this.active) {
|
|
193
|
+
try {
|
|
194
|
+
this.bridge.cancel(id);
|
|
195
|
+
}
|
|
196
|
+
catch { /* state reconciles in poll */ }
|
|
197
|
+
}
|
|
198
|
+
this.report("The coordinator remained disconnected; cancellation was requested for the active execution.");
|
|
199
|
+
}, this.options.disconnectGraceMs ?? 45_000);
|
|
200
|
+
this.graceTimer.unref?.();
|
|
201
|
+
const base = Math.min(30_000, 2000 * 2 ** Math.min(this.reconnectAttempt++, 4));
|
|
202
|
+
const delay = this.options.reconnectMs ?? Math.min(30_000, Math.round(base * (0.75 + Math.random() * 0.5)));
|
|
203
|
+
this.reconnectTimer = setTimeout(() => { this.reconnectTimer = null; this.connect(); }, delay);
|
|
204
|
+
this.reconnectTimer.unref?.();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
send(value) {
|
|
208
|
+
if (this.socket?.readyState !== WebSocket.OPEN)
|
|
209
|
+
return;
|
|
210
|
+
const frame = JSON.stringify(value);
|
|
211
|
+
if (Buffer.byteLength(frame) > MAX_FRAME)
|
|
212
|
+
throw new Error("Runtime frame limit exceeded");
|
|
213
|
+
if (this.socket.bufferedAmount > 2 * MAX_FRAME) {
|
|
214
|
+
this.socket.close(1013, "Runtime backpressure");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
this.socket.send(frame);
|
|
218
|
+
}
|
|
219
|
+
async receive(text) {
|
|
220
|
+
const value = JSON.parse(text);
|
|
221
|
+
if (!object(value) || value.v !== 1 || typeof value.type !== "string")
|
|
222
|
+
throw new Error("Unsupported runtime protocol");
|
|
223
|
+
if (value.type === "welcome") {
|
|
224
|
+
if (value.machineId !== this.credentials?.machineId || !Number.isSafeInteger(value.generation))
|
|
225
|
+
throw new Error("Runtime machine identity mismatch");
|
|
226
|
+
this.welcomed = true;
|
|
227
|
+
this.reconnectAttempt = 0;
|
|
228
|
+
if (this.graceTimer) {
|
|
229
|
+
clearTimeout(this.graceTimer);
|
|
230
|
+
this.graceTimer = null;
|
|
231
|
+
}
|
|
232
|
+
this.report("Connected to AgEnD.");
|
|
233
|
+
for (const job of this.options.store.list())
|
|
234
|
+
this.publish(job);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (value.type === "revoked") {
|
|
238
|
+
this.report("This machine was revoked by AgEnD.");
|
|
239
|
+
await this.stop({ retirePanes: true });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (!this.welcomed)
|
|
243
|
+
throw new Error("Runtime handshake is incomplete");
|
|
244
|
+
if (value.type === "ping") {
|
|
245
|
+
this.send({ v: 1, type: "pong" });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (value.type === "tool.result") {
|
|
249
|
+
if (typeof value.id !== "string")
|
|
250
|
+
throw new Error("Invalid tool reply");
|
|
251
|
+
const pending = this.pendingTools.get(value.id);
|
|
252
|
+
if (!pending)
|
|
253
|
+
return;
|
|
254
|
+
clearTimeout(pending.timer);
|
|
255
|
+
this.pendingTools.delete(value.id);
|
|
256
|
+
if (value.ok === true)
|
|
257
|
+
pending.accept(value.result);
|
|
258
|
+
else
|
|
259
|
+
pending.reject(new Error("AgEnD MCP request was refused"));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (value.type === "pane.tool.result") {
|
|
263
|
+
if (Object.keys(value).some(key => !["v", "type", "id", "ok", "result"].includes(key)) || !validWindowsAgentId(value.id) || typeof value.ok !== "boolean")
|
|
264
|
+
throw Error("Invalid pane MCP reply");
|
|
265
|
+
const pending = this.pendingPaneTools.get(value.id);
|
|
266
|
+
if (!pending)
|
|
267
|
+
return;
|
|
268
|
+
this.pendingPaneTools.delete(value.id);
|
|
269
|
+
clearTimeout(pending.timer);
|
|
270
|
+
if (value.ok)
|
|
271
|
+
pending.accept(value.result);
|
|
272
|
+
else
|
|
273
|
+
pending.reject(Error("Pane MCP request was refused"));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (value.type === "pane.request") {
|
|
277
|
+
if (Object.keys(value).some(key => !["v", "type", "id", "maxAgents", "request"].includes(key))
|
|
278
|
+
|| typeof value.id !== "string" || !validWindowsAgentId(value.id) || !Number.isInteger(value.maxAgents)
|
|
279
|
+
|| value.maxAgents < 1 || value.maxAgents > 16)
|
|
280
|
+
throw Error("Invalid pane transport envelope");
|
|
281
|
+
const request = validateRuntimePaneRequest(value.request);
|
|
282
|
+
this.machinePaneLimit = value.maxAgents;
|
|
283
|
+
const epoch = this.paneEpoch;
|
|
284
|
+
try {
|
|
285
|
+
const host = await this.panes();
|
|
286
|
+
const result = await host.request(request, () => epoch === this.paneEpoch && this.online());
|
|
287
|
+
if (request.action === "shutdown") {
|
|
288
|
+
validateRuntimePaneShutdown(result, request.hostBootId);
|
|
289
|
+
// Explicit coordinator disable, not a disconnect: the next enabled request discovers a new host boot.
|
|
290
|
+
// Retire this cache before sending the reply, including when that reply is lost under backpressure.
|
|
291
|
+
if (this.paneHost === host) {
|
|
292
|
+
this.paneHost = null;
|
|
293
|
+
this.paneOpening = null;
|
|
294
|
+
}
|
|
295
|
+
await host.stop();
|
|
296
|
+
}
|
|
297
|
+
this.send({ v: 1, type: "pane.response", id: value.id, ok: true, result });
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.send({ v: 1, type: "pane.response", id: value.id, ok: false, error: message(error) });
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (value.type !== "request" || typeof value.id !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(value.id)
|
|
305
|
+
|| typeof value.operation !== "string" || !object(value.payload))
|
|
306
|
+
throw new Error("Invalid runtime request");
|
|
307
|
+
const command = value;
|
|
308
|
+
try {
|
|
309
|
+
this.send({ v: 1, type: "response", id: command.id, ok: true, result: await this.operation(command.operation, command.payload) });
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
this.send({ v: 1, type: "response", id: command.id, ok: false, error: message(error) });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
publish(job) { if (this.online())
|
|
316
|
+
this.send({ v: 1, type: "task", seq: job.seq, task: job.task }); }
|
|
317
|
+
panes() {
|
|
318
|
+
// A detached host link is not a live cache. Rediscover on the next independent request; never replay this RPC.
|
|
319
|
+
if (this.paneHost && "connected" in this.paneHost && !this.paneHost.connected()) {
|
|
320
|
+
this.paneHost = null;
|
|
321
|
+
this.paneOpening = null;
|
|
322
|
+
}
|
|
323
|
+
if (!this.paneOpening) {
|
|
324
|
+
const output = snapshot => { if (this.online())
|
|
325
|
+
this.send({ v: 1, type: "pane.output", snapshot }); };
|
|
326
|
+
if (this.options.createPaneHost)
|
|
327
|
+
this.paneOpening = import("./runtime-pane-store.js").then(({ RuntimePaneStore }) => {
|
|
328
|
+
const store = new RuntimePaneStore(join(this.options.store.directory, "panes"));
|
|
329
|
+
store.recoverInterrupted();
|
|
330
|
+
return this.paneHost = this.options.createPaneHost({ store, bootId: this.bootId, online: () => !this.stopped && this.online(), maxAgents: () => this.machinePaneLimit, output });
|
|
331
|
+
});
|
|
332
|
+
else
|
|
333
|
+
this.paneOpening = import("./runtime-pane-link.js").then(({ RuntimePaneLink }) => (this.options.openPaneLink ?? RuntimePaneLink.open.bind(RuntimePaneLink))({
|
|
334
|
+
directory: this.options.store.directory, server: this.credentials.server, machineId: this.credentials.machineId,
|
|
335
|
+
online: () => !this.stopped && this.online(), maxAgents: () => this.machinePaneLimit, epoch: () => this.paneEpoch, output, mcp: (body, current) => this.paneMcp(body, current),
|
|
336
|
+
})).then(host => this.paneHost = host);
|
|
337
|
+
const opening = this.paneOpening;
|
|
338
|
+
void opening.catch(() => { if (this.paneOpening === opening)
|
|
339
|
+
this.paneOpening = null; });
|
|
340
|
+
}
|
|
341
|
+
return this.paneOpening;
|
|
342
|
+
}
|
|
343
|
+
paneMcp(raw, current) {
|
|
344
|
+
const body = validatePaneMcpBody(raw), epoch = this.paneEpoch;
|
|
345
|
+
if (!current() || !this.online() || this.pendingPaneTools.size >= 32)
|
|
346
|
+
return Promise.reject(Error("Pane MCP transport is unavailable"));
|
|
347
|
+
const id = randomUUID();
|
|
348
|
+
return new Promise((accept, reject) => {
|
|
349
|
+
const timer = setTimeout(() => { this.pendingPaneTools.delete(id); reject(Error("Pane MCP response is unconfirmed")); }, 85_000);
|
|
350
|
+
timer.unref();
|
|
351
|
+
this.pendingPaneTools.set(id, { timer, reject, accept: value => current() && this.online() && this.paneEpoch === epoch ? accept(value) : reject(Error("Pane MCP lease expired")) });
|
|
352
|
+
try {
|
|
353
|
+
this.send({ v: 1, type: "pane.tool.request", id, body });
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
this.pendingPaneTools.delete(id);
|
|
357
|
+
clearTimeout(timer);
|
|
358
|
+
reject(Error("Pane MCP response is unconfirmed"));
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
update(task) {
|
|
363
|
+
if (this.closed)
|
|
364
|
+
return;
|
|
365
|
+
const serialized = JSON.stringify(task);
|
|
366
|
+
if (this.snapshots.get(task.id) === serialized)
|
|
367
|
+
return;
|
|
368
|
+
try {
|
|
369
|
+
const job = this.options.store.update(task);
|
|
370
|
+
this.snapshots.set(task.id, serialized);
|
|
371
|
+
this.publish(job);
|
|
372
|
+
if (task.finishedAt) {
|
|
373
|
+
this.active.delete(task.id);
|
|
374
|
+
this.closeMcp(task.id);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// A process whose state could not be committed must not continue accepting commands.
|
|
379
|
+
this.report("Runtime execution state could not be saved. Stop and inspect the private runtime profile.");
|
|
380
|
+
try {
|
|
381
|
+
this.bridge.cancel(task.id);
|
|
382
|
+
}
|
|
383
|
+
catch { /* leave durable accepted state blocked */ }
|
|
384
|
+
this.socket?.close(1011, "Runtime storage failed");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
poll() { for (const id of this.active) {
|
|
388
|
+
const task = this.bridge.get(id);
|
|
389
|
+
if (task)
|
|
390
|
+
this.update(task);
|
|
391
|
+
} }
|
|
392
|
+
async refreshInventory(force = false) {
|
|
393
|
+
const inventory = await this.bridge.probe({ force });
|
|
394
|
+
this.inventory = inventory;
|
|
395
|
+
if (this.online())
|
|
396
|
+
this.send({ v: 1, type: "inventory", inventory });
|
|
397
|
+
return inventory;
|
|
398
|
+
}
|
|
399
|
+
/** Testable command surface. Mutation identity is committed before calling the native bridge. */
|
|
400
|
+
async operation(operation, payload) {
|
|
401
|
+
if (operation === "probe") {
|
|
402
|
+
if (Object.keys(payload).some(key => key !== "force") || (payload.force !== undefined && typeof payload.force !== "boolean"))
|
|
403
|
+
throw new Error("Invalid discovery request");
|
|
404
|
+
return this.refreshInventory(payload.force === true);
|
|
405
|
+
}
|
|
406
|
+
if (operation === "workspace") {
|
|
407
|
+
if (Object.keys(payload).some(key => key !== "agentId") || !validWindowsAgentId(payload.agentId))
|
|
408
|
+
throw new Error("Invalid workspace request");
|
|
409
|
+
return { agentId: payload.agentId, cwd: await this.bridge.prepareWorkspace(payload.agentId) };
|
|
410
|
+
}
|
|
411
|
+
if (operation === "reconcile") {
|
|
412
|
+
if (Object.keys(payload).length)
|
|
413
|
+
throw new Error("Invalid reconciliation request");
|
|
414
|
+
return { jobs: this.options.store.list().map(row => ({ seq: row.seq, task: row.task })) };
|
|
415
|
+
}
|
|
416
|
+
if (operation === "cancel" || operation === "recover") {
|
|
417
|
+
if (typeof payload.runId !== "string" || Object.keys(payload).some(key => !["runId", ...(operation === "recover" ? ["confirmedStopped", "original"] : [])].includes(key)))
|
|
418
|
+
throw new Error("Invalid execution operation");
|
|
419
|
+
const stored = this.options.store.get(payload.runId);
|
|
420
|
+
if (operation === "recover") {
|
|
421
|
+
if (this.active.has(payload.runId))
|
|
422
|
+
throw new Error("The native execution is still connected; wait for cancellation");
|
|
423
|
+
if (payload.confirmedStopped !== true)
|
|
424
|
+
throw new Error("Confirm the execution stopped before recovery");
|
|
425
|
+
const original = payload.original === undefined ? null : checkedRun(payload.original);
|
|
426
|
+
if (original && original.task.requestId !== payload.runId)
|
|
427
|
+
throw new Error("Recovery execution identity does not match the original run");
|
|
428
|
+
const digest = original ? runtimeJobDigest(original) : null;
|
|
429
|
+
if (stored && digest && stored.digest !== digest)
|
|
430
|
+
throw new Error("Recovery arguments do not match the accepted execution");
|
|
431
|
+
if (!stored) {
|
|
432
|
+
if (!original || !digest)
|
|
433
|
+
throw new Error("Unknown execution recovery needs its trusted original run");
|
|
434
|
+
if (this.active.size)
|
|
435
|
+
throw new Error("Wait for the active native execution before recovery");
|
|
436
|
+
const task = original.task;
|
|
437
|
+
// The coordinator may have durably submitted a run that never reached this client. Explicit manual
|
|
438
|
+
// confirmation creates a terminal tombstone, never an execution, so a delayed retry cannot launch it.
|
|
439
|
+
const accepted = this.options.store.accept({ id: task.requestId, cli: task.cli, cwd: task.cwd, sandbox: task.sandbox,
|
|
440
|
+
...(task.model ? { model: task.model } : {}), status: "failed", startedAt: Date.now(), finishedAt: Date.now(),
|
|
441
|
+
outcomeConfirmed: true, output: "", error: "The unconfirmed execution was manually confirmed stopped before local acceptance; it will not be replayed." }, digest);
|
|
442
|
+
this.publish(accepted.job);
|
|
443
|
+
return accepted.job.task;
|
|
444
|
+
}
|
|
445
|
+
if (stored.task.finishedAt && stored.task.outcomeConfirmed === true)
|
|
446
|
+
return stored.task;
|
|
447
|
+
this.bridge.releaseUnknown(payload.runId);
|
|
448
|
+
const job = this.options.store.recover(payload.runId, true);
|
|
449
|
+
this.publish(job);
|
|
450
|
+
return job.task;
|
|
451
|
+
}
|
|
452
|
+
if (!stored)
|
|
453
|
+
throw new Error("Runtime execution not found");
|
|
454
|
+
if (!this.active.has(payload.runId))
|
|
455
|
+
return stored.task;
|
|
456
|
+
const task = this.bridge.cancel(payload.runId);
|
|
457
|
+
this.update(task);
|
|
458
|
+
return task;
|
|
459
|
+
}
|
|
460
|
+
if (operation !== "run")
|
|
461
|
+
throw new Error("Unknown runtime operation");
|
|
462
|
+
const run = checkedRun(payload), task = run.task;
|
|
463
|
+
const digest = runtimeJobDigest(run);
|
|
464
|
+
const accepted = this.options.store.accept({ id: task.requestId, cli: task.cli, cwd: task.cwd, sandbox: task.sandbox,
|
|
465
|
+
...(task.model ? { model: task.model } : {}), status: "starting", startedAt: Date.now(), output: "" }, digest);
|
|
466
|
+
if (accepted.duplicate)
|
|
467
|
+
return accepted.job.task;
|
|
468
|
+
this.publish(accepted.job);
|
|
469
|
+
try {
|
|
470
|
+
const mcp = payload.mcp ? await this.openMcp(task.requestId, payload.mcp.tools) : undefined;
|
|
471
|
+
if (this.stopped || !this.online())
|
|
472
|
+
throw new Error("Runtime disconnected before native execution started");
|
|
473
|
+
this.active.add(task.requestId);
|
|
474
|
+
const initial = this.bridge.start(task, () => !this.stopped && this.online(), {
|
|
475
|
+
...(payload.resumeSessionId ? { resumeSessionId: payload.resumeSessionId } : {}),
|
|
476
|
+
...(payload.context ? { context: payload.context } : {}), ...(mcp ? { mcp } : {}),
|
|
477
|
+
onSettled: settled => this.update(settled),
|
|
478
|
+
});
|
|
479
|
+
this.update(initial);
|
|
480
|
+
return this.options.store.get(task.requestId).task;
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
this.active.delete(task.requestId);
|
|
484
|
+
this.closeMcp(task.requestId);
|
|
485
|
+
const failed = { ...accepted.job.task, status: "failed", error: message(error), finishedAt: Date.now(), outcomeConfirmed: true };
|
|
486
|
+
this.update(failed);
|
|
487
|
+
return failed;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
async openMcp(runId, tools) {
|
|
491
|
+
if (!this.mcpOpening)
|
|
492
|
+
this.mcpOpening = new Promise((accept, reject) => {
|
|
493
|
+
const server = this.mcpServer = createServer((req, res) => {
|
|
494
|
+
const reply = (status, value) => { if (!res.writableEnded && !res.destroyed) {
|
|
495
|
+
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
496
|
+
res.end(JSON.stringify(value));
|
|
497
|
+
} };
|
|
498
|
+
const header = req.headers["x-agend-native-token"];
|
|
499
|
+
const entry = typeof header === "string" && /^[a-f0-9]{64}$/.test(header) ? this.mcpTokens.get(header) : undefined;
|
|
500
|
+
if (req.method !== "POST" || req.url !== "/agent" || req.headers.origin !== undefined || req.headers.cookie !== undefined
|
|
501
|
+
|| !["127.0.0.1", "::1", "::ffff:127.0.0.1"].includes(req.socket.remoteAddress ?? "") || !entry
|
|
502
|
+
|| !this.active.has(entry.runId) || !this.online()) {
|
|
503
|
+
req.resume();
|
|
504
|
+
reply(403, { error: "Runtime MCP capability is unavailable" });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
void jsonBody(req).then(async (body) => {
|
|
508
|
+
if (!object(body) || body.op !== "native-mcp" || !["tools/list", "tools/call"].includes(String(body.method))
|
|
509
|
+
|| !object(body.params) || Object.keys(body).some(key => !["op", "method", "params", "callId"].includes(key)))
|
|
510
|
+
throw new Error("Invalid runtime MCP request");
|
|
511
|
+
if (body.method === "tools/call" && (!entry.tools.includes(String(body.params.name)) || !object(body.params.arguments)
|
|
512
|
+
|| typeof body.callId !== "string" || !/^[A-Za-z0-9._:-]{1,128}$/.test(body.callId)))
|
|
513
|
+
throw new Error("Runtime MCP tool is unavailable");
|
|
514
|
+
const result = await this.toolCall(entry.runId, body);
|
|
515
|
+
if (!this.active.has(entry.runId) || !this.online()) {
|
|
516
|
+
reply(401, { error: "Runtime MCP capability expired" });
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (Buffer.byteLength(JSON.stringify(result) ?? "") > MAX_TOOL)
|
|
520
|
+
throw new Error("Runtime MCP result is too large");
|
|
521
|
+
reply(200, result);
|
|
522
|
+
}).catch(() => reply(403, { error: "Runtime MCP request was refused or its execution expired" }));
|
|
523
|
+
});
|
|
524
|
+
server.on("error", reject);
|
|
525
|
+
server.listen(0, "127.0.0.1", () => { const address = server.address(); if (!address || typeof address === "string") {
|
|
526
|
+
reject(new Error("Runtime MCP could not bind"));
|
|
527
|
+
return;
|
|
528
|
+
} this.mcpPort = address.port; accept(address.port); });
|
|
529
|
+
});
|
|
530
|
+
await this.mcpOpening;
|
|
531
|
+
const token = randomBytes(32).toString("hex");
|
|
532
|
+
this.mcpTokens.set(token, { runId, tools: [...tools] });
|
|
533
|
+
return { endpoint: `http://127.0.0.1:${this.mcpPort}/agent`, token, tools: [...tools] };
|
|
534
|
+
}
|
|
535
|
+
closeMcp(runId) { for (const [token, entry] of this.mcpTokens)
|
|
536
|
+
if (entry.runId === runId)
|
|
537
|
+
this.mcpTokens.delete(token); }
|
|
538
|
+
toolCall(runId, body) {
|
|
539
|
+
if (!this.online())
|
|
540
|
+
return Promise.reject(new Error("Runtime is disconnected"));
|
|
541
|
+
if (this.pendingTools.size >= 64)
|
|
542
|
+
return Promise.reject(new Error("Runtime MCP call capacity reached"));
|
|
543
|
+
const id = randomUUID();
|
|
544
|
+
return new Promise((accept, reject) => {
|
|
545
|
+
const timer = setTimeout(() => { this.pendingTools.delete(id); reject(new Error("AgEnD MCP request timed out")); }, 90_000);
|
|
546
|
+
timer.unref?.();
|
|
547
|
+
this.pendingTools.set(id, { accept, reject, timer });
|
|
548
|
+
this.send({ v: 1, type: "tool.call", id, runId, body });
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
rejectTools() {
|
|
552
|
+
for (const pending of this.pendingPaneTools.values()) {
|
|
553
|
+
clearTimeout(pending.timer);
|
|
554
|
+
pending.reject(Error("Pane MCP disconnected; no call was replayed"));
|
|
555
|
+
}
|
|
556
|
+
this.pendingPaneTools.clear();
|
|
557
|
+
for (const pending of this.pendingTools.values()) {
|
|
558
|
+
clearTimeout(pending.timer);
|
|
559
|
+
pending.reject(new Error("Runtime disconnected"));
|
|
560
|
+
}
|
|
561
|
+
this.pendingTools.clear();
|
|
562
|
+
}
|
|
563
|
+
async stop(options = {}) {
|
|
564
|
+
if (this.stopped)
|
|
565
|
+
return;
|
|
566
|
+
this.stopped = true;
|
|
567
|
+
this.welcomed = false;
|
|
568
|
+
if (this.reconnectTimer)
|
|
569
|
+
clearTimeout(this.reconnectTimer);
|
|
570
|
+
if (this.graceTimer)
|
|
571
|
+
clearTimeout(this.graceTimer);
|
|
572
|
+
if (this.pollTimer)
|
|
573
|
+
clearInterval(this.pollTimer);
|
|
574
|
+
this.reconnectTimer = null;
|
|
575
|
+
this.graceTimer = null;
|
|
576
|
+
this.pollTimer = null;
|
|
577
|
+
this.rejectTools();
|
|
578
|
+
this.mcpTokens.clear();
|
|
579
|
+
this.bridge.close();
|
|
580
|
+
const socket = this.socket;
|
|
581
|
+
this.socket = null;
|
|
582
|
+
if (socket) {
|
|
583
|
+
socket.close();
|
|
584
|
+
if (socket.readyState !== WebSocket.CLOSED)
|
|
585
|
+
socket.terminate();
|
|
586
|
+
}
|
|
587
|
+
// A cancelled start may still finish its durable credential write. Do not release the profile to another writer.
|
|
588
|
+
await this.startupRotation?.catch(() => { });
|
|
589
|
+
await this.paneOpening?.then(async (host) => {
|
|
590
|
+
try {
|
|
591
|
+
if (options.retirePanes && "retireOwned" in host)
|
|
592
|
+
await host.retireOwned();
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
await host.stop();
|
|
596
|
+
}
|
|
597
|
+
}).catch(() => this.report("Pane cleanup is unconfirmed; its durable reservations remain held."));
|
|
598
|
+
// Keep the profile lock until cancellation is confirmed or the journal is explicitly marked uncertain.
|
|
599
|
+
const deadline = Date.now() + 5000;
|
|
600
|
+
while (this.active.size && Date.now() < deadline) {
|
|
601
|
+
this.poll();
|
|
602
|
+
if (!this.active.size)
|
|
603
|
+
break;
|
|
604
|
+
await new Promise(done => setTimeout(done, 50));
|
|
605
|
+
}
|
|
606
|
+
for (const id of this.active) {
|
|
607
|
+
const old = this.options.store.get(id);
|
|
608
|
+
if (old)
|
|
609
|
+
this.update({ ...old.task, status: "failed", finishedAt: Date.now(), outcomeConfirmed: false,
|
|
610
|
+
error: "Runtime stopped before native process shutdown was confirmed. Inspect processes before recovery." });
|
|
611
|
+
}
|
|
612
|
+
this.closed = true;
|
|
613
|
+
if (this.mcpServer) {
|
|
614
|
+
const server = this.mcpServer;
|
|
615
|
+
this.mcpServer = null;
|
|
616
|
+
server.closeAllConnections();
|
|
617
|
+
await new Promise(done => server.close(() => done()));
|
|
618
|
+
}
|
|
619
|
+
this.unlock?.();
|
|
620
|
+
this.unlock = null;
|
|
621
|
+
for (const done of this.stoppedListeners)
|
|
622
|
+
done();
|
|
623
|
+
this.stoppedListeners.clear();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async function post(server, path, body, signal, credential) {
|
|
627
|
+
const response = await fetch(new URL(path, server), { method: "POST", redirect: "error", headers: { "Content-Type": "application/json", ...(credential ? { Authorization: `Bearer ${credential}` } : {}) },
|
|
628
|
+
body: JSON.stringify(body), signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15_000)]) : AbortSignal.timeout(15_000) });
|
|
629
|
+
let text = "", bytes = 0;
|
|
630
|
+
if (response.body)
|
|
631
|
+
for await (const part of response.body) {
|
|
632
|
+
bytes += part.byteLength;
|
|
633
|
+
if (bytes > MAX_TOOL)
|
|
634
|
+
throw new Error("Runtime pairing response is too large");
|
|
635
|
+
text += new TextDecoder().decode(part);
|
|
636
|
+
}
|
|
637
|
+
let data;
|
|
638
|
+
try {
|
|
639
|
+
data = JSON.parse(text);
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
throw new Error("Invalid runtime pairing response");
|
|
643
|
+
}
|
|
644
|
+
if (!object(data))
|
|
645
|
+
throw new Error("Invalid runtime pairing response");
|
|
646
|
+
return { status: response.status, data };
|
|
647
|
+
}
|
|
648
|
+
/** Caller holds the profile lock. Persist the candidate before wire; either credential can recover a lost response. */
|
|
649
|
+
export async function rotateRuntimeCredentials(input) {
|
|
650
|
+
let saved = input.store.credentials();
|
|
651
|
+
if (!saved)
|
|
652
|
+
throw Error("Pair this runtime before rotating its credential");
|
|
653
|
+
if (!saved.pendingCredential) {
|
|
654
|
+
saved = { ...saved, pendingCredential: randomBytes(32).toString("hex") };
|
|
655
|
+
input.store.saveCredentials(saved);
|
|
656
|
+
}
|
|
657
|
+
const server = validateRuntimeServer(saved.server), candidate = saved.pendingCredential;
|
|
658
|
+
for (const credential of [saved.credential, candidate]) {
|
|
659
|
+
const response = await post(server, RUNTIME_ROTATE_PATH, { machineId: saved.machineId, credential: candidate }, input.signal, credential);
|
|
660
|
+
if (response.status === 200 && response.data.status === "rotated" && response.data.machineId === saved.machineId) {
|
|
661
|
+
const { pendingCredential: _, ...current } = saved;
|
|
662
|
+
const completed = { ...current, credential: candidate };
|
|
663
|
+
input.store.saveCredentials(completed);
|
|
664
|
+
return completed;
|
|
665
|
+
}
|
|
666
|
+
if (response.status !== 401 && response.status !== 429)
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
throw Error("Runtime credential rotation was not confirmed. The saved candidate is retained; retry rotate before connecting.");
|
|
670
|
+
}
|
|
671
|
+
export async function pairRuntime(input) {
|
|
672
|
+
const server = validateRuntimeServer(input.server);
|
|
673
|
+
if (!input.code.trim() || input.code.length > 256 || /[\x00-\x1f]/.test(input.code) || !input.name.trim() || input.name.length > 100)
|
|
674
|
+
throw new Error("Enter a valid pairing code and machine name");
|
|
675
|
+
const paired = await post(server, "/runtime/v1/pair", { code: input.code.trim(), name: input.name.trim(), platform: process.platform, hostname: hostname() }, input.signal);
|
|
676
|
+
if (paired.status !== 201 || paired.data.status !== "pending" || typeof paired.data.machineId !== "string" || typeof paired.data.claimSecret !== "string")
|
|
677
|
+
throw new Error(`Runtime pairing was refused (HTTP ${paired.status})`);
|
|
678
|
+
const deadline = Math.min(Date.now() + 10 * 60_000, typeof paired.data.expiresAt === "number" ? paired.data.expiresAt : Infinity);
|
|
679
|
+
const fingerprint = runtimeClaimFingerprint(paired.data.claimSecret);
|
|
680
|
+
input.report?.(`Pairing fingerprint: ${fingerprint}. Compare it with AgEnD Settings → Runtime before approving this machine; waiting for approval…`);
|
|
681
|
+
while (!input.signal?.aborted && Date.now() < deadline) {
|
|
682
|
+
const claimed = await post(server, "/runtime/v1/claim", { machineId: paired.data.machineId, claimSecret: paired.data.claimSecret }, input.signal);
|
|
683
|
+
if (claimed.status === 200 && claimed.data.status === "approved" && claimed.data.machineId === paired.data.machineId
|
|
684
|
+
&& typeof claimed.data.credential === "string" && /^[A-Za-z0-9._-]{16,256}$/.test(claimed.data.credential)) {
|
|
685
|
+
const credentials = { server: server.origin, machineId: paired.data.machineId, credential: claimed.data.credential, name: input.name.trim() };
|
|
686
|
+
input.store.saveCredentials(credentials);
|
|
687
|
+
input.report?.("Runtime paired. Its machine credential was saved in the private local profile.");
|
|
688
|
+
return credentials;
|
|
689
|
+
}
|
|
690
|
+
if (claimed.status !== 202 || claimed.data.status !== "pending")
|
|
691
|
+
throw new Error(`Runtime approval was refused or expired (HTTP ${claimed.status})`);
|
|
692
|
+
await new Promise((accept, reject) => {
|
|
693
|
+
const timer = setTimeout(() => { input.signal?.removeEventListener("abort", abort); accept(); }, input.pollMs ?? 1500);
|
|
694
|
+
const abort = () => { clearTimeout(timer); reject(new Error("Runtime pairing cancelled")); };
|
|
695
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
throw new Error("Runtime pairing was cancelled or expired");
|
|
699
|
+
}
|
|
700
|
+
//# sourceMappingURL=runtime-client.js.map
|