@songsid/agend 2.3.5-alpha.4 → 2.3.5-beta.1
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 +0 -2
- package/README.zh-TW.md +0 -2
- package/THIRD_PARTY_NOTICES.md +12 -28
- package/dist/agent-cli-instructions.md +1 -1
- package/dist/agent-credentials.d.ts +24 -0
- package/dist/agent-credentials.js +66 -0
- package/dist/agent-credentials.js.map +1 -0
- package/dist/agent-endpoint.d.ts +4 -6
- package/dist/agent-endpoint.js +15 -12
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/auth-api.d.ts +0 -21
- package/dist/auth-api.js +8 -93
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +30 -4
- package/dist/authority/session-authorizations.js +69 -17
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +2 -12
- package/dist/authority/web-authority.js +4 -25
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +3 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +12 -15
- package/dist/backend/claude-code.js +133 -152
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +14 -0
- package/dist/backend/codex-metadata.js +45 -0
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex-package.d.ts +2 -5
- package/dist/backend/codex-package.js +5 -1
- package/dist/backend/codex-package.js.map +1 -1
- package/dist/backend/codex-quota-history.d.ts +7 -0
- package/dist/backend/codex-quota-history.js +72 -0
- package/dist/backend/codex-quota-history.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +6 -0
- package/dist/backend/codex-session-lookup.js +1 -1
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex-speed.d.ts +14 -0
- package/dist/backend/codex-speed.js +35 -0
- package/dist/backend/codex-speed.js.map +1 -0
- package/dist/backend/codex.d.ts +116 -136
- package/dist/backend/codex.js +912 -942
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok-weekly-reset.d.ts +24 -0
- package/dist/backend/grok-weekly-reset.js +95 -0
- package/dist/backend/grok-weekly-reset.js.map +1 -0
- package/dist/backend/grok.d.ts +30 -3
- package/dist/backend/grok.js +301 -76
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +3 -0
- package/dist/backend/kiro.js +50 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +5 -3
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +15 -8
- package/dist/backend/types.js +3 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-tmux-guard.d.ts +21 -0
- package/dist/bundled-tmux-guard.js +96 -0
- package/dist/bundled-tmux-guard.js.map +1 -0
- package/dist/cache-ledger.d.ts +95 -0
- package/dist/cache-ledger.js +276 -4
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +5 -0
- package/dist/cache-service.js +16 -4
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +11 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +51 -0
- package/dist/channel/adapters/telegram.js +235 -17
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/ipc-bridge.d.ts +0 -2
- package/dist/channel/mcp-tools.js +3 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/classic-channel-manager.d.ts +2 -0
- package/dist/classic-channel-manager.js +5 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +14 -10
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +30 -71
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +3 -17
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +10 -0
- package/dist/cross-instance-notice.js +67 -5
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +70 -68
- package/dist/daemon.js +774 -846
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +8 -0
- package/dist/dashboard-permissions.js +16 -0
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +26 -16
- package/dist/delivery-consumed-watch.js +31 -26
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.d.ts +0 -5
- package/dist/delivery-outbox.js +4 -32
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +42 -2
- package/dist/delivery-reconciliation.js +164 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/export-import.d.ts +7 -0
- package/dist/export-import.js +44 -10
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-context.d.ts +4 -2
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +0 -2
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +108 -245
- package/dist/fleet-manager.js +656 -2028
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-instance.d.ts +0 -7
- package/dist/general-instance.js +3 -20
- package/dist/general-instance.js.map +1 -1
- package/dist/general-knowledge/skills/concepts/SKILL.md +393 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +41 -4
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +9 -3
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/instance-config-impact.js +6 -7
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +19 -23
- package/dist/instance-lifecycle.js +216 -383
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-topic-delete.d.ts +77 -0
- package/dist/instance-topic-delete.js +123 -0
- package/dist/instance-topic-delete.js.map +1 -0
- package/dist/instructions.d.ts +1 -3
- package/dist/instructions.js +1 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +11 -0
- package/dist/kiro-engine-status.js +6 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/locale.js +63 -86
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +28 -0
- package/dist/log-service.js +29 -0
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +4 -3
- package/dist/log-steps.js +32 -7
- package/dist/log-steps.js.map +1 -1
- package/dist/logger.d.ts +16 -2
- package/dist/logger.js +346 -66
- package/dist/logger.js.map +1 -1
- package/dist/login-flows.d.ts +7 -0
- package/dist/login-flows.js +5 -2
- package/dist/login-flows.js.map +1 -1
- package/dist/login-manager.js +25 -4
- package/dist/login-manager.js.map +1 -1
- package/dist/missed-offline.d.ts +25 -0
- package/dist/missed-offline.js +79 -0
- package/dist/missed-offline.js.map +1 -0
- package/dist/muse-usage-relay.d.ts +2 -0
- package/dist/muse-usage-relay.js +5 -3
- package/dist/muse-usage-relay.js.map +1 -1
- package/dist/needs-you-hub.d.ts +6 -3
- package/dist/needs-you-hub.js +31 -9
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +20 -18
- package/dist/needs-you.js +18 -14
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share-page.js +1 -1
- package/dist/office-share-page.js.map +1 -1
- package/dist/outbound-handlers.d.ts +2 -16
- package/dist/outbound-handlers.js +10 -72
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -38
- package/dist/outbound-schemas.js +5 -14
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-quota-history.d.ts +7 -0
- package/dist/pane-quota-history.js +47 -0
- package/dist/pane-quota-history.js.map +1 -0
- package/dist/platform-general.d.ts +46 -0
- package/dist/platform-general.js +91 -0
- package/dist/platform-general.js.map +1 -0
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +0 -11
- package/dist/public-web-link.js +0 -13
- package/dist/public-web-link.js.map +1 -1
- package/dist/queued-handoffs.d.ts +33 -0
- package/dist/queued-handoffs.js +75 -0
- package/dist/queued-handoffs.js.map +1 -0
- package/dist/quickstart-api.js +5 -9
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +8 -1
- package/dist/quickstart.js +42 -4
- package/dist/quickstart.js.map +1 -1
- package/dist/quota-notice-config.d.ts +22 -0
- package/dist/quota-notice-config.js +41 -0
- package/dist/quota-notice-config.js.map +1 -0
- package/dist/quota-notice.d.ts +7 -0
- package/dist/quota-notice.js +40 -0
- package/dist/quota-notice.js.map +1 -1
- package/dist/reply-buttons-controller.js +10 -6
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-polls.d.ts +2 -2
- package/dist/reply-polls.js +3 -3
- package/dist/reply-polls.js.map +1 -1
- package/dist/scheduler/db.d.ts +2 -1
- package/dist/scheduler/db.js +23 -9
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +9 -0
- package/dist/scheduler/task-board.js +19 -0
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/settings-api.d.ts +13 -5
- package/dist/settings-api.js +26 -96
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +0 -1
- package/dist/settings-baseline.js +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.js +23 -28
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +1 -16
- package/dist/settings-confirmation.js +9 -47
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.d.ts +0 -4
- package/dist/settings-effect.js +1 -58
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +0 -4
- package/dist/settings-http-confirmation.js +3 -31
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +28 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/settings-request-capability.d.ts +0 -2
- package/dist/settings-request-capability.js +0 -4
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/settings-transaction.d.ts +0 -4
- package/dist/settings-transaction.js +0 -9
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-form.js +3 -6
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.js +0 -3
- package/dist/setup-i18n.js.map +1 -1
- package/dist/task-board-tool.d.ts +11 -0
- package/dist/task-board-tool.js +44 -0
- package/dist/task-board-tool.js.map +1 -1
- package/dist/task-board-waker.d.ts +6 -0
- package/dist/task-board-waker.js +4 -0
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +1 -2
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +5 -2
- package/dist/tool-permissions.js +7 -7
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.d.ts +2 -0
- package/dist/tool-progress.js +19 -3
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.js +4 -3
- 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 +20 -3
- package/dist/transcript-monitor.js +92 -24
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +0 -5
- package/dist/transcript-sources.js +79 -8
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +3 -23
- package/dist/ui/app.html +8 -1
- package/dist/ui/backend-signin.js +21 -36
- package/dist/ui/chat-md-strings.js +9 -0
- package/dist/ui/chat-store.js +2 -13
- package/dist/ui/chat-strings.js +2 -50
- package/dist/ui/chat-thread.js +53 -3
- package/dist/ui/fleet-task-card.js +3 -3
- package/dist/ui/fleet-task-common.js +10 -0
- package/dist/ui/fleet-tasks.js +7 -5
- package/dist/ui/fleet-usage.js +90 -27
- package/dist/ui/panel-chat.js +37 -66
- package/dist/ui/panel-details.js +1 -2
- package/dist/ui/panel-fleet.js +88 -107
- package/dist/ui/panel-needs.js +21 -163
- package/dist/ui/panel-notices.js +4 -3
- package/dist/ui/panel-settings.js +149 -239
- package/dist/ui/preview-panel.js +84 -6
- package/dist/ui/search-dialog.js +3 -2
- package/dist/ui/settings-appearance.js +23 -122
- package/dist/ui/settings-catalog.js +10 -37
- package/dist/ui/settings-codex-copy.js +19 -0
- package/dist/ui/settings-confirm.js +1 -3
- package/dist/ui/settings-dialogs.js +20 -27
- package/dist/ui/settings-guests.js +14 -27
- package/dist/ui/settings-model.js +17 -12
- package/dist/ui/settings-quota-notice.js +100 -0
- package/dist/ui/settings-registry.js +4 -11
- package/dist/ui/settings-strings.js +4 -113
- package/dist/ui/settings-web-authority.js +13 -15
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +1 -1
- package/dist/ui/shared/app-route.js +2 -6
- package/dist/ui/shared/app-shell.js +18 -23
- package/dist/ui/shared/app-store.js +2 -3
- package/dist/ui/shared/app-stream.js +1 -3
- package/dist/ui/shared/app.css +40 -1312
- package/dist/ui/shared/app.js +2 -5
- package/dist/ui/shared/header-tools.js +4 -6
- package/dist/ui/shared/instance-nav.js +7 -34
- package/dist/ui/shared/instance-switch.js +3 -4
- package/dist/ui/shared/loading.css +37 -0
- package/dist/ui/shared/panel-view.js +77 -4
- package/dist/ui/shared/signin.js +6 -39
- package/dist/ui/shared/tokens.css +0 -1
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/ui-menu.js +10 -30
- package/dist/ui/shared/ui-states.js +14 -3
- package/dist/ui/shared/usage-dialog.js +18 -1
- package/dist/ui/shared/vendor/vendor.json +2 -19
- package/dist/ui/shared/view-history.css +17 -0
- package/dist/ui/shared/view-history.js +438 -0
- package/dist/ui/shared/view-strings.js +16 -8
- package/dist/ui/signin.html +5 -2
- package/dist/unattended-install.d.ts +11 -2
- package/dist/unattended-install.js +39 -5
- package/dist/unattended-install.js.map +1 -1
- package/dist/usage/capacity.d.ts +69 -0
- package/dist/usage/capacity.js +119 -0
- package/dist/usage/capacity.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +3 -0
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/profile-timeline.d.ts +16 -0
- package/dist/usage/profile-timeline.js +58 -0
- package/dist/usage/profile-timeline.js.map +1 -0
- package/dist/usage/providers.d.ts +5 -0
- package/dist/usage/providers.js +5 -0
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/quota-history.d.ts +43 -0
- package/dist/usage/quota-history.js +168 -0
- package/dist/usage/quota-history.js.map +1 -0
- package/dist/usage/quota-notices.d.ts +38 -0
- package/dist/usage/quota-notices.js +160 -0
- package/dist/usage/quota-notices.js.map +1 -0
- package/dist/usage/usage-api.d.ts +32 -0
- package/dist/usage/usage-api.js +96 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.d.ts +25 -1
- package/dist/usage-report.js +55 -4
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +6 -1
- package/dist/usage-rollup.js +16 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/view-api.d.ts +16 -0
- package/dist/view-api.js +100 -16
- package/dist/view-api.js.map +1 -1
- package/dist/view-history.d.ts +84 -0
- package/dist/view-history.js +116 -0
- package/dist/view-history.js.map +1 -0
- package/dist/web-api.d.ts +6 -38
- package/dist/web-api.js +20 -255
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +2 -19
- package/dist/web-chat-history.js +3 -72
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-only-fleet.d.ts +0 -20
- package/dist/web-only-fleet.js +0 -21
- package/dist/web-only-fleet.js.map +1 -1
- package/dist/web-session-authorization.d.ts +13 -2
- package/dist/web-session-authorization.js +10 -3
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.d.ts +0 -7
- package/dist/web-session.js +0 -2
- package/dist/web-session.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -4
- package/dist/web-shell-routes.js +2 -7
- package/dist/web-shell-routes.js.map +1 -1
- package/launcher/private-home.cjs +1 -1
- package/package.json +3 -9
- package/dist/agent-runtime-api.d.ts +0 -4
- package/dist/agent-runtime-api.js +0 -59
- package/dist/agent-runtime-api.js.map +0 -1
- package/dist/agent-runtime-change.d.ts +0 -74
- package/dist/agent-runtime-change.js +0 -102
- package/dist/agent-runtime-change.js.map +0 -1
- package/dist/backend/grok-model-cache.d.ts +0 -7
- package/dist/backend/grok-model-cache.js +0 -65
- package/dist/backend/grok-model-cache.js.map +0 -1
- package/dist/backend/native-pane.d.ts +0 -9
- package/dist/backend/native-pane.js +0 -59
- package/dist/backend/native-pane.js.map +0 -1
- package/dist/fleet-windows-runtime.d.ts +0 -97
- package/dist/fleet-windows-runtime.js +0 -449
- package/dist/fleet-windows-runtime.js.map +0 -1
- package/dist/general-binding.d.ts +0 -85
- package/dist/general-binding.js +0 -298
- package/dist/general-binding.js.map +0 -1
- package/dist/general-host-owner.d.ts +0 -8
- package/dist/general-host-owner.js +0 -26
- package/dist/general-host-owner.js.map +0 -1
- package/dist/general-role.d.ts +0 -5
- package/dist/general-role.js +0 -26
- package/dist/general-role.js.map +0 -1
- package/dist/local-pane-endpoint.d.ts +0 -46
- package/dist/local-pane-endpoint.js +0 -189
- package/dist/local-pane-endpoint.js.map +0 -1
- package/dist/local-pane-launcher.d.ts +0 -2
- package/dist/local-pane-launcher.js +0 -38
- package/dist/local-pane-launcher.js.map +0 -1
- package/dist/local-pane-stage.ps1 +0 -101
- package/dist/model-catalog-defaults.d.ts +0 -14
- package/dist/model-catalog-defaults.js +0 -20
- package/dist/model-catalog-defaults.js.map +0 -1
- package/dist/native-agent-admission.d.ts +0 -7
- package/dist/native-agent-admission.js +0 -26
- package/dist/native-agent-admission.js.map +0 -1
- package/dist/native-daemon-client.d.ts +0 -13
- package/dist/native-daemon-client.js +0 -28
- package/dist/native-daemon-client.js.map +0 -1
- package/dist/native-instance-api.d.ts +0 -14
- package/dist/native-instance-api.js +0 -205
- package/dist/native-instance-api.js.map +0 -1
- package/dist/native-instance-input.d.ts +0 -84
- package/dist/native-instance-input.js +0 -54
- package/dist/native-instance-input.js.map +0 -1
- package/dist/native-instance-registry.d.ts +0 -32
- package/dist/native-instance-registry.js +0 -105
- package/dist/native-instance-registry.js.map +0 -1
- package/dist/native-mcp.d.ts +0 -40
- package/dist/native-mcp.js +0 -278
- package/dist/native-mcp.js.map +0 -1
- package/dist/native-model-catalog.d.ts +0 -5
- package/dist/native-model-catalog.js +0 -76
- package/dist/native-model-catalog.js.map +0 -1
- package/dist/native-pane-crash-budget.d.ts +0 -20
- package/dist/native-pane-crash-budget.js +0 -60
- package/dist/native-pane-crash-budget.js.map +0 -1
- package/dist/native-pane-create.d.ts +0 -26
- package/dist/native-pane-create.js +0 -53
- package/dist/native-pane-create.js.map +0 -1
- package/dist/native-pane-lifecycle.d.ts +0 -65
- package/dist/native-pane-lifecycle.js +0 -230
- package/dist/native-pane-lifecycle.js.map +0 -1
- package/dist/native-pane-manager.d.ts +0 -59
- package/dist/native-pane-manager.js +0 -584
- package/dist/native-pane-manager.js.map +0 -1
- package/dist/native-pane-output-log.d.ts +0 -15
- package/dist/native-pane-output-log.js +0 -108
- package/dist/native-pane-output-log.js.map +0 -1
- package/dist/native-pane-reconciliation.d.ts +0 -4
- package/dist/native-pane-reconciliation.js +0 -40
- package/dist/native-pane-reconciliation.js.map +0 -1
- package/dist/native-pane-recovery.d.ts +0 -12
- package/dist/native-pane-recovery.js +0 -10
- package/dist/native-pane-recovery.js.map +0 -1
- package/dist/native-pane-tools.d.ts +0 -2
- package/dist/native-pane-tools.js +0 -14
- package/dist/native-pane-tools.js.map +0 -1
- package/dist/pane-port.d.ts +0 -118
- package/dist/pane-port.js +0 -2
- package/dist/pane-port.js.map +0 -1
- package/dist/runtime-api.d.ts +0 -8
- package/dist/runtime-api.js +0 -171
- package/dist/runtime-api.js.map +0 -1
- package/dist/runtime-assets/@xterm/addon-serialize/LICENSE +0 -21
- package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.js +0 -2
- package/dist/runtime-assets/@xterm/addon-serialize/lib/addon-serialize.mjs +0 -19
- package/dist/runtime-assets/@xterm/addon-serialize/package.json +0 -29
- package/dist/runtime-assets/@xterm/headless/LICENSE +0 -21
- package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.js +0 -2
- package/dist/runtime-assets/@xterm/headless/lib-headless/xterm-headless.mjs +0 -46
- package/dist/runtime-assets/@xterm/headless/package.json +0 -29
- package/dist/runtime-assets/node-pty/CONPTY-LICENSE +0 -21
- package/dist/runtime-assets/node-pty/CONPTY-NOTICE.md +0 -503
- package/dist/runtime-assets/node-pty/LICENSE +0 -69
- package/dist/runtime-assets/node-pty/lib/conpty_console_list_agent.js +0 -16
- package/dist/runtime-assets/node-pty/lib/eventEmitter2.js +0 -47
- package/dist/runtime-assets/node-pty/lib/index.js +0 -52
- package/dist/runtime-assets/node-pty/lib/interfaces.js +0 -7
- package/dist/runtime-assets/node-pty/lib/shared/conout.js +0 -11
- package/dist/runtime-assets/node-pty/lib/terminal.js +0 -190
- package/dist/runtime-assets/node-pty/lib/types.js +0 -7
- package/dist/runtime-assets/node-pty/lib/unixTerminal.js +0 -346
- package/dist/runtime-assets/node-pty/lib/utils.js +0 -39
- package/dist/runtime-assets/node-pty/lib/windowsConoutConnection.js +0 -125
- package/dist/runtime-assets/node-pty/lib/windowsPtyAgent.js +0 -320
- package/dist/runtime-assets/node-pty/lib/windowsTerminal.js +0 -199
- package/dist/runtime-assets/node-pty/lib/worker/conoutSocketWorker.js +0 -22
- package/dist/runtime-assets/node-pty/package.json +0 -64
- 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-build-info.d.ts +0 -9
- package/dist/runtime-build-info.js +0 -57
- package/dist/runtime-build-info.js.map +0 -1
- package/dist/runtime-bundle.d.ts +0 -19
- package/dist/runtime-bundle.js +0 -156
- package/dist/runtime-bundle.js.map +0 -1
- package/dist/runtime-cli.d.ts +0 -7
- package/dist/runtime-cli.js +0 -152
- package/dist/runtime-cli.js.map +0 -1
- package/dist/runtime-client-store.d.ts +0 -68
- package/dist/runtime-client-store.js +0 -344
- package/dist/runtime-client-store.js.map +0 -1
- package/dist/runtime-client.d.ts +0 -112
- package/dist/runtime-client.js +0 -736
- package/dist/runtime-client.js.map +0 -1
- package/dist/runtime-connection.d.ts +0 -22
- package/dist/runtime-connection.js +0 -93
- package/dist/runtime-connection.js.map +0 -1
- package/dist/runtime-coordinator.d.ts +0 -91
- package/dist/runtime-coordinator.js +0 -911
- package/dist/runtime-coordinator.js.map +0 -1
- package/dist/runtime-general-protocol.d.ts +0 -22
- package/dist/runtime-general-protocol.js +0 -57
- package/dist/runtime-general-protocol.js.map +0 -1
- package/dist/runtime-general-workspace.d.ts +0 -17
- package/dist/runtime-general-workspace.js +0 -213
- package/dist/runtime-general-workspace.js.map +0 -1
- package/dist/runtime-pane-assets.d.ts +0 -17
- package/dist/runtime-pane-assets.js +0 -23
- package/dist/runtime-pane-assets.js.map +0 -1
- package/dist/runtime-pane-boot.d.ts +0 -9
- package/dist/runtime-pane-boot.js +0 -47
- package/dist/runtime-pane-boot.js.map +0 -1
- package/dist/runtime-pane-codex-index.d.ts +0 -1
- package/dist/runtime-pane-codex-index.js +0 -47
- package/dist/runtime-pane-codex-index.js.map +0 -1
- package/dist/runtime-pane-config.d.ts +0 -16
- package/dist/runtime-pane-config.js +0 -54
- package/dist/runtime-pane-config.js.map +0 -1
- package/dist/runtime-pane-host.d.ts +0 -44
- package/dist/runtime-pane-host.js +0 -424
- package/dist/runtime-pane-host.js.map +0 -1
- package/dist/runtime-pane-job-owner.d.ts +0 -1
- package/dist/runtime-pane-job-owner.js +0 -29
- package/dist/runtime-pane-job-owner.js.map +0 -1
- package/dist/runtime-pane-job.ps1 +0 -116
- package/dist/runtime-pane-link.d.ts +0 -40
- package/dist/runtime-pane-link.js +0 -246
- package/dist/runtime-pane-link.js.map +0 -1
- package/dist/runtime-pane-mcp-protocol.d.ts +0 -32
- package/dist/runtime-pane-mcp-protocol.js +0 -39
- package/dist/runtime-pane-mcp-protocol.js.map +0 -1
- package/dist/runtime-pane-mcp-relay.d.ts +0 -17
- package/dist/runtime-pane-mcp-relay.js +0 -70
- package/dist/runtime-pane-mcp-relay.js.map +0 -1
- package/dist/runtime-pane-mcp.d.ts +0 -51
- package/dist/runtime-pane-mcp.js +0 -150
- package/dist/runtime-pane-mcp.js.map +0 -1
- package/dist/runtime-pane-native.d.ts +0 -45
- package/dist/runtime-pane-native.js +0 -65
- package/dist/runtime-pane-native.js.map +0 -1
- package/dist/runtime-pane-output.d.ts +0 -19
- package/dist/runtime-pane-output.js +0 -53
- package/dist/runtime-pane-output.js.map +0 -1
- package/dist/runtime-pane-port.d.ts +0 -139
- package/dist/runtime-pane-port.js +0 -558
- package/dist/runtime-pane-port.js.map +0 -1
- package/dist/runtime-pane-private.d.ts +0 -14
- package/dist/runtime-pane-private.js +0 -51
- package/dist/runtime-pane-private.js.map +0 -1
- package/dist/runtime-pane-private.ps1 +0 -14
- package/dist/runtime-pane-process.d.ts +0 -4
- package/dist/runtime-pane-process.js +0 -108
- package/dist/runtime-pane-process.js.map +0 -1
- package/dist/runtime-pane-protocol.d.ts +0 -172
- package/dist/runtime-pane-protocol.js +0 -195
- package/dist/runtime-pane-protocol.js.map +0 -1
- package/dist/runtime-pane-screen.d.ts +0 -19
- package/dist/runtime-pane-screen.js +0 -76
- package/dist/runtime-pane-screen.js.map +0 -1
- package/dist/runtime-pane-service.d.ts +0 -11
- package/dist/runtime-pane-service.js +0 -221
- package/dist/runtime-pane-service.js.map +0 -1
- package/dist/runtime-pane-stdio-protocol.d.ts +0 -54
- package/dist/runtime-pane-stdio-protocol.js +0 -94
- package/dist/runtime-pane-stdio-protocol.js.map +0 -1
- package/dist/runtime-pane-stdio.d.ts +0 -3
- package/dist/runtime-pane-stdio.js +0 -123
- package/dist/runtime-pane-stdio.js.map +0 -1
- package/dist/runtime-pane-store.d.ts +0 -33
- package/dist/runtime-pane-store.js +0 -164
- package/dist/runtime-pane-store.js.map +0 -1
- package/dist/runtime-pane-transcript-protocol.d.ts +0 -43
- package/dist/runtime-pane-transcript-protocol.js +0 -58
- package/dist/runtime-pane-transcript-protocol.js.map +0 -1
- package/dist/runtime-pane-transcript.d.ts +0 -45
- package/dist/runtime-pane-transcript.js +0 -605
- package/dist/runtime-pane-transcript.js.map +0 -1
- package/dist/runtime-pane-worker.d.ts +0 -1
- package/dist/runtime-pane-worker.js +0 -154
- package/dist/runtime-pane-worker.js.map +0 -1
- package/dist/runtime-protocol.d.ts +0 -162
- package/dist/runtime-protocol.js +0 -37
- package/dist/runtime-protocol.js.map +0 -1
- package/dist/runtime-switches.d.ts +0 -79
- package/dist/runtime-switches.js +0 -90
- package/dist/runtime-switches.js.map +0 -1
- package/dist/runtime-transcript-delta-reader.d.ts +0 -23
- package/dist/runtime-transcript-delta-reader.js +0 -74
- package/dist/runtime-transcript-delta-reader.js.map +0 -1
- package/dist/runtime-transcript-source.d.ts +0 -36
- package/dist/runtime-transcript-source.js +0 -189
- package/dist/runtime-transcript-source.js.map +0 -1
- package/dist/runtime-windows-bridge.d.ts +0 -41
- package/dist/runtime-windows-bridge.js +0 -157
- package/dist/runtime-windows-bridge.js.map +0 -1
- package/dist/settings-route-lifetime.d.ts +0 -14
- package/dist/settings-route-lifetime.js +0 -48
- package/dist/settings-route-lifetime.js.map +0 -1
- package/dist/tmux-pane-host.d.ts +0 -26
- package/dist/tmux-pane-host.js +0 -25
- package/dist/tmux-pane-host.js.map +0 -1
- package/dist/transcript-needles.d.ts +0 -35
- package/dist/transcript-needles.js +0 -158
- package/dist/transcript-needles.js.map +0 -1
- package/dist/transcript-records.d.ts +0 -21
- package/dist/transcript-records.js +0 -140
- package/dist/transcript-records.js.map +0 -1
- package/dist/ui/agent-proposals.js +0 -90
- package/dist/ui/agent-runtime-settings.js +0 -67
- package/dist/ui/agent-runtime.js +0 -124
- package/dist/ui/cli-picker.js +0 -42
- package/dist/ui/general-location.js +0 -71
- package/dist/ui/native-instance-settings.js +0 -44
- package/dist/ui/native-pane-recovery.js +0 -45
- package/dist/ui/panel-runtimes.js +0 -334
- package/dist/ui/panel-windows-agent.js +0 -86
- package/dist/ui/panel-windows-cli.js +0 -179
- package/dist/ui/provider-marks.js +0 -83
- package/dist/ui/runtime-catalog.js +0 -55
- package/dist/ui/settings-agent-filters.js +0 -94
- package/dist/ui/settings-layout.js +0 -245
- package/dist/ui/settings-outside.js +0 -176
- package/dist/ui/settings-shortcuts.js +0 -65
- package/dist/ui/shared/machine-name.js +0 -13
- package/dist/ui/shared/qrcode.module.js +0 -2237
- package/dist/ui/shared/settings-width.js +0 -31
- package/dist/ui/shared/vendor/qrcode-generator.LICENSE +0 -27
- package/dist/ui/windows-agents-nav.js +0 -2
- package/dist/ui/windows-cli-catalog.js +0 -108
- package/dist/web-remote-access.d.ts +0 -152
- package/dist/web-remote-access.js +0 -368
- package/dist/web-remote-access.js.map +0 -1
- package/dist/windows-agents.d.ts +0 -126
- package/dist/windows-agents.js +0 -490
- package/dist/windows-agents.js.map +0 -1
- package/dist/windows-cli-api.d.ts +0 -23
- package/dist/windows-cli-api.js +0 -159
- package/dist/windows-cli-api.js.map +0 -1
- package/dist/windows-cli-providers.d.ts +0 -115
- package/dist/windows-cli-providers.js +0 -429
- package/dist/windows-cli-providers.js.map +0 -1
- package/dist/windows-cli-worker.d.ts +0 -59
- package/dist/windows-cli-worker.js +0 -479
- package/dist/windows-cli-worker.js.map +0 -1
- package/dist/windows-cli.d.ts +0 -116
- package/dist/windows-cli.js +0 -407
- package/dist/windows-cli.js.map +0 -1
- package/dist/windows-native-mcp.d.ts +0 -19
- package/dist/windows-native-mcp.js +0 -143
- package/dist/windows-native-mcp.js.map +0 -1
- package/dist/windows-pane-mcp.d.ts +0 -26
- package/dist/windows-pane-mcp.js +0 -187
- package/dist/windows-pane-mcp.js.map +0 -1
- package/templates/runtime-assets/conpty-LICENSE +0 -21
- package/templates/runtime-assets/conpty-NOTICE.md +0 -503
- package/templates/runtime-assets/local-pane-stage.ps1 +0 -101
- package/templates/runtime-assets/runtime-pane-job.ps1 +0 -116
- package/templates/runtime-assets/runtime-pane-private.ps1 +0 -14
- package/templates/runtime-assets/xterm-LICENSE +0 -21
|
@@ -1,911 +0,0 @@
|
|
|
1
|
-
/** Durable machine registration and outbound-client transport; never starts a remote shell. */
|
|
2
|
-
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { WebSocket, WebSocketServer } from "ws";
|
|
6
|
-
import { readBoundedWebBody } from "./web-body.js";
|
|
7
|
-
import { atomicWriteFileSync } from "./atomic-write.js";
|
|
8
|
-
import { validateNativeTask } from "./windows-cli-worker.js";
|
|
9
|
-
import { inventoryProbed } from "./windows-cli.js";
|
|
10
|
-
import { isNativeProviderId } from "./windows-cli-providers.js";
|
|
11
|
-
import { validatePaneMcpBody } from "./runtime-pane-mcp-protocol.js";
|
|
12
|
-
import { validateRuntimePaneRequest, validateRuntimePaneSnapshot, validateRuntimePaneShutdown } from "./runtime-pane-protocol.js";
|
|
13
|
-
import { RUNTIME_CLAIM_PATH, RUNTIME_CONNECT_PATH, RUNTIME_MAX_FRAME_BYTES, RUNTIME_PAIR_PATH, RUNTIME_ROTATE_PATH, runtimeFingerprintFromHash, parseRuntimeInfo, RUNTIME_PROTOCOL_VERSION } from "./runtime-protocol.js";
|
|
14
|
-
const PAIR_TTL = 10 * 60_000;
|
|
15
|
-
const MAX_STORE_BYTES = 64 * 1024 * 1024;
|
|
16
|
-
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i;
|
|
17
|
-
const secret = () => randomBytes(32).toString("hex");
|
|
18
|
-
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
19
|
-
const equal = (value, digest) => /^[a-f0-9]{64}$/i.test(digest) && timingSafeEqual(Buffer.from(hash(value), "hex"), Buffer.from(digest, "hex"));
|
|
20
|
-
const record = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
21
|
-
/** A deliberate validation failure whose static message is safe to show the runtime; every other error (I/O, internal) is not. */
|
|
22
|
-
class RuntimeProtocolError extends Error {
|
|
23
|
-
}
|
|
24
|
-
const text = (v, max) => typeof v === "string" && v.length > 0 && v.length <= max && !/[\0\r\n]/.test(v);
|
|
25
|
-
const terminal = (task) => !!task.finishedAt && ["completed", "failed", "cancelled"].includes(task.status);
|
|
26
|
-
const loopback = (address) => address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
|
|
27
|
-
function privateTransport(req) {
|
|
28
|
-
return req.headers.origin === undefined && req.headers.cookie === undefined
|
|
29
|
-
&& (loopback(req.socket.remoteAddress) || req.socket.encrypted === true);
|
|
30
|
-
}
|
|
31
|
-
function stable(value, depth = 0) {
|
|
32
|
-
if (depth > 64)
|
|
33
|
-
throw Error("Runtime request is too deeply nested");
|
|
34
|
-
if (Array.isArray(value))
|
|
35
|
-
return `[${value.map(value => stable(value, depth + 1)).join(",")}]`;
|
|
36
|
-
if (record(value))
|
|
37
|
-
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stable(value[key], depth + 1)}`).join(",")}}`;
|
|
38
|
-
return JSON.stringify(value) ?? "null";
|
|
39
|
-
}
|
|
40
|
-
function inventory(value) {
|
|
41
|
-
if (!record(value) || typeof value.home !== "string" || value.home.length > 1024 || !Array.isArray(value.clis) || value.clis.length > 16)
|
|
42
|
-
throw new RuntimeProtocolError("Invalid runtime inventory");
|
|
43
|
-
const seen = new Set();
|
|
44
|
-
for (const row of value.clis) {
|
|
45
|
-
if (!record(row) || !isNativeProviderId(row.id) || seen.has(row.id) || !record(row.supports)
|
|
46
|
-
|| typeof row.runnable !== "boolean" || typeof row.readOnlySupport !== "boolean"
|
|
47
|
-
|| ![true, false, null].includes(row.authenticated)
|
|
48
|
-
|| !(row.path === null || text(row.path, 2048)) || !(row.version === null || text(row.version, 256))
|
|
49
|
-
|| typeof row.permissionNote !== "string" || row.permissionNote.length > 2048
|
|
50
|
-
|| !Array.isArray(row.models) || row.models.length > 256)
|
|
51
|
-
throw new RuntimeProtocolError("Invalid runtime CLI inventory");
|
|
52
|
-
for (const model of row.models)
|
|
53
|
-
if (!record(model) || !text(model.id, 256) || model.name !== undefined && !text(model.name, 256)
|
|
54
|
-
|| model.description !== undefined && (typeof model.description !== "string" || model.description.length > 2048))
|
|
55
|
-
throw new RuntimeProtocolError("Invalid runtime model inventory");
|
|
56
|
-
if (Object.values(row.supports).some(v => typeof v !== "boolean"))
|
|
57
|
-
throw new RuntimeProtocolError("Invalid runtime capabilities");
|
|
58
|
-
seen.add(row.id);
|
|
59
|
-
}
|
|
60
|
-
if (Buffer.byteLength(JSON.stringify(value)) > RUNTIME_MAX_FRAME_BYTES / 2)
|
|
61
|
-
throw new RuntimeProtocolError("Runtime inventory is too large");
|
|
62
|
-
return structuredClone(value);
|
|
63
|
-
}
|
|
64
|
-
function machineView(machine) {
|
|
65
|
-
return { ...structuredClone(machine), runtimeCompatibility: machine.runtimeInfo
|
|
66
|
-
? machine.runtimeInfo.protocolVersions.includes(RUNTIME_PROTOCOL_VERSION) ? "compatible" : "incompatible"
|
|
67
|
-
: machine.bootId ? "legacy" : "unknown" };
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* M-2239 P2-1 (#2936 review): the journal is compacted, never just capped. A settled command (failed, or accepted with a
|
|
71
|
-
* terminal, process-confirmed outcome) older than COMMAND_KEEP_MS, or the oldest settled ones beyond COMMAND_SOFT_CAP,
|
|
72
|
-
* become tombstones; pending and uncertain commands are never compacted. Tombstones are evicted only beyond
|
|
73
|
-
* TOMBSTONE_CAP and only when older than TOMBSTONE_KEEP_MS — a replayed id is refused for at least that long.
|
|
74
|
-
*/
|
|
75
|
-
const COMMAND_HARD_CAP = 4096, COMMAND_SOFT_CAP = 2048, COMMAND_KEEP_MS = 7 * 24 * 3_600_000;
|
|
76
|
-
const TOMBSTONE_CAP = 65_536, TOMBSTONE_KEEP_MS = 30 * 24 * 3_600_000;
|
|
77
|
-
/**
|
|
78
|
-
* alpha.2 polish: what an interrupted command becomes. A probe only reads the runner's CLI inventory — nothing to reconcile
|
|
79
|
-
* if its answer was lost — so it settles as failed (compacted like any settled command); every other operation stays
|
|
80
|
-
* uncertain (never compacted, never sent again). The automatic probe on connect must not leave a trail in the journal.
|
|
81
|
-
*/
|
|
82
|
-
const interrupted = (command) => command.operation === "probe" ? "failed" : "uncertain";
|
|
83
|
-
export class RuntimeCoordinator {
|
|
84
|
-
options;
|
|
85
|
-
paneEpoch = randomUUID();
|
|
86
|
-
path;
|
|
87
|
-
state;
|
|
88
|
-
server = new WebSocketServer({ noServer: true, maxPayload: RUNTIME_MAX_FRAME_BYTES, perMessageDeflate: false });
|
|
89
|
-
connections = new Map();
|
|
90
|
-
pending = new Map();
|
|
91
|
-
paneToolPending = new Set();
|
|
92
|
-
panePending = new Map();
|
|
93
|
-
listeners = new Set();
|
|
94
|
-
limits = new Map();
|
|
95
|
-
heartbeat;
|
|
96
|
-
closed = false;
|
|
97
|
-
constructor(home, options = {}) {
|
|
98
|
-
this.options = options;
|
|
99
|
-
mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
100
|
-
this.path = join(home, "runtime-machines.json");
|
|
101
|
-
if (existsSync(this.path)) {
|
|
102
|
-
const stat = lstatSync(this.path);
|
|
103
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_STORE_BYTES)
|
|
104
|
-
throw Error("Invalid runtime machine store file");
|
|
105
|
-
}
|
|
106
|
-
this.state = existsSync(this.path) ? JSON.parse(readFileSync(this.path, "utf8")) : { version: 1, machines: [], pairings: [], commands: [] };
|
|
107
|
-
if (this.state.version !== 1 || !Array.isArray(this.state.machines) || !Array.isArray(this.state.pairings) || !Array.isArray(this.state.commands)
|
|
108
|
-
|| this.state.tombstones !== undefined && !Array.isArray(this.state.tombstones))
|
|
109
|
-
throw Error("Invalid runtime machine store");
|
|
110
|
-
this.state.tombstones ??= [];
|
|
111
|
-
if (this.state.machines.length > 256 || this.state.pairings.length > 16 || this.state.commands.length > COMMAND_HARD_CAP || this.state.tombstones.length > TOMBSTONE_CAP
|
|
112
|
-
|| this.state.tombstones.some(row => !record(row) || !text(row.id, 128) || !UUID.test(String(row.machineId)) || !/^[a-f0-9]{64}$/.test(row.digest) || !Number.isFinite(row.at))
|
|
113
|
-
|| this.state.machines.some(row => !record(row) || !record(row.machine) || !UUID.test(String(row.machine.id)) || !text(row.machine.name, 100)
|
|
114
|
-
|| row.machine.platform !== "win32" || !["pending", "offline", "online", "revoked"].includes(String(row.machine.status))
|
|
115
|
-
|| !Number.isSafeInteger(row.machine.generation) || row.machine.generation < 0
|
|
116
|
-
|| row.machine.pairingFingerprint !== undefined && (typeof row.machine.pairingFingerprint !== "string" || !/^[A-F0-9]{4}(?:-[A-F0-9]{4}){2}$/.test(row.machine.pairingFingerprint))
|
|
117
|
-
|| row.credentialHash !== undefined && !/^[a-f0-9]{64}$/.test(row.credentialHash)
|
|
118
|
-
|| row.claimHash !== undefined && !/^[a-f0-9]{64}$/.test(row.claimHash))
|
|
119
|
-
|| new Set(this.state.machines.map(row => row.machine.id)).size !== this.state.machines.length
|
|
120
|
-
|| this.state.pairings.some(row => !record(row) || !UUID.test(String(row.id)) || !/^[a-f0-9]{64}$/.test(row.codeHash) || !Number.isFinite(row.expiresAt))
|
|
121
|
-
|| this.state.commands.some(row => !record(row) || !text(row.id, 128) || !UUID.test(String(row.machineId)) || !/^[a-f0-9]{64}$/.test(row.digest)
|
|
122
|
-
|| !["probe", "workspace", "run", "cancel", "reconcile", "recover"].includes(row.operation)
|
|
123
|
-
|| !["pending", "accepted", "failed", "uncertain"].includes(row.state) || !Number.isSafeInteger(row.seq) || row.seq < 0))
|
|
124
|
-
throw Error("Malformed runtime machine store");
|
|
125
|
-
// A coordinator restart never grants execution authority or redelivers an in-flight command.
|
|
126
|
-
for (const row of this.state.machines) {
|
|
127
|
-
if (row.machine.status === "online")
|
|
128
|
-
row.machine.status = "offline";
|
|
129
|
-
if (row.claimHash)
|
|
130
|
-
row.machine.pairingFingerprint = runtimeFingerprintFromHash(row.claimHash);
|
|
131
|
-
}
|
|
132
|
-
for (const command of this.state.commands)
|
|
133
|
-
if (command.state === "pending")
|
|
134
|
-
command.state = interrupted(command);
|
|
135
|
-
for (const row of this.state.machines)
|
|
136
|
-
if (row.machine.runtimeInfo !== undefined)
|
|
137
|
-
row.machine.runtimeInfo = parseRuntimeInfo(row.machine.runtimeInfo);
|
|
138
|
-
this.save();
|
|
139
|
-
this.heartbeat = setInterval(() => {
|
|
140
|
-
for (const connection of this.connections.values()) {
|
|
141
|
-
if (!connection.alive) {
|
|
142
|
-
connection.ws.terminate();
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
connection.alive = false;
|
|
146
|
-
connection.ws.ping();
|
|
147
|
-
}
|
|
148
|
-
}, 15_000);
|
|
149
|
-
this.heartbeat.unref();
|
|
150
|
-
}
|
|
151
|
-
now() { return this.options.now?.() ?? Date.now(); }
|
|
152
|
-
save() {
|
|
153
|
-
const value = JSON.stringify(this.state);
|
|
154
|
-
if (Buffer.byteLength(value) > MAX_STORE_BYTES)
|
|
155
|
-
throw Error("Runtime machine journal limit reached");
|
|
156
|
-
for (let attempt = 0;; attempt++) {
|
|
157
|
-
try {
|
|
158
|
-
atomicWriteFileSync(this.path, value, { mode: 0o600 });
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
// Windows scanners can briefly hold a destination open; retain atomic replacement and a bounded retry.
|
|
163
|
-
if (process.platform !== "win32" || attempt >= 4 || !["EPERM", "EACCES", "EBUSY"].includes(error.code ?? ""))
|
|
164
|
-
throw error;
|
|
165
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10 * (attempt + 1));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
emit(event) {
|
|
170
|
-
try {
|
|
171
|
-
this.options.changed?.(event);
|
|
172
|
-
}
|
|
173
|
-
catch { /* UI observers cannot change the durable verdict. */ }
|
|
174
|
-
for (const listener of this.listeners) {
|
|
175
|
-
try {
|
|
176
|
-
listener(structuredClone(event));
|
|
177
|
-
}
|
|
178
|
-
catch { /* Observer isolation. */ }
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
entry(id) { const row = this.state.machines.find(v => v.machine.id === id); if (!row)
|
|
182
|
-
throw Error("Runtime machine not found"); return row; }
|
|
183
|
-
listMachines() { return this.state.machines.map(row => machineView(row.machine)); }
|
|
184
|
-
machine(id) { const row = this.state.machines.find(v => v.machine.id === id); return row ? machineView(row.machine) : null; }
|
|
185
|
-
subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
|
|
186
|
-
createPairing() {
|
|
187
|
-
this.state.pairings = this.state.pairings.filter(row => row.expiresAt > this.now());
|
|
188
|
-
if (this.state.pairings.length >= 16)
|
|
189
|
-
throw Error("Too many pending runtime pairing codes");
|
|
190
|
-
const code = randomBytes(6).toString("hex").toUpperCase(), pairingId = randomUUID(), expiresAt = this.now() + PAIR_TTL;
|
|
191
|
-
this.state.pairings.push({ id: pairingId, codeHash: hash(code), expiresAt });
|
|
192
|
-
this.save();
|
|
193
|
-
return { pairingId, code: code.match(/.{4}/g).join("-"), expiresAt };
|
|
194
|
-
}
|
|
195
|
-
pair(input) {
|
|
196
|
-
if (!record(input) || Object.keys(input).some(k => !["code", "name", "platform", "hostname"].includes(k))
|
|
197
|
-
|| !text(input.code, 32) || !text(input.name, 100) || !input.name.trim() || input.platform !== "win32"
|
|
198
|
-
|| input.hostname !== undefined && !text(input.hostname, 255))
|
|
199
|
-
throw Error("Invalid runtime pairing request");
|
|
200
|
-
if (this.state.machines.length >= 256 || this.state.machines.filter(row => row.machine.status !== "revoked").length >= 64)
|
|
201
|
-
throw Error("Runtime machine limit reached");
|
|
202
|
-
const code = input.code.replaceAll("-", "").toUpperCase();
|
|
203
|
-
const pairing = this.state.pairings.find(row => row.expiresAt > this.now() && equal(code, row.codeHash));
|
|
204
|
-
if (!pairing)
|
|
205
|
-
throw Error("Pairing code is invalid, used or expired");
|
|
206
|
-
const machineId = randomUUID(), claimSecret = secret();
|
|
207
|
-
this.state.pairings = this.state.pairings.filter(row => row !== pairing);
|
|
208
|
-
this.state.machines.push({ machine: { id: machineId, name: input.name.trim(), platform: "win32", ...(input.hostname ? { hostname: input.hostname } : {}), status: "pending", createdAt: this.now(), generation: 0, pairingFingerprint: runtimeFingerprintFromHash(hash(claimSecret)) }, claimHash: hash(claimSecret), claimExpiresAt: pairing.expiresAt });
|
|
209
|
-
this.save();
|
|
210
|
-
this.emit({ type: "machine", machineId });
|
|
211
|
-
return { machineId, claimSecret, status: "pending", expiresAt: pairing.expiresAt };
|
|
212
|
-
}
|
|
213
|
-
claim(input) {
|
|
214
|
-
if (!record(input) || Object.keys(input).some(k => !["machineId", "claimSecret"].includes(k)) || !text(input.machineId, 64) || !text(input.claimSecret, 128))
|
|
215
|
-
throw Error("Invalid runtime claim");
|
|
216
|
-
const row = this.state.machines.find(row => row.machine.id === input.machineId);
|
|
217
|
-
if (!row?.claimHash || !row.claimExpiresAt || row.claimExpiresAt <= this.now() || !equal(input.claimSecret, row.claimHash) || row.machine.status === "revoked")
|
|
218
|
-
throw Error("Runtime claim is invalid or expired");
|
|
219
|
-
if (!row.machine.approvedAt)
|
|
220
|
-
return { status: "pending" };
|
|
221
|
-
const credential = secret();
|
|
222
|
-
row.credentialHash = hash(credential);
|
|
223
|
-
delete row.claimHash;
|
|
224
|
-
delete row.claimExpiresAt;
|
|
225
|
-
this.save();
|
|
226
|
-
return { machineId: row.machine.id, credential, status: "approved" };
|
|
227
|
-
}
|
|
228
|
-
approve(id, fingerprint) {
|
|
229
|
-
const row = this.entry(id);
|
|
230
|
-
if (row.machine.status !== "pending" || !row.claimExpiresAt || row.claimExpiresAt <= this.now())
|
|
231
|
-
throw Error("Runtime pairing is no longer pending");
|
|
232
|
-
if (!fingerprint || fingerprint !== row.machine.pairingFingerprint)
|
|
233
|
-
throw Error("Compare the pairing fingerprint shown by this Runtime before approving");
|
|
234
|
-
row.machine.approvedAt = this.now();
|
|
235
|
-
row.machine.status = "offline";
|
|
236
|
-
this.save();
|
|
237
|
-
this.emit({ type: "machine", machineId: id });
|
|
238
|
-
return structuredClone(row.machine);
|
|
239
|
-
}
|
|
240
|
-
rename(id, name) {
|
|
241
|
-
if (!text(name, 100) || !name.trim())
|
|
242
|
-
throw Error("Invalid runtime machine name");
|
|
243
|
-
const row = this.entry(id);
|
|
244
|
-
row.machine.name = name.trim();
|
|
245
|
-
this.save();
|
|
246
|
-
this.emit({ type: "machine", machineId: id });
|
|
247
|
-
return structuredClone(row.machine);
|
|
248
|
-
}
|
|
249
|
-
revoke(id) {
|
|
250
|
-
const row = this.entry(id);
|
|
251
|
-
row.machine.status = "revoked";
|
|
252
|
-
delete row.credentialHash;
|
|
253
|
-
delete row.claimHash;
|
|
254
|
-
delete row.claimExpiresAt;
|
|
255
|
-
this.save();
|
|
256
|
-
const connection = this.connections.get(id);
|
|
257
|
-
if (connection) {
|
|
258
|
-
this.connections.delete(id);
|
|
259
|
-
this.rejectPending(connection, "Runtime machine was revoked");
|
|
260
|
-
this.options.warn?.({ machineId: id, code: 1008, reason: "Machine revoked" }, "Runtime connection closed because the machine was revoked");
|
|
261
|
-
connection.ws.close(1008, "Machine revoked");
|
|
262
|
-
}
|
|
263
|
-
this.emit({ type: "machine", machineId: id });
|
|
264
|
-
return structuredClone(row.machine);
|
|
265
|
-
}
|
|
266
|
-
credentialOwner(auth) {
|
|
267
|
-
if (typeof auth !== "string" || !/^Bearer [a-f0-9]{64}$/i.test(auth))
|
|
268
|
-
return;
|
|
269
|
-
return this.state.machines.find(row => row.credentialHash && row.machine.status !== "revoked" && row.machine.approvedAt && equal(auth.slice(7), row.credentialHash));
|
|
270
|
-
}
|
|
271
|
-
/** Valid capabilities never share the invalid-traffic bucket of a loopback proxy. Forwarding headers are untrusted. */
|
|
272
|
-
rate(key, limit) {
|
|
273
|
-
const now = this.now();
|
|
274
|
-
for (const [id, value] of this.limits)
|
|
275
|
-
if (value.until <= now)
|
|
276
|
-
this.limits.delete(id);
|
|
277
|
-
if (!this.limits.has(key) && this.limits.size >= 512) {
|
|
278
|
-
const invalid = [...this.limits.keys()].find(id => id.startsWith("invalid:"));
|
|
279
|
-
if (!invalid || key.startsWith("invalid:"))
|
|
280
|
-
return false;
|
|
281
|
-
this.limits.delete(invalid);
|
|
282
|
-
}
|
|
283
|
-
const old = this.limits.get(key), row = old && old.until > now ? old : { count: 0, until: now + 60_000 };
|
|
284
|
-
this.limits.set(key, row);
|
|
285
|
-
return ++row.count <= limit;
|
|
286
|
-
}
|
|
287
|
-
requestRateKey(path, input, req) {
|
|
288
|
-
if (path === RUNTIME_ROTATE_PATH) {
|
|
289
|
-
const row = this.credentialOwner(req.headers.authorization);
|
|
290
|
-
if (row)
|
|
291
|
-
return `rotate:${row.machine.id}`;
|
|
292
|
-
}
|
|
293
|
-
else if (record(input)) {
|
|
294
|
-
if (path === RUNTIME_PAIR_PATH && text(input.code, 32)) {
|
|
295
|
-
const code = input.code.replaceAll("-", "").toUpperCase();
|
|
296
|
-
const row = this.state.pairings.find(row => row.expiresAt > this.now() && equal(code, row.codeHash));
|
|
297
|
-
if (row)
|
|
298
|
-
return `pair:${row.id}`;
|
|
299
|
-
}
|
|
300
|
-
if (path === RUNTIME_CLAIM_PATH && text(input.claimSecret, 128)) {
|
|
301
|
-
const row = this.state.machines.find(row => row.machine.id === input.machineId && row.claimHash && row.claimExpiresAt > this.now()
|
|
302
|
-
&& row.machine.status !== "revoked" && equal(input.claimSecret, row.claimHash));
|
|
303
|
-
if (row)
|
|
304
|
-
return `claim:${row.machine.id}`;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return `invalid:${req.socket.remoteAddress ?? "unknown"}`;
|
|
308
|
-
}
|
|
309
|
-
rotateCredential(auth, input) {
|
|
310
|
-
const row = this.credentialOwner(auth);
|
|
311
|
-
if (!row || !record(input) || Object.keys(input).some(key => !["machineId", "credential"].includes(key))
|
|
312
|
-
|| input.machineId !== row.machine.id || typeof input.credential !== "string" || !/^[a-f0-9]{64}$/.test(input.credential))
|
|
313
|
-
throw Error("Invalid runtime credential rotation");
|
|
314
|
-
if (!equal(input.credential, row.credentialHash)) {
|
|
315
|
-
const before = { credentialHash: row.credentialHash, status: row.machine.status };
|
|
316
|
-
row.credentialHash = hash(input.credential);
|
|
317
|
-
row.machine.status = "offline";
|
|
318
|
-
try {
|
|
319
|
-
this.save();
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
row.credentialHash = before.credentialHash;
|
|
323
|
-
row.machine.status = before.status;
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
const connection = this.connections.get(row.machine.id);
|
|
327
|
-
if (connection) {
|
|
328
|
-
this.connections.delete(row.machine.id);
|
|
329
|
-
this.rejectPending(connection, "Runtime credential rotated; reconnect to reconcile");
|
|
330
|
-
connection.ws.close(1012, "Credential rotated");
|
|
331
|
-
}
|
|
332
|
-
this.emit({ type: "machine", machineId: row.machine.id });
|
|
333
|
-
}
|
|
334
|
-
return { machineId: row.machine.id, status: "rotated" };
|
|
335
|
-
}
|
|
336
|
-
handlePublicRequest(req, res, url) {
|
|
337
|
-
if (![RUNTIME_PAIR_PATH, RUNTIME_CLAIM_PATH, RUNTIME_ROTATE_PATH].includes(url.pathname))
|
|
338
|
-
return false;
|
|
339
|
-
const answer = (status, body) => { if (!res.destroyed && !res.writableEnded) {
|
|
340
|
-
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
341
|
-
res.end(JSON.stringify(body));
|
|
342
|
-
} };
|
|
343
|
-
if (this.closed || req.method !== "POST" || url.search || !privateTransport(req)) {
|
|
344
|
-
answer(403, { error: "Runtime pairing requires a protected non-browser connection" });
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
let charged = false;
|
|
348
|
-
void readBoundedWebBody(req, 4096).then(bytes => {
|
|
349
|
-
const value = JSON.parse(bytes.toString("utf8"));
|
|
350
|
-
if (this.closed) {
|
|
351
|
-
answer(403, { error: "Runtime coordinator is closed" });
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const key = this.requestRateKey(url.pathname, value, req);
|
|
355
|
-
charged = true;
|
|
356
|
-
if (!this.rate(key, key.startsWith("claim:") ? 120 : 30)) {
|
|
357
|
-
answer(429, { error: "Too many runtime pairing requests" });
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (url.pathname === RUNTIME_ROTATE_PATH) {
|
|
361
|
-
if (!this.credentialOwner(req.headers.authorization)) {
|
|
362
|
-
answer(401, { error: "Runtime credential was refused" });
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
answer(200, this.rotateCredential(req.headers.authorization, value));
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (url.pathname === RUNTIME_PAIR_PATH)
|
|
369
|
-
answer(201, this.pair(value));
|
|
370
|
-
else {
|
|
371
|
-
const result = this.claim(value);
|
|
372
|
-
answer(result.status === "pending" ? 202 : 200, result);
|
|
373
|
-
}
|
|
374
|
-
}).catch(() => {
|
|
375
|
-
const allowed = charged || this.rate(`invalid:${req.socket.remoteAddress ?? "unknown"}`, 30);
|
|
376
|
-
answer(allowed ? 400 : 429, { error: "Runtime pairing request is invalid, expired or already used" });
|
|
377
|
-
});
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
handleUpgrade(req, socket, head) {
|
|
381
|
-
let url;
|
|
382
|
-
try {
|
|
383
|
-
url = new URL(req.url ?? "", "http://localhost");
|
|
384
|
-
}
|
|
385
|
-
catch {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
if (url.pathname !== RUNTIME_CONNECT_PATH)
|
|
389
|
-
return false;
|
|
390
|
-
const refuse = () => { socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); };
|
|
391
|
-
const auth = req.headers.authorization;
|
|
392
|
-
if (this.closed || req.method !== "GET" || url.search || !privateTransport(req) || typeof auth !== "string" || !/^Bearer [a-f0-9]{64}$/i.test(auth)) {
|
|
393
|
-
refuse();
|
|
394
|
-
return true;
|
|
395
|
-
}
|
|
396
|
-
const row = this.credentialOwner(auth);
|
|
397
|
-
if (!row) {
|
|
398
|
-
refuse();
|
|
399
|
-
return true;
|
|
400
|
-
}
|
|
401
|
-
this.server.handleUpgrade(req, socket, head, ws => this.attach(ws, row));
|
|
402
|
-
return true;
|
|
403
|
-
}
|
|
404
|
-
current(connection) { return !this.closed && this.connections.get(connection.machineId) === connection && this.entry(connection.machineId).machine.status !== "revoked"; }
|
|
405
|
-
attach(ws, row) {
|
|
406
|
-
const old = this.connections.get(row.machine.id);
|
|
407
|
-
if (old) {
|
|
408
|
-
this.rejectPending(old, "Runtime connection was replaced; execution must be reconciled");
|
|
409
|
-
this.options.warn?.({ machineId: row.machine.id, code: 1008, reason: "Connection replaced" }, "Runtime connection closed because the same machine credential connected again");
|
|
410
|
-
old.ws.close(1008, "Connection replaced");
|
|
411
|
-
}
|
|
412
|
-
row.machine.generation++;
|
|
413
|
-
row.machine.status = "offline";
|
|
414
|
-
this.save();
|
|
415
|
-
const connection = { ws, machineId: row.machine.id, generation: row.machine.generation, ready: false, alive: true, helloTimer: setTimeout(() => ws.close(1008, "Runtime hello required"), 10_000) };
|
|
416
|
-
connection.helloTimer.unref();
|
|
417
|
-
this.connections.set(row.machine.id, connection);
|
|
418
|
-
ws.on("pong", () => { connection.alive = true; if (this.current(connection))
|
|
419
|
-
row.machine.lastSeenAt = this.now(); });
|
|
420
|
-
ws.on("message", (data, binary) => {
|
|
421
|
-
if (binary) {
|
|
422
|
-
ws.close(1008, "Runtime requires JSON text frames");
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
void this.message(connection, data).catch(error => {
|
|
426
|
-
// The cause (a static validation message) goes to the log and, trimmed, to the runtime so its terminal can show it.
|
|
427
|
-
const reason = error instanceof RuntimeProtocolError ? `Invalid runtime message: ${error.message}`.replace(/[^ -~]/g, "?").slice(0, 120) : "Invalid runtime message";
|
|
428
|
-
this.options.warn?.({ machineId: connection.machineId, code: 1008, reason, detail: error instanceof Error ? error.message : String(error) }, "Runtime connection closed because of an invalid runtime message");
|
|
429
|
-
ws.close(1008, reason);
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
ws.on("error", () => { });
|
|
433
|
-
ws.on("close", () => {
|
|
434
|
-
clearTimeout(connection.helloTimer);
|
|
435
|
-
if (this.connections.get(row.machine.id) !== connection)
|
|
436
|
-
return;
|
|
437
|
-
this.connections.delete(row.machine.id);
|
|
438
|
-
this.rejectPending(connection, "Runtime disconnected; reconcile before retrying");
|
|
439
|
-
if (row.machine.status !== "revoked")
|
|
440
|
-
row.machine.status = "offline";
|
|
441
|
-
this.save();
|
|
442
|
-
this.emit({ type: "machine", machineId: row.machine.id });
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
send(connection, value) {
|
|
446
|
-
if (!this.current(connection) || connection.ws.readyState !== WebSocket.OPEN)
|
|
447
|
-
throw Error("Runtime machine is offline");
|
|
448
|
-
const frame = JSON.stringify(value);
|
|
449
|
-
if (Buffer.byteLength(frame) > RUNTIME_MAX_FRAME_BYTES)
|
|
450
|
-
throw Error("Runtime response exceeds the frame limit");
|
|
451
|
-
if (connection.ws.bufferedAmount > RUNTIME_MAX_FRAME_BYTES * 2) {
|
|
452
|
-
connection.ws.close(1013, "Runtime backpressure");
|
|
453
|
-
throw Error("Runtime connection is too slow");
|
|
454
|
-
}
|
|
455
|
-
connection.ws.send(frame);
|
|
456
|
-
}
|
|
457
|
-
rejectPending(connection, reason) {
|
|
458
|
-
for (const [id, pending] of this.panePending)
|
|
459
|
-
if (pending.machineId === connection.machineId && pending.generation === connection.generation) {
|
|
460
|
-
this.panePending.delete(id);
|
|
461
|
-
clearTimeout(pending.timer);
|
|
462
|
-
pending.reject(Error(reason));
|
|
463
|
-
}
|
|
464
|
-
let changed = false;
|
|
465
|
-
for (const [id, pending] of this.pending)
|
|
466
|
-
if (pending.machineId === connection.machineId && pending.generation === connection.generation) {
|
|
467
|
-
this.pending.delete(id);
|
|
468
|
-
clearTimeout(pending.timer);
|
|
469
|
-
const command = this.state.commands.find(row => row.id === id);
|
|
470
|
-
if (command?.state === "pending") {
|
|
471
|
-
command.state = interrupted(command);
|
|
472
|
-
command.error = reason;
|
|
473
|
-
changed = true;
|
|
474
|
-
}
|
|
475
|
-
pending.reject(Error(reason));
|
|
476
|
-
}
|
|
477
|
-
if (changed)
|
|
478
|
-
this.save();
|
|
479
|
-
}
|
|
480
|
-
validateTask(command, value) {
|
|
481
|
-
if (command.operation !== "run" || !record(command.payload) || !record(command.payload.task) || !record(value))
|
|
482
|
-
throw new RuntimeProtocolError("Unexpected runtime task");
|
|
483
|
-
const input = command.payload.task;
|
|
484
|
-
if (value.id !== input.requestId || value.cli !== input.cli || value.cwd !== input.cwd || value.sandbox !== input.sandbox || (value.model ?? "") !== (input.model ?? "")
|
|
485
|
-
|| !["starting", "running", "cancelling", "completed", "failed", "cancelled"].includes(String(value.status))
|
|
486
|
-
|| typeof value.startedAt !== "number" || !Number.isFinite(value.startedAt) || typeof value.output !== "string" || value.output.length > 64 * 1024
|
|
487
|
-
|| value.finishedAt !== undefined && (typeof value.finishedAt !== "number" || !Number.isFinite(value.finishedAt))
|
|
488
|
-
|| ["completed", "failed", "cancelled"].includes(String(value.status)) && (typeof value.finishedAt !== "number" || typeof value.outcomeConfirmed !== "boolean")
|
|
489
|
-
|| value.error !== undefined && (typeof value.error !== "string" || value.error.length > 8192)
|
|
490
|
-
|| value.threadId !== undefined && !text(value.threadId, 256)
|
|
491
|
-
|| value.threadId !== undefined && command.payload.resumeSessionId !== undefined && value.threadId !== command.payload.resumeSessionId
|
|
492
|
-
|| value.outcomeConfirmed !== undefined && typeof value.outcomeConfirmed !== "boolean")
|
|
493
|
-
throw new RuntimeProtocolError("Invalid runtime task snapshot");
|
|
494
|
-
return { id: value.id, cli: value.cli, cwd: value.cwd, sandbox: value.sandbox, ...(value.model ? { model: value.model } : {}),
|
|
495
|
-
status: value.status, startedAt: value.startedAt, output: value.output,
|
|
496
|
-
...(value.finishedAt !== undefined ? { finishedAt: value.finishedAt } : {}), ...(value.error ? { error: value.error } : {}), ...(value.threadId ? { threadId: value.threadId } : {}), ...(value.outcomeConfirmed !== undefined ? { outcomeConfirmed: value.outcomeConfirmed } : {}) };
|
|
497
|
-
}
|
|
498
|
-
async message(connection, raw) {
|
|
499
|
-
if (!this.current(connection))
|
|
500
|
-
return;
|
|
501
|
-
const value = JSON.parse(raw.toString());
|
|
502
|
-
if (!record(value) || value.v !== 1 || !text(value.type, 32))
|
|
503
|
-
throw new RuntimeProtocolError("Invalid runtime frame");
|
|
504
|
-
const row = this.entry(connection.machineId);
|
|
505
|
-
connection.alive = true;
|
|
506
|
-
if (!connection.ready) {
|
|
507
|
-
if (value.type !== "hello" || !text(value.bootId, 128))
|
|
508
|
-
throw new RuntimeProtocolError("Runtime hello required");
|
|
509
|
-
const runtimeInfo = parseRuntimeInfo(value.runtimeInfo);
|
|
510
|
-
connection.ready = true;
|
|
511
|
-
connection.bootId = value.bootId;
|
|
512
|
-
clearTimeout(connection.helloTimer);
|
|
513
|
-
row.machine.bootId = value.bootId;
|
|
514
|
-
row.machine.status = "online";
|
|
515
|
-
row.machine.lastSeenAt = this.now();
|
|
516
|
-
if (runtimeInfo)
|
|
517
|
-
row.machine.runtimeInfo = runtimeInfo;
|
|
518
|
-
else
|
|
519
|
-
delete row.machine.runtimeInfo;
|
|
520
|
-
if (value.paneProtocol === 1)
|
|
521
|
-
row.machine.paneProtocol = 1;
|
|
522
|
-
else
|
|
523
|
-
delete row.machine.paneProtocol;
|
|
524
|
-
if (value.inventory !== undefined) {
|
|
525
|
-
row.machine.inventory = inventory(value.inventory);
|
|
526
|
-
row.machine.inventoryUpdatedAt = this.now();
|
|
527
|
-
}
|
|
528
|
-
this.save();
|
|
529
|
-
this.send(connection, { v: 1, type: "welcome", machineId: connection.machineId, generation: connection.generation });
|
|
530
|
-
this.emit({ type: "machine", machineId: connection.machineId });
|
|
531
|
-
// alpha.2 polish (dev-claude's no-chat e2e): a runner that brings no CLI inventory (just paired, nothing detected
|
|
532
|
-
// yet) is asked for its own report now — its detection runs on that PC, never on this fleet's loop — so the first
|
|
533
|
-
// approval of an agent's request there is not refused for "no available CLI" until someone presses Refresh.
|
|
534
|
-
// (#3004 rig: the runner's hello may carry a placeholder — `probedAt: null`, no CLIs — which is no report either.)
|
|
535
|
-
// (#3005 rig recheck: `force` — without it the runner answers with its cached inventory, on a fresh profile the same
|
|
536
|
-
// placeholder; Refresh sends force, and so does this: detection runs on that PC.)
|
|
537
|
-
if (!inventoryProbed(row.machine.inventory))
|
|
538
|
-
void this.call(connection.machineId, "probe", { force: true }).catch(() => { });
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
row.machine.lastSeenAt = this.now();
|
|
542
|
-
if (value.type === "pane.tool.request") {
|
|
543
|
-
if (row.machine.paneProtocol !== 1 || Object.keys(value).some(key => !["v", "type", "id", "body"].includes(key)) || !UUID.test(String(value.id)))
|
|
544
|
-
throw new RuntimeProtocolError("Invalid pane MCP transport request");
|
|
545
|
-
const body = validatePaneMcpBody(value.body), id = value.id;
|
|
546
|
-
const key = `${connection.machineId}:${connection.generation}:${id}`;
|
|
547
|
-
if (this.paneToolPending.size >= 128 || this.paneToolPending.has(key))
|
|
548
|
-
throw new RuntimeProtocolError("Pane MCP transport backlog or duplicate request");
|
|
549
|
-
let active = true;
|
|
550
|
-
const current = () => active && !this.closed && this.current(connection) && connection.ready && connection.ws.readyState === WebSocket.OPEN;
|
|
551
|
-
this.paneToolPending.add(key);
|
|
552
|
-
let timer;
|
|
553
|
-
try {
|
|
554
|
-
if (!current() || !this.options.handlePaneMcp)
|
|
555
|
-
throw new RuntimeProtocolError("Pane MCP scope unavailable");
|
|
556
|
-
const result = await Promise.race([this.options.handlePaneMcp(connection.machineId, body, current), new Promise((_resolve, reject) => {
|
|
557
|
-
timer = setTimeout(() => { active = false; reject(Error("Pane MCP deadline")); }, 80_000);
|
|
558
|
-
timer.unref();
|
|
559
|
-
})]);
|
|
560
|
-
if (current())
|
|
561
|
-
this.send(connection, { v: 1, type: "pane.tool.result", id, ok: true, result });
|
|
562
|
-
}
|
|
563
|
-
catch {
|
|
564
|
-
if (current())
|
|
565
|
-
this.send(connection, { v: 1, type: "pane.tool.result", id, ok: false });
|
|
566
|
-
}
|
|
567
|
-
finally {
|
|
568
|
-
active = false;
|
|
569
|
-
clearTimeout(timer);
|
|
570
|
-
this.paneToolPending.delete(key);
|
|
571
|
-
}
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
if (value.type === "pane.output") {
|
|
575
|
-
if (row.machine.paneProtocol !== 1)
|
|
576
|
-
throw new RuntimeProtocolError("Pane protocol was not negotiated");
|
|
577
|
-
const snapshot = validateRuntimePaneSnapshot(value.snapshot);
|
|
578
|
-
this.emit({ type: "pane", machineId: connection.machineId, pane: snapshot });
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
if (value.type === "pane.response") {
|
|
582
|
-
if (row.machine.paneProtocol !== 1 || !UUID.test(String(value.id)) || typeof value.ok !== "boolean")
|
|
583
|
-
throw new RuntimeProtocolError("Invalid pane transport response");
|
|
584
|
-
const pending = this.panePending.get(value.id);
|
|
585
|
-
if (!pending)
|
|
586
|
-
return; // A late reply proves nothing and never replays a command.
|
|
587
|
-
if (pending.machineId !== connection.machineId || pending.generation !== connection.generation)
|
|
588
|
-
throw new RuntimeProtocolError("Pane response belongs to another connection");
|
|
589
|
-
this.panePending.delete(value.id);
|
|
590
|
-
clearTimeout(pending.timer);
|
|
591
|
-
if (value.ok)
|
|
592
|
-
pending.resolve(value.result);
|
|
593
|
-
else
|
|
594
|
-
pending.reject(Error(typeof value.error === "string" ? value.error.slice(0, 1000) : "Pane operation failed"));
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
if (value.type === "inventory") {
|
|
598
|
-
row.machine.inventory = inventory(value.inventory);
|
|
599
|
-
row.machine.inventoryUpdatedAt = this.now();
|
|
600
|
-
this.save();
|
|
601
|
-
this.emit({ type: "machine", machineId: connection.machineId });
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
if (value.type === "task") {
|
|
605
|
-
if (!record(value.task) || !Number.isSafeInteger(value.seq) || value.seq < 1)
|
|
606
|
-
throw new RuntimeProtocolError("Invalid runtime task event");
|
|
607
|
-
const taskValue = value.task;
|
|
608
|
-
const command = this.state.commands.find(command => command.id === taskValue.id && command.machineId === connection.machineId && command.operation === "run");
|
|
609
|
-
if (!command) {
|
|
610
|
-
// #2945 review: a reconnecting runtime republishes what it retained; a run already settled and compacted here
|
|
611
|
-
// is acknowledged by its tombstone and changes nothing. An unknown id, or another machine's, still closes.
|
|
612
|
-
if (this.tombstoned(connection.machineId, taskValue.id))
|
|
613
|
-
return;
|
|
614
|
-
throw new RuntimeProtocolError("Runtime task does not belong to this machine");
|
|
615
|
-
}
|
|
616
|
-
const task = this.validateTask(command, value.task);
|
|
617
|
-
if (value.seq <= command.seq)
|
|
618
|
-
return;
|
|
619
|
-
if (command.task && terminal(command.task) && !terminal(task))
|
|
620
|
-
throw new RuntimeProtocolError("Runtime task cannot restart");
|
|
621
|
-
command.seq = value.seq;
|
|
622
|
-
command.task = task;
|
|
623
|
-
command.state = "accepted";
|
|
624
|
-
this.save();
|
|
625
|
-
this.emit({ type: "task", machineId: connection.machineId, task });
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
if (value.type === "response") {
|
|
629
|
-
if (!text(value.id, 128) || typeof value.ok !== "boolean")
|
|
630
|
-
throw new RuntimeProtocolError("Invalid runtime response");
|
|
631
|
-
const pending = this.pending.get(value.id), command = this.state.commands.find(command => command.id === value.id);
|
|
632
|
-
if (!command || command.machineId !== connection.machineId) {
|
|
633
|
-
if (!command && this.tombstoned(connection.machineId, value.id))
|
|
634
|
-
return;
|
|
635
|
-
throw new RuntimeProtocolError("Runtime response does not belong to this machine");
|
|
636
|
-
}
|
|
637
|
-
if (!pending || pending.generation !== connection.generation)
|
|
638
|
-
return;
|
|
639
|
-
this.pending.delete(value.id);
|
|
640
|
-
clearTimeout(pending.timer);
|
|
641
|
-
try {
|
|
642
|
-
if (!value.ok) {
|
|
643
|
-
command.state = "failed";
|
|
644
|
-
command.error = typeof value.error === "string" ? value.error.slice(0, 1024) : "Runtime request failed";
|
|
645
|
-
this.save();
|
|
646
|
-
pending.reject(Error(command.error));
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
let result = value.result;
|
|
650
|
-
if (command.operation === "probe") {
|
|
651
|
-
result = inventory(result);
|
|
652
|
-
row.machine.inventory = result;
|
|
653
|
-
row.machine.inventoryUpdatedAt = this.now();
|
|
654
|
-
}
|
|
655
|
-
if (command.operation === "run") {
|
|
656
|
-
const task = this.validateTask(command, result);
|
|
657
|
-
if (!command.task || !terminal(command.task))
|
|
658
|
-
command.task = task;
|
|
659
|
-
result = command.task;
|
|
660
|
-
}
|
|
661
|
-
command.state = "accepted";
|
|
662
|
-
command.result = result;
|
|
663
|
-
this.save();
|
|
664
|
-
if (command.task)
|
|
665
|
-
this.emit({ type: "task", machineId: connection.machineId, task: command.task });
|
|
666
|
-
else if (command.operation === "probe")
|
|
667
|
-
this.emit({ type: "machine", machineId: connection.machineId });
|
|
668
|
-
pending.resolve(structuredClone(result));
|
|
669
|
-
}
|
|
670
|
-
catch (error) {
|
|
671
|
-
command.state = "uncertain";
|
|
672
|
-
this.save();
|
|
673
|
-
pending.reject(error instanceof Error ? error : Error("Invalid runtime response"));
|
|
674
|
-
throw error;
|
|
675
|
-
}
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
if (value.type === "tool.call") {
|
|
679
|
-
if (!text(value.id, 128) || !text(value.runId, 128))
|
|
680
|
-
throw new RuntimeProtocolError("Invalid runtime tool call");
|
|
681
|
-
const command = this.state.commands.find(command => command.id === value.runId && command.machineId === connection.machineId && command.operation === "run");
|
|
682
|
-
try {
|
|
683
|
-
if (!command || !record(command.payload) || !record(command.payload.mcp) || command.task && terminal(command.task) || !this.options.handleMcp)
|
|
684
|
-
throw new RuntimeProtocolError("Runtime tool call has no active turn");
|
|
685
|
-
const body = value.body;
|
|
686
|
-
if (!record(body) || Object.keys(body).some(key => !["op", "method", "params", "callId"].includes(key))
|
|
687
|
-
|| body.op !== "native-mcp" || !record(body.params) || Buffer.byteLength(JSON.stringify(body)) > 64 * 1024)
|
|
688
|
-
throw new RuntimeProtocolError("Invalid runtime MCP request");
|
|
689
|
-
if (body.method === "tools/list") {
|
|
690
|
-
if (Object.keys(body.params).length || body.callId !== undefined)
|
|
691
|
-
throw new RuntimeProtocolError("Invalid runtime MCP listing");
|
|
692
|
-
}
|
|
693
|
-
else if (body.method === "tools/call") {
|
|
694
|
-
if (!text(body.callId, 128) || !/^[A-Za-z0-9._:-]+$/.test(body.callId)
|
|
695
|
-
|| Object.keys(body.params).some(key => !["name", "arguments"].includes(key)) || typeof body.params.name !== "string"
|
|
696
|
-
|| !Array.isArray(command.payload.mcp.tools) || !command.payload.mcp.tools.includes(body.params.name)
|
|
697
|
-
|| body.params.arguments !== undefined && !record(body.params.arguments))
|
|
698
|
-
throw new RuntimeProtocolError("Runtime MCP tool is outside this turn");
|
|
699
|
-
stable(body.params);
|
|
700
|
-
}
|
|
701
|
-
else
|
|
702
|
-
throw new RuntimeProtocolError("Invalid runtime MCP operation");
|
|
703
|
-
const result = await this.options.handleMcp(connection.machineId, value.runId, body);
|
|
704
|
-
if (!this.current(connection))
|
|
705
|
-
return;
|
|
706
|
-
this.send(connection, { v: 1, type: "tool.result", id: value.id, ok: true, result });
|
|
707
|
-
}
|
|
708
|
-
catch {
|
|
709
|
-
if (this.current(connection))
|
|
710
|
-
this.send(connection, { v: 1, type: "tool.result", id: value.id, ok: false, error: "Runtime tool call was refused or its turn expired" });
|
|
711
|
-
}
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
throw new RuntimeProtocolError("Unknown runtime message");
|
|
715
|
-
}
|
|
716
|
-
tombstoned(machineId, id) {
|
|
717
|
-
return typeof id === "string" && this.state.tombstones.some(row => row.id === id && row.machineId === machineId);
|
|
718
|
-
}
|
|
719
|
-
/** M-2239 P2-1: settled commands to tombstones (see COMMAND_KEEP_MS); never a pending, uncertain or unconfirmed one. */
|
|
720
|
-
compact() {
|
|
721
|
-
const now = this.now();
|
|
722
|
-
const settled = (command) => !this.pending.has(command.id) && (command.state === "failed"
|
|
723
|
-
|| command.state === "accepted" && (command.operation !== "run" || !!command.task && terminal(command.task) && command.task.outcomeConfirmed === true));
|
|
724
|
-
const drop = new Set(this.state.commands.filter(command => settled(command) && now - command.createdAt > COMMAND_KEEP_MS));
|
|
725
|
-
const excess = this.state.commands.length - drop.size - COMMAND_SOFT_CAP;
|
|
726
|
-
if (excess > 0)
|
|
727
|
-
for (const command of this.state.commands.filter(command => settled(command) && !drop.has(command)).sort((a, b) => a.createdAt - b.createdAt).slice(0, excess))
|
|
728
|
-
drop.add(command);
|
|
729
|
-
if (!drop.size)
|
|
730
|
-
return;
|
|
731
|
-
// Room for new tombstones without passing TOMBSTONE_CAP (what the constructor reads back, #2945 review): old ones
|
|
732
|
-
// past TOMBSTONE_KEEP_MS give way, oldest first; a younger one never does. Commands beyond that room stay as they
|
|
733
|
-
// are — still refused by id from the journal — until room frees up.
|
|
734
|
-
const tombstones = this.state.tombstones;
|
|
735
|
-
const evictable = tombstones.filter(row => now - row.at > TOMBSTONE_KEEP_MS).sort((a, b) => a.at - b.at);
|
|
736
|
-
const room = TOMBSTONE_CAP - tombstones.length + evictable.length;
|
|
737
|
-
const moved = [...drop].sort((a, b) => a.createdAt - b.createdAt).slice(0, Math.max(0, room));
|
|
738
|
-
if (!moved.length)
|
|
739
|
-
return;
|
|
740
|
-
const gone = new Set(evictable.slice(0, Math.max(0, tombstones.length + moved.length - TOMBSTONE_CAP)));
|
|
741
|
-
const movedSet = new Set(moved);
|
|
742
|
-
this.state.tombstones = [...tombstones.filter(row => !gone.has(row)),
|
|
743
|
-
...moved.map(command => ({ id: command.id, machineId: command.machineId, digest: command.digest, at: now }))];
|
|
744
|
-
this.state.commands = this.state.commands.filter(command => !movedSet.has(command));
|
|
745
|
-
this.save();
|
|
746
|
-
}
|
|
747
|
-
task(id) { const task = this.state.commands.find(command => command.id === id && command.operation === "run")?.task; return task ? structuredClone(task) : null; }
|
|
748
|
-
tasks() { return this.state.commands.flatMap(command => command.operation === "run" && command.task ? [{ machineId: command.machineId, task: structuredClone(command.task) }] : []); }
|
|
749
|
-
/** Include journaled sends whose acknowledgement is still missing, so reopening diagnostics cannot hide an uncertain run. */
|
|
750
|
-
diagnosticTasks(machineId) {
|
|
751
|
-
return this.state.commands.flatMap(command => {
|
|
752
|
-
if (command.operation !== "run" || command.machineId !== machineId)
|
|
753
|
-
return [];
|
|
754
|
-
if (command.task)
|
|
755
|
-
return [structuredClone(command.task)];
|
|
756
|
-
let input;
|
|
757
|
-
try {
|
|
758
|
-
input = validateNativeTask(command.payload.task);
|
|
759
|
-
}
|
|
760
|
-
catch {
|
|
761
|
-
return [];
|
|
762
|
-
}
|
|
763
|
-
const uncertain = command.state === "uncertain", rejected = command.state === "failed";
|
|
764
|
-
return [{ id: command.id, cli: input.cli, cwd: input.cwd, sandbox: input.sandbox, ...(input.model ? { model: input.model } : {}),
|
|
765
|
-
startedAt: command.createdAt, status: uncertain || rejected ? "failed" : "starting", output: "",
|
|
766
|
-
...(rejected ? { outcomeConfirmed: true, error: command.error || "Runtime request failed" } : {}),
|
|
767
|
-
...(uncertain ? { outcomeConfirmed: false, error: "Remote execution outcome is unconfirmed. Reconnect this runtime to reconcile before sending another task." } : {}) }];
|
|
768
|
-
}).sort((a, b) => b.startedAt - a.startedAt).slice(0, 20);
|
|
769
|
-
}
|
|
770
|
-
hasActiveTask(machineId) { return this.state.commands.some(command => command.machineId === machineId && command.operation === "run" && command.state !== "failed" && (!command.task || !terminal(command.task) || command.task.outcomeConfirmed === false)); }
|
|
771
|
-
paneTransportEpoch(machineId) {
|
|
772
|
-
const connection = this.connections.get(machineId);
|
|
773
|
-
return connection?.ready && connection.ws.readyState === WebSocket.OPEN && this.current(connection) ? `${this.paneEpoch}:${machineId}:${connection.generation}` : null;
|
|
774
|
-
}
|
|
775
|
-
/** Pane input replay/sequence authority lives in its pane ledger; reads never fill the exec-task journal. */
|
|
776
|
-
async callPane(machineId, raw, maxAgents = 4) {
|
|
777
|
-
const request = validateRuntimePaneRequest(raw);
|
|
778
|
-
if (!Number.isInteger(maxAgents) || maxAgents < 1 || maxAgents > 16)
|
|
779
|
-
throw Error("Invalid machine pane limit");
|
|
780
|
-
const machine = this.entry(machineId).machine, connection = this.connections.get(machineId);
|
|
781
|
-
if (this.closed || machine.status !== "online" || machine.paneProtocol !== 1 || !connection?.ready || !this.current(connection))
|
|
782
|
-
throw Error("Pane runtime is offline or unsupported");
|
|
783
|
-
if (this.panePending.size >= 64)
|
|
784
|
-
throw Error("Pane transport capacity reached");
|
|
785
|
-
const id = randomUUID();
|
|
786
|
-
return new Promise((resolve, reject) => {
|
|
787
|
-
const timer = setTimeout(() => { this.panePending.delete(id); reject(Error("Pane response timed out; reconcile before another input")); }, this.options.requestTimeoutMs ?? 30_000);
|
|
788
|
-
timer.unref();
|
|
789
|
-
this.panePending.set(id, { machineId, generation: connection.generation, resolve, reject, timer });
|
|
790
|
-
try {
|
|
791
|
-
this.send(connection, { v: 1, type: "pane.request", id, maxAgents, request });
|
|
792
|
-
}
|
|
793
|
-
catch (error) {
|
|
794
|
-
this.panePending.delete(id);
|
|
795
|
-
clearTimeout(timer);
|
|
796
|
-
reject(error);
|
|
797
|
-
}
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
/** Explicit switch-off only, after owned pane closes. A normal hub close/restart never shuts its hosts down. */
|
|
801
|
-
async shutdownPaneHosts() {
|
|
802
|
-
const machines = this.listMachines().filter(machine => machine.status === "online" && machine.paneProtocol === 1);
|
|
803
|
-
const outcomes = await Promise.allSettled(machines.map(async (machine) => {
|
|
804
|
-
const raw = await this.callPane(machine.id, { action: "list" });
|
|
805
|
-
if (!raw || typeof raw.hostBootId !== "string" || !/^[a-f0-9-]{36}$/i.test(raw.hostBootId) || !Array.isArray(raw.panes) || raw.panes.length > 4096)
|
|
806
|
-
throw Error("Pane shutdown inventory is unconfirmed");
|
|
807
|
-
if (raw.panes.map(validateRuntimePaneSnapshot).some(pane => pane.state !== "exited"))
|
|
808
|
-
throw Error("Pane shutdown still has live or unconfirmed work");
|
|
809
|
-
validateRuntimePaneShutdown(await this.callPane(machine.id, { action: "shutdown", hostBootId: raw.hostBootId }), raw.hostBootId);
|
|
810
|
-
}));
|
|
811
|
-
if (outcomes.some(result => result.status === "rejected"))
|
|
812
|
-
throw Error("One or more pane hosts could not confirm empty shutdown; retained work stays reserved");
|
|
813
|
-
}
|
|
814
|
-
async call(machineId, operation, payload) {
|
|
815
|
-
if (this.closed || !["probe", "workspace", "run", "cancel", "reconcile", "recover"].includes(operation))
|
|
816
|
-
throw Error("Unsupported runtime operation");
|
|
817
|
-
const machine = this.entry(machineId).machine;
|
|
818
|
-
const connection = this.connections.get(machineId);
|
|
819
|
-
if (machine.status !== "online" || !connection?.ready || !this.current(connection))
|
|
820
|
-
throw Error("Runtime machine is offline");
|
|
821
|
-
let id = randomUUID(), normalized = payload;
|
|
822
|
-
if (operation === "run") {
|
|
823
|
-
if (!record(payload) || Object.keys(payload).some(k => !["task", "resumeSessionId", "context", "mcp"].includes(k)))
|
|
824
|
-
throw Error("Invalid runtime run payload");
|
|
825
|
-
const task = validateNativeTask(payload.task);
|
|
826
|
-
if (payload.resumeSessionId !== undefined && !text(payload.resumeSessionId, 256) || payload.context !== undefined && (typeof payload.context !== "string" || payload.context.length > 2000 || payload.context.includes("\0")))
|
|
827
|
-
throw Error("Invalid runtime turn context");
|
|
828
|
-
if (payload.mcp !== undefined && (!record(payload.mcp) || Object.keys(payload.mcp).some(k => k !== "tools") || !Array.isArray(payload.mcp.tools) || !payload.mcp.tools.length || payload.mcp.tools.length > 128 || payload.mcp.tools.some(v => typeof v !== "string" || !/^[a-z][a-z0-9_]{0,63}$/.test(v))))
|
|
829
|
-
throw Error("Invalid runtime tool scope");
|
|
830
|
-
normalized = { ...payload, task };
|
|
831
|
-
id = task.requestId;
|
|
832
|
-
}
|
|
833
|
-
if (operation === "recover") {
|
|
834
|
-
if (!record(payload) || Object.keys(payload).some(key => !["runId", "confirmedStopped"].includes(key))
|
|
835
|
-
|| !text(payload.runId, 80) || payload.confirmedStopped !== true)
|
|
836
|
-
throw Error("Runtime recovery requires explicit stopped-process confirmation");
|
|
837
|
-
const original = this.state.commands.find(command => command.id === payload.runId && command.machineId === machineId && command.operation === "run");
|
|
838
|
-
if (!original)
|
|
839
|
-
throw Error("Runtime recovery does not belong to this machine");
|
|
840
|
-
// A runtime that never received the original request can still save a no-execution tombstone after manual
|
|
841
|
-
// confirmation. The original is server-owned; callers cannot replace it with another task or tool scope.
|
|
842
|
-
normalized = { runId: payload.runId, confirmedStopped: true, original: structuredClone(original.payload) };
|
|
843
|
-
}
|
|
844
|
-
const digest = hash(stable({ operation, payload: normalized }));
|
|
845
|
-
if (Buffer.byteLength(JSON.stringify(normalized)) > RUNTIME_MAX_FRAME_BYTES / 2)
|
|
846
|
-
throw Error("Runtime command payload is too large");
|
|
847
|
-
const existing = this.state.commands.find(command => command.id === id);
|
|
848
|
-
if (existing) {
|
|
849
|
-
if (existing.machineId !== machineId || existing.digest !== digest)
|
|
850
|
-
throw Error("Runtime task ID belongs to a different request or machine");
|
|
851
|
-
if (existing.task)
|
|
852
|
-
return structuredClone(existing.task);
|
|
853
|
-
throw Error("Runtime command is already recorded; reconcile it before retrying");
|
|
854
|
-
}
|
|
855
|
-
const tombstone = this.state.tombstones.find(row => row.id === id);
|
|
856
|
-
if (tombstone) {
|
|
857
|
-
if (tombstone.machineId !== machineId || tombstone.digest !== digest)
|
|
858
|
-
throw Error("Runtime task ID belongs to a different request or machine");
|
|
859
|
-
throw Error("Runtime command already ran and its record was compacted; do not retry it");
|
|
860
|
-
}
|
|
861
|
-
if (operation === "run" && this.hasActiveTask(machineId))
|
|
862
|
-
throw Error("Runtime machine is busy or has an unconfirmed task");
|
|
863
|
-
this.compact();
|
|
864
|
-
if (this.state.commands.length >= COMMAND_HARD_CAP)
|
|
865
|
-
throw Error("Runtime command journal limit reached: too many unsettled commands; reconcile them");
|
|
866
|
-
const command = { id, machineId, operation, digest, payload: normalized, createdAt: this.now(), state: "pending", seq: 0 };
|
|
867
|
-
// Journal is committed before the first byte reaches the execution computer. Never auto-send it again.
|
|
868
|
-
this.state.commands.push(command);
|
|
869
|
-
this.save();
|
|
870
|
-
return new Promise((resolve, reject) => {
|
|
871
|
-
const timer = setTimeout(() => {
|
|
872
|
-
this.pending.delete(id);
|
|
873
|
-
command.state = interrupted(command);
|
|
874
|
-
command.error = command.state === "failed" ? "Runtime inventory request timed out" : "Runtime acknowledgment timed out; reconcile before retrying";
|
|
875
|
-
this.save();
|
|
876
|
-
reject(Error(command.error));
|
|
877
|
-
}, this.options.requestTimeoutMs ?? (operation === "probe" ? 90_000 : 30_000));
|
|
878
|
-
timer.unref();
|
|
879
|
-
this.pending.set(id, { machineId, generation: connection.generation, resolve, reject, timer });
|
|
880
|
-
try {
|
|
881
|
-
this.send(connection, { v: 1, type: "request", id, operation, payload: normalized });
|
|
882
|
-
}
|
|
883
|
-
catch (error) {
|
|
884
|
-
this.pending.delete(id);
|
|
885
|
-
clearTimeout(timer);
|
|
886
|
-
command.state = interrupted(command);
|
|
887
|
-
this.save();
|
|
888
|
-
reject(error);
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
close() {
|
|
893
|
-
if (this.closed)
|
|
894
|
-
return;
|
|
895
|
-
this.closed = true;
|
|
896
|
-
clearInterval(this.heartbeat);
|
|
897
|
-
for (const connection of this.connections.values()) {
|
|
898
|
-
clearTimeout(connection.helloTimer);
|
|
899
|
-
this.rejectPending(connection, "Runtime coordinator stopped; reconcile before retrying");
|
|
900
|
-
connection.ws.terminate();
|
|
901
|
-
}
|
|
902
|
-
this.connections.clear();
|
|
903
|
-
for (const row of this.state.machines)
|
|
904
|
-
if (row.machine.status === "online")
|
|
905
|
-
row.machine.status = "offline";
|
|
906
|
-
this.save();
|
|
907
|
-
this.server.close();
|
|
908
|
-
this.listeners.clear();
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
//# sourceMappingURL=runtime-coordinator.js.map
|