@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
package/dist/fleet-manager.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { NativePaneRecoveryError } from "./native-pane-recovery.js";
|
|
2
|
+
import { nativePaneToolAvailable, nativePaneToolRefusal } from "./native-pane-tools.js";
|
|
3
|
+
import { windowsAgentsFor } from "./windows-agents.js";
|
|
4
|
+
import { buildFleetInstructions } from "./instructions.js";
|
|
1
5
|
import { supportsVerifiedKiroMigration, getCachedKiroCliCompatibility, kiroV1DbPath, kiroV2SessionsDir } from "./backend/kiro.js";
|
|
2
6
|
import { listKiroV1Sessions, readKiroClassicLinks } from "./backend/kiro-identity.js";
|
|
3
7
|
import { readKiroV1SavedModel } from "./backend/kiro-retired-model.js";
|
|
@@ -9,10 +13,12 @@ import { WorkingRecoveryCoordinator, WORKING_RECOVERY_KIND, buildWorkingRecovery
|
|
|
9
13
|
import { PUBLIC_TUNNEL_TOKEN_ENV, PUBLIC_TUNNEL_TOKEN_HINT, normalizePublicTunnelToken } from "./public-tunnel-config.js";
|
|
10
14
|
import { humanOwnerObjectId, webHumanOrigin } from "./human-delivery-receipts.js";
|
|
11
15
|
import { SettingsBaselines } from "./settings-baseline.js";
|
|
12
|
-
import {
|
|
16
|
+
import { prepareSettingsEffect } from "./settings-effect.js";
|
|
17
|
+
import { settingsDisplay } from "./settings-change.js";
|
|
18
|
+
import { SettingsConfirmationError, SettingsConfirmationStore, settingsSourceLabel } from "./settings-confirmation.js";
|
|
13
19
|
import { SettingsHttpConfirmation } from "./settings-http-confirmation.js";
|
|
14
20
|
import { SettingsControlServer } from "./settings-control.js";
|
|
15
|
-
import { SettingsExecution, settingsRevision, noteSettingsWrite, settingsFileResource, assertSettingsLease, trySettingsLease, waitSettingsLease, settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
|
|
21
|
+
import { settingsFingerprint, SettingsExecution, settingsRevision, noteSettingsWrite, settingsFileResource, assertSettingsLease, trySettingsLease, waitSettingsLease, settingsUndo, undoSettingsPaths } from "./settings-transaction.js";
|
|
16
22
|
import { performance } from "node:perf_hooks";
|
|
17
23
|
import { withSpawnHoldReply } from "./spawn-hold-reply.js";
|
|
18
24
|
import { gatewayRequestContext } from "./web-request-context.js";
|
|
@@ -83,7 +89,8 @@ import { startEventLoopWatch } from "./event-loop-watch.js";
|
|
|
83
89
|
import { TmuxManager } from "./tmux-manager.js";
|
|
84
90
|
import { AccessManager } from "./channel/access-manager.js";
|
|
85
91
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
86
|
-
import {
|
|
92
|
+
import { NativeDaemonClient } from "./native-daemon-client.js";
|
|
93
|
+
import { usesChannelAddress, TOOLS } from "./channel/mcp-tools.js";
|
|
87
94
|
import { ParkedNotices, PARKED_NOTICE_LEDGER_FILE } from "./parked-notices.js";
|
|
88
95
|
import { formatFleetTime } from "./tz-utils.js";
|
|
89
96
|
import { createAdapter } from "./channel/factory.js";
|
|
@@ -93,6 +100,7 @@ import { createBackend } from "./backend/factory.js";
|
|
|
93
100
|
import { readEffortMetadata } from "./backend/effort-metadata.js";
|
|
94
101
|
import { codexEffort, flagEffort } from "./backend/launch-effort.js";
|
|
95
102
|
import { codexMetadataHomes, readCodexModelEffortLevels, resolveCodexLaunchModel } from "./backend/codex-metadata.js";
|
|
103
|
+
import { readGrokModelCache } from "./backend/grok-model-cache.js";
|
|
96
104
|
import { CLI_ENV_TTL_MS, isModelCompatible, noteResolvedBinary, resolveBinary, SYSINFO_BACKEND_IDS, UnsupportedCliError, ResumeBlockedError } from "./backend/types.js";
|
|
97
105
|
import { createLogger, rotateLogIfNeeded } from "./logger.js";
|
|
98
106
|
import { processAttachments } from "./channel/attachment-handler.js";
|
|
@@ -167,6 +175,16 @@ export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
|
|
|
167
175
|
return tasks;
|
|
168
176
|
}
|
|
169
177
|
import { handleWebRequest, webPeerOf, broadcastSseEvent, broadcastToGuests, SSE_HEARTBEAT_MS } from "./web-api.js";
|
|
178
|
+
import { windowsCliEnabled, closeWindowsBridge, inventoryProbed, LOCAL_WINDOWS_CLI_OFF } from "./windows-cli.js";
|
|
179
|
+
import { localWindowsCliEnabled, remoteRuntimeEnabled, setConfiguredRuntimeSwitches, maxAgentsPerMachine } from "./runtime-switches.js";
|
|
180
|
+
import { RuntimeCoordinator } from "./runtime-coordinator.js";
|
|
181
|
+
import { RuntimeWindowsBridge } from "./runtime-windows-bridge.js";
|
|
182
|
+
import { FleetWindowsRuntime } from "./fleet-windows-runtime.js";
|
|
183
|
+
import { NativeMcpSessions } from "./native-mcp.js";
|
|
184
|
+
import { nativeProviderMcpSupported } from "./windows-cli-providers.js";
|
|
185
|
+
import { isWindowsInstance, isNativePaneInstance, isNativeExecInstance, WINDOWS_CONFIG_REFUSED } from "./native-instance-registry.js";
|
|
186
|
+
import { createNativeWindowsInstance, updateNativeWindowsInstance } from "./native-instance-api.js";
|
|
187
|
+
import { NativeInstanceConfigEdit, parseNativeInstanceCreate } from "./native-instance-input.js";
|
|
170
188
|
import { LogService } from "./log-service.js";
|
|
171
189
|
import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId, webChatFrom } from "./web-chat-history.js";
|
|
172
190
|
import { WEB_ECHO_COPIES_MAX, WEB_MIRROR_TIMEOUT_MS, webReactionEmoji, webReactionProblem } from "./web-reactions.js";
|
|
@@ -728,6 +746,518 @@ export class FleetManager {
|
|
|
728
746
|
/** Live view of lifecycle.daemons — used throughout; not deprecated. */
|
|
729
747
|
get daemons() { return this.lifecycle.daemons; }
|
|
730
748
|
fleetConfig = null;
|
|
749
|
+
fleetWindowsRuntime = null;
|
|
750
|
+
nativeMcpStore = null;
|
|
751
|
+
paneManager = null;
|
|
752
|
+
localPaneRelay = null;
|
|
753
|
+
openingLocalPane = null;
|
|
754
|
+
runtimeHub = null;
|
|
755
|
+
runtimeExecutor = null;
|
|
756
|
+
runtimeStatusScheduled = false;
|
|
757
|
+
reconciledRemoteResults = new Set();
|
|
758
|
+
/** Machine credentials are separate from dashboard sessions and per-turn agent capabilities. */
|
|
759
|
+
runtimeCoordinator() {
|
|
760
|
+
if (!remoteRuntimeEnabled())
|
|
761
|
+
throw new Error("Remote runtimes are not enabled");
|
|
762
|
+
return this.runtimeHub ??= new RuntimeCoordinator(this.dataDir, {
|
|
763
|
+
handleMcp: (machineId, runId, body) => this.runtimeWindowsExecutor().mcp(machineId, runId, body),
|
|
764
|
+
handlePaneMcp: async (machineId, body, current) => (await this.nativePanes()).handle(machineId, body, current),
|
|
765
|
+
changed: event => {
|
|
766
|
+
if (event?.type === "machine" && this.runtimeHub?.machine(event.machineId)?.status !== "online") {
|
|
767
|
+
for (const [name, config] of Object.entries(this.fleetConfig?.instances ?? {}))
|
|
768
|
+
if (config.machine_id === event.machineId)
|
|
769
|
+
this.nativeMcpStore?.closeInstance(name);
|
|
770
|
+
}
|
|
771
|
+
if (this.runtimeStatusScheduled || this.shuttingDown)
|
|
772
|
+
return;
|
|
773
|
+
this.runtimeStatusScheduled = true;
|
|
774
|
+
queueMicrotask(() => {
|
|
775
|
+
this.runtimeStatusScheduled = false;
|
|
776
|
+
if (this.shuttingDown)
|
|
777
|
+
return;
|
|
778
|
+
try {
|
|
779
|
+
this.fleetWindowsRuntime?.importHistory();
|
|
780
|
+
for (const snapshot of this.runtimeHub?.tasks() ?? [])
|
|
781
|
+
this.reconcileRemoteDelivery(snapshot.machineId, snapshot.task);
|
|
782
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
783
|
+
this.scheduleDeliveryOutboxPump();
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
this.logger.warn({ err: error }, "Runtime status reconciliation failed");
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
runtimeWindowsExecutor() {
|
|
793
|
+
return this.runtimeExecutor ??= new RuntimeWindowsBridge(this, () => remoteRuntimeEnabled() && !this.shuttingDown ? this.runtimeCoordinator() : null);
|
|
794
|
+
}
|
|
795
|
+
/** Per-turn capabilities for native MCP, separate from browser and Linux-agent credentials. */
|
|
796
|
+
nativeMcpSessions() {
|
|
797
|
+
return this.nativeMcpStore ??= new NativeMcpSessions(this);
|
|
798
|
+
}
|
|
799
|
+
/** The same instance registry and delivery entry point, with a native Windows execution adapter. */
|
|
800
|
+
nativeInstanceRuntime() {
|
|
801
|
+
return this.fleetWindowsRuntime ??= new FleetWindowsRuntime({
|
|
802
|
+
owner: this, fleet: () => this.fleetConfig, save: () => this.saveFleetConfig(), stopping: () => this.shuttingDown, history: this.webChatHistory,
|
|
803
|
+
openMcp: (instance, requestId, sandbox, stillCurrent) => this.healthServerListening
|
|
804
|
+
? this.nativeMcpSessions().grant({ instance, requestId, sandbox, stillCurrent, endpoint: `http://127.0.0.1:${this.fleetConfig?.health_port ?? 19280}/agent` }) : null,
|
|
805
|
+
warn: (message, error) => this.logger.warn({ err: error }, message),
|
|
806
|
+
removed: name => this.nativeInstanceRemoved(name),
|
|
807
|
+
paneCurrent: name => this.daemons.get(name)?.nativePaneCurrent?.() === true,
|
|
808
|
+
machineAvailable: id => remoteRuntimeEnabled() && this.runtimeCoordinator().machine(id)?.status === "online",
|
|
809
|
+
validateMachine: (id, cli, sandbox) => {
|
|
810
|
+
const machine = this.runtimeCoordinator().machine(id);
|
|
811
|
+
const provider = machine?.inventory?.clis.find(row => row.id === cli && row.runnable && row.supports.resume);
|
|
812
|
+
if (!machine || machine.status !== "online" || machine.platform !== "win32" || !provider)
|
|
813
|
+
throw new Error("Choose an online runtime and an available CLI");
|
|
814
|
+
if (sandbox === "read-only" && !provider.readOnlySupport)
|
|
815
|
+
throw new Error("This runtime CLI cannot enforce read-only execution");
|
|
816
|
+
},
|
|
817
|
+
emit: (event, data) => {
|
|
818
|
+
this.emitSseEvent(event, event === "status" ? this.getUiStatus() : data);
|
|
819
|
+
if (event === "activity")
|
|
820
|
+
this.scheduleDeliveryOutboxPump();
|
|
821
|
+
},
|
|
822
|
+
}, { bridge: this.runtimeWindowsExecutor() });
|
|
823
|
+
}
|
|
824
|
+
/** Explicit dashboard opt-in, with the same General confirmation and ordinary topic/Daemon routing. */
|
|
825
|
+
async createNativePaneInstance(profile, channelId, authorized, execution) {
|
|
826
|
+
const { createNativePane } = await import("./native-pane-create.js");
|
|
827
|
+
return createNativePane({ fleet: () => this.fleetConfig, runtime: this.nativeInstanceRuntime(),
|
|
828
|
+
topic: target => {
|
|
829
|
+
const world = this.worlds.get(target.id), adapter = world?.adapter;
|
|
830
|
+
if (!world || !adapter?.createTopic || !adapter.deleteTopic)
|
|
831
|
+
throw Error("The approved connection cannot create and compensate a topic");
|
|
832
|
+
const same = () => {
|
|
833
|
+
const channels = this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []);
|
|
834
|
+
const matches = channels.filter(row => (row.id ?? row.type) === target.id);
|
|
835
|
+
return !this.shuttingDown && this.worlds.get(target.id) === world && world.adapter === adapter && matches.length === 1 && matches[0].type === target.type && matches[0].group_id === target.group_id;
|
|
836
|
+
};
|
|
837
|
+
return { current: same, create: name => adapter.createTopic(name), remove: id => adapter.deleteTopic(id) };
|
|
838
|
+
},
|
|
839
|
+
epoch: name => this.lifecycle.epochOf(name),
|
|
840
|
+
start: async (name, config, current) => {
|
|
841
|
+
if (!current())
|
|
842
|
+
throw Error("Native creation expired before start");
|
|
843
|
+
await this.startInstance(name, config, true);
|
|
844
|
+
if (!current())
|
|
845
|
+
throw Error("Native creation expired during start");
|
|
846
|
+
await this.connectIpcToInstance(name);
|
|
847
|
+
if (!current())
|
|
848
|
+
throw Error("Native creation expired during channel connection");
|
|
849
|
+
},
|
|
850
|
+
close: (name, config, epoch) => this.lifecycle.runTransition(name, async (transition) => {
|
|
851
|
+
if (this.fleetConfig?.instances[name] !== config || this.lifecycle.epochOf(name) !== epoch)
|
|
852
|
+
return false;
|
|
853
|
+
await this.stopInstance(name, transition);
|
|
854
|
+
return this.fleetConfig?.instances[name] === config && this.lifecycle.epochOf(name) === epoch + 1;
|
|
855
|
+
}),
|
|
856
|
+
routes: () => { if (this.fleetConfig)
|
|
857
|
+
this.routing.rebuild(this.fleetConfig); },
|
|
858
|
+
warn: error => this.logger.warn({ err: error }, "Native creation cleanup is held; retained identity and topic require attention"), }, profile, channelId, () => !this.shuttingDown && authorized(), execution);
|
|
859
|
+
}
|
|
860
|
+
/** Admin-only settings path: validate first, then close the exact old launch before changing cold fields. */
|
|
861
|
+
async updateNativePaneConfig(name, patch, authorized, transaction) {
|
|
862
|
+
return this.lifecycle.runTransition(name, async (transition) => {
|
|
863
|
+
const before = this.fleetConfig?.instances[name];
|
|
864
|
+
if (!isNativePaneInstance(before))
|
|
865
|
+
throw Error("Persistent Windows instance not found");
|
|
866
|
+
const runtime = this.nativeInstanceRuntime(), generation = this.explicitStopGeneration.get(name) ?? 0;
|
|
867
|
+
let expectedGeneration = generation;
|
|
868
|
+
const current = () => authorized() && !this.shuttingDown && this.fleetConfig?.instances[name] === before
|
|
869
|
+
&& (this.explicitStopGeneration.get(name) ?? 0) === expectedGeneration
|
|
870
|
+
&& (before.machine_id ? remoteRuntimeEnabled() : localWindowsCliEnabled());
|
|
871
|
+
if (!current())
|
|
872
|
+
throw Error("Native settings authority expired");
|
|
873
|
+
runtime.previewUpdate(name, patch);
|
|
874
|
+
const cold = ["working_directory", "model", "systemPrompt", "native_sandbox"].some(key => Object.hasOwn(patch, key) && (patch[key] ?? "") !== (before[key] ?? ""));
|
|
875
|
+
const running = this.daemons.get(name);
|
|
876
|
+
if (cold) {
|
|
877
|
+
if (running && !running.nativePaneHasExited() && (!await running.nativePauseReady({ current, accepts: () => true, revalidate: async () => current() }) || !running.nativePauseStillReady()))
|
|
878
|
+
throw Error("Wait for this Windows agent to be idle before changing its launch settings");
|
|
879
|
+
if (!current())
|
|
880
|
+
throw Error("Native settings authority expired before closing its old pane");
|
|
881
|
+
await this.stopInstance(name, transition);
|
|
882
|
+
expectedGeneration = generation + 1;
|
|
883
|
+
if (!current())
|
|
884
|
+
throw Error("Native settings authority expired after closing its old pane");
|
|
885
|
+
if (runtime.get(name)?.activeId)
|
|
886
|
+
throw Error("Native pane exit is unconfirmed; its settings were not changed");
|
|
887
|
+
}
|
|
888
|
+
const result = runtime.update(name, patch, current, transaction), next = this.fleetConfig.instances[name];
|
|
889
|
+
if (cold && running) {
|
|
890
|
+
if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== next || (this.explicitStopGeneration.get(name) ?? 0) !== expectedGeneration)
|
|
891
|
+
throw Error("Native settings saved; restart authority expired");
|
|
892
|
+
await this.startInstance(name, next, true, "fleet-topic", false, transition);
|
|
893
|
+
}
|
|
894
|
+
return result;
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
async localNativePaneEndpoint() {
|
|
898
|
+
const admit = () => { if (this.shuttingDown || this.localPaneDrain || !localWindowsCliEnabled())
|
|
899
|
+
throw Error("Local Windows runtime is switched off or draining"); };
|
|
900
|
+
admit();
|
|
901
|
+
if (this.localPaneDrain)
|
|
902
|
+
throw Error("Local Windows runtime cleanup is pending");
|
|
903
|
+
if (this.localPaneRelay?.online())
|
|
904
|
+
return this.localPaneRelay;
|
|
905
|
+
if (this.openingLocalPane)
|
|
906
|
+
return this.openingLocalPane;
|
|
907
|
+
const opening = (async () => {
|
|
908
|
+
const [{ LocalPaneEndpoint }, { launchLocalPaneRelay, localPaneScope }, inventory] = await Promise.all([
|
|
909
|
+
import("./local-pane-endpoint.js"), import("./local-pane-launcher.js"), this.runtimeWindowsExecutor().probe({ force: false }),
|
|
910
|
+
]);
|
|
911
|
+
admit();
|
|
912
|
+
this.localPaneRelay?.stop();
|
|
913
|
+
const relay = await LocalPaneEndpoint.open({ scope: localPaneScope(this.dataDir), admit, maxAgents: maxAgentsPerMachine,
|
|
914
|
+
diagnostic: text => this.logger.warn({ stderr: text }, "Local Windows pane relay wrote to stderr"),
|
|
915
|
+
version: provider => inventory.clis.find(row => row.id === provider && row.runnable)?.version ?? null,
|
|
916
|
+
launch: () => launchLocalPaneRelay(this.dataDir, admit),
|
|
917
|
+
mcp: async (body, current) => (await this.nativePanes()).handle("local", body, current),
|
|
918
|
+
});
|
|
919
|
+
try {
|
|
920
|
+
admit();
|
|
921
|
+
this.localPaneRelay = relay;
|
|
922
|
+
return relay;
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
relay.stop();
|
|
926
|
+
throw error;
|
|
927
|
+
}
|
|
928
|
+
})();
|
|
929
|
+
this.openingLocalPane = opening;
|
|
930
|
+
try {
|
|
931
|
+
return await opening;
|
|
932
|
+
}
|
|
933
|
+
finally {
|
|
934
|
+
if (this.openingLocalPane === opening)
|
|
935
|
+
this.openingLocalPane = null;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
/** Persistent ownership is independent of the exec worker's per-turn capabilities. */
|
|
939
|
+
async nativePanes() {
|
|
940
|
+
if (this.paneManager)
|
|
941
|
+
return this.paneManager;
|
|
942
|
+
const { NativePaneManager } = await import("./native-pane-manager.js");
|
|
943
|
+
if (this.paneManager)
|
|
944
|
+
return this.paneManager;
|
|
945
|
+
const store = windowsAgentsFor(this, this.runtimeWindowsExecutor());
|
|
946
|
+
const allowed = (name, tool) => nativePaneToolAvailable(tool) && mayUseTool(resolveToolSet(this.fleetConfig?.instances[name], name), tool)
|
|
947
|
+
&& !dashboardRefusalIn(this.fleetConfig, name, tool);
|
|
948
|
+
return this.paneManager = new NativePaneManager({
|
|
949
|
+
home: this.dataDir, store, config: name => this.fleetConfig?.instances[name],
|
|
950
|
+
freshNotice: async (name, provider) => {
|
|
951
|
+
const message = t(provider === "codex" ? "native_pane.codex_fresh_started" : "native_pane.fresh_started");
|
|
952
|
+
this.eventLog?.insert(name, "native_pane_fresh_started", {});
|
|
953
|
+
this.logger.info({ name }, message);
|
|
954
|
+
await this.notifyInstanceTopic(name, message);
|
|
955
|
+
},
|
|
956
|
+
admit: (name, config) => {
|
|
957
|
+
const now = this.fleetConfig?.instances[name];
|
|
958
|
+
if (this.shuttingDown || !isNativePaneInstance(now) || now?.native_agent_id !== config.native_agent_id
|
|
959
|
+
|| now?.machine_id !== config.machine_id || now?.working_directory !== config.working_directory || now?.backend !== config.backend
|
|
960
|
+
|| now?.model !== config.model || now?.native_sandbox !== config.native_sandbox || now?.systemPrompt !== config.systemPrompt || now?.workflow !== config.workflow)
|
|
961
|
+
throw Error("Native pane configuration or lifetime changed");
|
|
962
|
+
if (config.machine_id ? this.remotePaneDrain || !remoteRuntimeEnabled() : this.localPaneDrain || !localWindowsCliEnabled())
|
|
963
|
+
throw Error("Native runtime is switched off");
|
|
964
|
+
},
|
|
965
|
+
endpoint: async (agent, cleanup = false) => {
|
|
966
|
+
if (!agent.machineId) {
|
|
967
|
+
if (!cleanup)
|
|
968
|
+
return this.localNativePaneEndpoint();
|
|
969
|
+
if (!this.localPaneRelay)
|
|
970
|
+
throw Error("Local cleanup transport is unavailable; pane remains reserved");
|
|
971
|
+
return this.localPaneRelay;
|
|
972
|
+
}
|
|
973
|
+
const hub = cleanup ? this.runtimeHub : this.runtimeCoordinator(), machineId = agent.machineId;
|
|
974
|
+
if (!hub)
|
|
975
|
+
throw Error("Remote cleanup transport is unavailable; pane remains reserved");
|
|
976
|
+
const draining = () => !!this.remotePaneDrain;
|
|
977
|
+
return { machineId, online: () => !this.shuttingDown && (draining() || remoteRuntimeEnabled()) && this.runtimeHub === hub && hub.machine(machineId)?.status === "online",
|
|
978
|
+
call: request => {
|
|
979
|
+
if (this.runtimeHub !== hub || !(draining() || remoteRuntimeEnabled()) || draining() && request.action !== "snapshot" && request.action !== "close")
|
|
980
|
+
return Promise.reject(Error("Remote pane transport only permits owned cleanup"));
|
|
981
|
+
return hub.callPane(machineId, request, maxAgentsPerMachine());
|
|
982
|
+
}, subscribe: listener => hub.subscribe(listener),
|
|
983
|
+
epoch: () => hub.paneTransportEpoch(machineId), version: provider => hub.machine(machineId)?.inventory?.clis.find(row => row.id === provider && row.runnable)?.version ?? null };
|
|
984
|
+
},
|
|
985
|
+
instructions: (name, config) => buildFleetInstructions({ instanceName: name, workingDirectory: config.working_directory,
|
|
986
|
+
displayName: config.display_name, description: config.description, customPrompt: config.systemPrompt,
|
|
987
|
+
workflow: config.workflow, taskBoard: !dashboardRefusalIn(this.fleetConfig, name, "task"),
|
|
988
|
+
runtimeIdentity: { kind: "fleet-topic", backend: config.backend ?? "codex", model: config.model || "default" } }),
|
|
989
|
+
menu: name => TOOLS.filter(tool => allowed(name, tool.name)).map(tool => ({ ...tool })),
|
|
990
|
+
allowed: (name, tool, args) => allowed(name, tool) && !nativePaneToolRefusal(tool, args), published: (name, daemon) => this.daemons.get(name) === daemon,
|
|
991
|
+
invoke: async (daemon, input, current, name) => {
|
|
992
|
+
if (input.tool === "list_models") {
|
|
993
|
+
const { ListModelsArgs } = await import("./outbound-schemas.js");
|
|
994
|
+
const parsed = ListModelsArgs.safeParse(input.args);
|
|
995
|
+
if (!parsed.success || !current())
|
|
996
|
+
return { result: null, error: "Invalid native model request or expired owner" };
|
|
997
|
+
const target = parsed.data.instance_name ?? name;
|
|
998
|
+
if (!isWindowsInstance(this.fleetConfig?.instances[target]))
|
|
999
|
+
return { result: null, error: "Native panes can only read Windows runtime model inventories" };
|
|
1000
|
+
return { result: await this.listModelCatalog({ instanceName: target }) };
|
|
1001
|
+
}
|
|
1002
|
+
const ops = { list_instances: "list", describe_instance: "describe", task: "task", send_to_instance: "send",
|
|
1003
|
+
delegate_task: "delegate", request_information: "ask", report_result: "report", delivery_status: "delivery-status" };
|
|
1004
|
+
const { nativeAgentAdmissionContext } = await import("./native-agent-admission.js");
|
|
1005
|
+
const { dispatchAgentOperation } = await import("./agent-endpoint.js");
|
|
1006
|
+
return daemon.handlePaneToolCall(input, { current, allowed: tool => allowed(name, tool),
|
|
1007
|
+
dispatch: (tool, args) => ops[tool] ? dispatchAgentOperation(nativeAgentAdmissionContext(this, {
|
|
1008
|
+
instance: name, current: () => current() && allowed(name, tool), taskAllowed: () => current() && allowed(name, "task"),
|
|
1009
|
+
}), name, ops[tool], args, input.operationId) : undefined });
|
|
1010
|
+
},
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
async nativePaneFor(name, config, assertCurrent) {
|
|
1014
|
+
return (await this.nativePanes()).prepare(name, config, assertCurrent);
|
|
1015
|
+
}
|
|
1016
|
+
async recoverNativePane(name, launchId, authorized) {
|
|
1017
|
+
const config = this.fleetConfig?.instances[name];
|
|
1018
|
+
if (!isNativePaneInstance(config))
|
|
1019
|
+
throw Error("Persistent Windows pane is not configured");
|
|
1020
|
+
const generation = this.explicitStopGeneration.get(name) ?? 0;
|
|
1021
|
+
await this.lifecycle.runTransition(name, async (transition) => {
|
|
1022
|
+
const current = () => { if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== config || (this.explicitStopGeneration.get(name) ?? 0) !== generation)
|
|
1023
|
+
throw Error(t("native_pane.authority_expired")); };
|
|
1024
|
+
current();
|
|
1025
|
+
if (this.daemons.get(name)?.nativePaneCurrent())
|
|
1026
|
+
throw Error(t("native_pane.still_running"));
|
|
1027
|
+
this.cancelStartupRetry(name);
|
|
1028
|
+
this.lifecycle.cancelNativeRecovery(name);
|
|
1029
|
+
await (await this.nativePanes()).recover(name, config, launchId, true, current);
|
|
1030
|
+
current();
|
|
1031
|
+
// The host supplied a positive exit before ordinary lifecycle cleanup. No signal or input is required.
|
|
1032
|
+
await this.lifecycle.stop(name, transition);
|
|
1033
|
+
current();
|
|
1034
|
+
this.instanceProcessStatus.set(name, "stopped");
|
|
1035
|
+
this.eventLog?.insert(name, "native_pane_recovered", { launchId, confirmedStopped: true });
|
|
1036
|
+
this.logger.info({ name, launchId }, "Windows saved pane recovered by an administrator; waiting for Start");
|
|
1037
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
holdNativePaneRecovery(name, error) {
|
|
1041
|
+
if (!(error instanceof NativePaneRecoveryError))
|
|
1042
|
+
return false;
|
|
1043
|
+
this.instanceProcessStatus.set(name, "stopped");
|
|
1044
|
+
this.logger.warn({ name, recovery: error.recovery }, error.message);
|
|
1045
|
+
this.eventLog?.insert(name, "native_pane_recovery_needed", { ...error.recovery });
|
|
1046
|
+
this.needsYou?.poke();
|
|
1047
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1048
|
+
return true;
|
|
1049
|
+
}
|
|
1050
|
+
async startFreshNativePane(name, launchId, sessionId, authorized) {
|
|
1051
|
+
const config = this.fleetConfig?.instances[name];
|
|
1052
|
+
if (!isNativePaneInstance(config))
|
|
1053
|
+
throw Error("Persistent Windows pane is not configured");
|
|
1054
|
+
const generation = this.explicitStopGeneration.get(name) ?? 0;
|
|
1055
|
+
await this.lifecycle.runTransition(name, async (transition) => {
|
|
1056
|
+
const current = () => { if (!authorized() || this.shuttingDown || this.fleetConfig?.instances[name] !== config || (this.explicitStopGeneration.get(name) ?? 0) !== generation)
|
|
1057
|
+
throw Error(t("native_pane.authority_expired")); };
|
|
1058
|
+
current();
|
|
1059
|
+
const held = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id).paneRecovery;
|
|
1060
|
+
if (held?.kind !== "session-unconfirmed" || held.launchId !== launchId || (held.sessionId ?? null) !== sessionId)
|
|
1061
|
+
throw Error(t("native_pane.changed"));
|
|
1062
|
+
if (this.daemons.get(name)?.nativePaneCurrent())
|
|
1063
|
+
throw Error(t("native_pane.still_running"));
|
|
1064
|
+
this.cancelStartupRetry(name);
|
|
1065
|
+
this.lifecycle.cancelNativeRecovery(name);
|
|
1066
|
+
await this.lifecycle.stop(name, transition);
|
|
1067
|
+
current();
|
|
1068
|
+
try {
|
|
1069
|
+
await (await this.nativePanes()).startFresh(name, config, launchId, sessionId, current);
|
|
1070
|
+
current();
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
current();
|
|
1074
|
+
this.needsYou?.poke();
|
|
1075
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1076
|
+
throw error;
|
|
1077
|
+
}
|
|
1078
|
+
this.eventLog?.insert(name, "native_pane_new_conversation", { launchId, sessionId });
|
|
1079
|
+
await this.startInstance(name, config, true, "fleet-topic", true, transition);
|
|
1080
|
+
current();
|
|
1081
|
+
this.needsYou?.poke();
|
|
1082
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
nativePanePinnedSession(name) {
|
|
1086
|
+
const config = this.fleetConfig?.instances[name];
|
|
1087
|
+
if (!isNativePaneInstance(config) || !config.native_agent_id)
|
|
1088
|
+
return null;
|
|
1089
|
+
const agent = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id);
|
|
1090
|
+
return agent.fleet_instance === name && agent.machineId === config.machine_id ? agent.threadId ?? null : null;
|
|
1091
|
+
}
|
|
1092
|
+
onNativePaneRecoverySettled(name) {
|
|
1093
|
+
if (this.daemons.get(name)?.nativePaneCurrent())
|
|
1094
|
+
this.instanceProcessStatus.delete(name);
|
|
1095
|
+
this.onAttentionChanged(name);
|
|
1096
|
+
this.scheduleDeliveryOutboxPump();
|
|
1097
|
+
}
|
|
1098
|
+
setNativePanePaused(name, paused) {
|
|
1099
|
+
const id = this.fleetConfig?.instances[name]?.native_agent_id;
|
|
1100
|
+
if (!id)
|
|
1101
|
+
throw Error("Native profile is missing");
|
|
1102
|
+
windowsAgentsFor(this, this.runtimeWindowsExecutor()).setPaused(id, paused);
|
|
1103
|
+
}
|
|
1104
|
+
async closeRetainedNativePane(name) {
|
|
1105
|
+
const config = this.fleetConfig?.instances[name];
|
|
1106
|
+
if (!config)
|
|
1107
|
+
throw Error("Native instance configuration is missing");
|
|
1108
|
+
await (await this.nativePanes()).closeRetained(name, config, () => { if (this.shuttingDown)
|
|
1109
|
+
throw Error("Fleet is stopping"); });
|
|
1110
|
+
}
|
|
1111
|
+
async reconcileNativePaneBeforeStart(name, daemon) {
|
|
1112
|
+
const outbox = this.deliveryOutbox;
|
|
1113
|
+
if (!outbox)
|
|
1114
|
+
return true;
|
|
1115
|
+
const epoch = this.lifecycle.epochOf(name);
|
|
1116
|
+
return daemon.reconcileNativeDeliveries(outbox, () => !this.shuttingDown && this.lifecycle.epochOf(name) === epoch);
|
|
1117
|
+
}
|
|
1118
|
+
/** M-2255: the Windows runtimes' switches the fleet's own state reflects (null until the start-up has applied them). */
|
|
1119
|
+
runtimeEffects = null;
|
|
1120
|
+
runtimeEffectsScheduled = false;
|
|
1121
|
+
localPaneDrain = null;
|
|
1122
|
+
remotePaneDrain = null;
|
|
1123
|
+
/** Revoke admissions synchronously; keep the captured transport only long enough for exact owned closes. */
|
|
1124
|
+
drainNativeRuntime(remote) {
|
|
1125
|
+
const existing = remote ? this.remotePaneDrain : this.localPaneDrain;
|
|
1126
|
+
if (existing)
|
|
1127
|
+
return existing;
|
|
1128
|
+
const hub = this.runtimeHub, relay = this.localPaneRelay;
|
|
1129
|
+
if (!remote)
|
|
1130
|
+
relay?.beginCleanup();
|
|
1131
|
+
const targets = Object.entries(this.fleetConfig?.instances ?? {}).filter(([, config]) => isWindowsInstance(config) && !!config.machine_id === remote);
|
|
1132
|
+
for (const [name, config] of targets) {
|
|
1133
|
+
this.nativeMcpStore?.closeInstance(name);
|
|
1134
|
+
if (isNativePaneInstance(config)) {
|
|
1135
|
+
this.lifecycle.cancelNativeRecovery(name);
|
|
1136
|
+
this.lifecycle.invalidate(name);
|
|
1137
|
+
this.daemons.get(name)?.fenceDeliveryWritesForStop();
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
const run = Promise.resolve().then(async () => {
|
|
1141
|
+
const results = await Promise.allSettled(targets.filter(([, config]) => isNativePaneInstance(config)).map(([name]) => this.stopInstance(name)));
|
|
1142
|
+
results.forEach((result, index) => { if (result.status === "rejected")
|
|
1143
|
+
this.logger.warn({ err: result.reason, remote, index }, "Windows pane cleanup unconfirmed; its reservation is retained"); });
|
|
1144
|
+
try {
|
|
1145
|
+
if (remote)
|
|
1146
|
+
await hub?.shutdownPaneHosts();
|
|
1147
|
+
else
|
|
1148
|
+
await relay?.shutdownHost();
|
|
1149
|
+
}
|
|
1150
|
+
catch (error) {
|
|
1151
|
+
this.logger.warn({ err: error, remote }, "Windows pane host shutdown unconfirmed; owned state is retained");
|
|
1152
|
+
}
|
|
1153
|
+
}).finally(() => {
|
|
1154
|
+
if (remote) {
|
|
1155
|
+
hub?.close();
|
|
1156
|
+
if (this.runtimeHub === hub)
|
|
1157
|
+
this.runtimeHub = null;
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
relay?.stop();
|
|
1161
|
+
if (this.localPaneRelay === relay)
|
|
1162
|
+
this.localPaneRelay = null;
|
|
1163
|
+
closeWindowsBridge(this);
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
if (remote)
|
|
1167
|
+
this.remotePaneDrain = run;
|
|
1168
|
+
else
|
|
1169
|
+
this.localPaneDrain = run;
|
|
1170
|
+
void run.finally(() => {
|
|
1171
|
+
if (remote) {
|
|
1172
|
+
if (this.remotePaneDrain === run)
|
|
1173
|
+
this.remotePaneDrain = null;
|
|
1174
|
+
}
|
|
1175
|
+
else if (this.localPaneDrain === run)
|
|
1176
|
+
this.localPaneDrain = null;
|
|
1177
|
+
if (!this.shuttingDown && (remote ? remoteRuntimeEnabled() : localWindowsCliEnabled())) {
|
|
1178
|
+
this.initializeNativeInstances();
|
|
1179
|
+
this.initializeNativeInstances(true);
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1183
|
+
}
|
|
1184
|
+
catch { }
|
|
1185
|
+
}).catch(error => this.logger.warn({ err: error }, "Windows runtime cleanup failed; retained ownership requires reconciliation"));
|
|
1186
|
+
return run;
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* M-2255: Settings → Runtime's switches (fleet.yaml `runtimes`, or their environment override) in force — set each time
|
|
1190
|
+
* the configuration in force changes (a load, a save, a reload's rollback), next to the web authority's. Every gate reads
|
|
1191
|
+
* them at once; what the fleet itself holds (paired computers' connections, this PC's Windows process, agents' tool
|
|
1192
|
+
* sessions) follows just after, outside the save that changed them (applyRuntimeSwitches): no restart is needed.
|
|
1193
|
+
*/
|
|
1194
|
+
observeRuntimeSwitches() {
|
|
1195
|
+
setConfiguredRuntimeSwitches(this.fleetConfig?.runtimes);
|
|
1196
|
+
if (!this.runtimeEffects || this.runtimeEffectsScheduled)
|
|
1197
|
+
return;
|
|
1198
|
+
this.runtimeEffectsScheduled = true;
|
|
1199
|
+
queueMicrotask(() => { this.runtimeEffectsScheduled = false; this.applyRuntimeSwitches(); });
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Turned off: what it allowed ends now — the paired computers' connections (their agents show as offline) or this PC's
|
|
1203
|
+
* Windows process (a turn in progress is cancelled, as when the fleet stops), and those agents' tool sessions.
|
|
1204
|
+
* Turned on: the Windows agents are registered and their chat history read, as at start-up.
|
|
1205
|
+
*/
|
|
1206
|
+
applyRuntimeSwitches() {
|
|
1207
|
+
const was = this.runtimeEffects;
|
|
1208
|
+
if (!was || this.shuttingDown)
|
|
1209
|
+
return;
|
|
1210
|
+
const now = { windows_cli: localWindowsCliEnabled(), remote_runtime: remoteRuntimeEnabled() };
|
|
1211
|
+
if (was.windows_cli === now.windows_cli && was.remote_runtime === now.remote_runtime)
|
|
1212
|
+
return;
|
|
1213
|
+
this.runtimeEffects = now;
|
|
1214
|
+
try {
|
|
1215
|
+
if (was.remote_runtime && !now.remote_runtime) {
|
|
1216
|
+
void this.drainNativeRuntime(true);
|
|
1217
|
+
}
|
|
1218
|
+
if (was.windows_cli && !now.windows_cli) {
|
|
1219
|
+
void this.drainNativeRuntime(false);
|
|
1220
|
+
}
|
|
1221
|
+
if (!was.windows_cli && !was.remote_runtime && !this.localPaneDrain && !this.remotePaneDrain) {
|
|
1222
|
+
this.initializeNativeInstances();
|
|
1223
|
+
this.initializeNativeInstances(true);
|
|
1224
|
+
}
|
|
1225
|
+
this.logger.info({ windows_cli: now.windows_cli, remote_runtime: now.remote_runtime }, "Windows runtime switches applied");
|
|
1226
|
+
}
|
|
1227
|
+
catch (error) {
|
|
1228
|
+
this.logger.warn({ err: error }, "Windows runtime switches could not be applied in full");
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1232
|
+
}
|
|
1233
|
+
catch { /* the next status frame carries it */ }
|
|
1234
|
+
}
|
|
1235
|
+
/** An unavailable opt-in runtime must not prevent the Linux fleet from starting or restoring its own history. */
|
|
1236
|
+
initializeNativeInstances(history = false) {
|
|
1237
|
+
if (!windowsCliEnabled())
|
|
1238
|
+
return;
|
|
1239
|
+
try {
|
|
1240
|
+
const runtime = this.nativeInstanceRuntime();
|
|
1241
|
+
if (history)
|
|
1242
|
+
runtime.importHistory();
|
|
1243
|
+
else
|
|
1244
|
+
runtime.register();
|
|
1245
|
+
}
|
|
1246
|
+
catch (error) {
|
|
1247
|
+
this.logger.warn({ err: error }, "Windows runtime state could not be loaded; affected native instances remain unavailable");
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/** Native CLIs are headless: the manager owns the durable delivery generation instead of a tmux daemon. */
|
|
1251
|
+
deliveryBootId(name) {
|
|
1252
|
+
const config = this.fleetConfig?.instances[name];
|
|
1253
|
+
if (isNativeExecInstance(config)) {
|
|
1254
|
+
if (!this.nativeInstanceRuntime().available(name))
|
|
1255
|
+
return null;
|
|
1256
|
+
const machine = config.machine_id ? this.runtimeCoordinator().machine(config.machine_id) : null;
|
|
1257
|
+
return machine ? `runtime:${machine.id}:${machine.bootId ?? machine.generation}` : this.managerBootId;
|
|
1258
|
+
}
|
|
1259
|
+
return this.daemons.get(name)?.bootId ?? null;
|
|
1260
|
+
}
|
|
731
1261
|
rawFleetConfig = {};
|
|
732
1262
|
rawFleetDocument = null;
|
|
733
1263
|
/** #2208: the paths a saveFleetConfig() wrote, for its check that nothing else changed. */
|
|
@@ -1495,6 +2025,8 @@ export class FleetManager {
|
|
|
1495
2025
|
* authorizing revoked links and cookies until the fleet restarted.
|
|
1496
2026
|
*/
|
|
1497
2027
|
settingsGeneration = 0;
|
|
2028
|
+
/** #2942: the settings baseline the agent-requested Windows changes are checked against (set with the gate). */
|
|
2029
|
+
settingsBaselineReader = null;
|
|
1498
2030
|
settingsRuntimeRevision = 0;
|
|
1499
2031
|
settingsConfirmation = null;
|
|
1500
2032
|
settingsControl = null;
|
|
@@ -1551,7 +2083,7 @@ export class FleetManager {
|
|
|
1551
2083
|
return this.settingsConfirmation;
|
|
1552
2084
|
const generation = this.settingsGeneration;
|
|
1553
2085
|
const current = () => !this.shuttingDown && this.settingsGeneration === generation;
|
|
1554
|
-
const baselines = new SettingsBaselines({ dataDir: this.dataDir, configPath: () => this.configPath,
|
|
2086
|
+
const baselines = this.settingsBaselineReader = new SettingsBaselines({ dataDir: this.dataDir, configPath: () => this.configPath,
|
|
1555
2087
|
config: () => this.fleetConfig, current: () => [this.settingsGeneration, this.settingsRuntimeRevision, this.shuttingDown],
|
|
1556
2088
|
proof: (kind, target, id, binding, key) => {
|
|
1557
2089
|
const map = kind === "provider" ? this.providerSecretChallenges : kind === "binding" ? this.connectionBindingChallenges : this.connectionSecretChallenges;
|
|
@@ -1573,6 +2105,17 @@ export class FleetManager {
|
|
|
1573
2105
|
const store = new SettingsConfirmationStore({
|
|
1574
2106
|
audit: (event, fields) => this.logger.info({ event: `settings_confirmation.${event}`, ...fields }, "Settings confirmation audit"),
|
|
1575
2107
|
notify: view => view.state === "pending" ? this.promptSettingsChange(store, view) : Promise.resolve(),
|
|
2108
|
+
// alpha.2 (M-2300): an agent's request came, was decided or ran out — the open pages read the list again (a ping
|
|
2109
|
+
// with nothing in it: only a web-only fleet's verified owner may read it, GET /ui/agent-proposals).
|
|
2110
|
+
changed: view => {
|
|
2111
|
+
if (view.source !== "agent_request")
|
|
2112
|
+
return;
|
|
2113
|
+
this.agentProposalsRev++;
|
|
2114
|
+
broadcastSseEvent(this.sseClients, "agent_proposals", {}, err => this.logger.debug({ err }, "agent_proposals event failed"));
|
|
2115
|
+
// …and a status frame with the new revision: a tab from before the upgrade (its stream knows no `agent_proposals`)
|
|
2116
|
+
// applies status frames, and a polling page reads the same frame at its next /ui/poll.
|
|
2117
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
2118
|
+
},
|
|
1576
2119
|
});
|
|
1577
2120
|
const gate = new SettingsHttpConfirmation(store, {
|
|
1578
2121
|
principal: req => {
|
|
@@ -1664,6 +2207,76 @@ export class FleetManager {
|
|
|
1664
2207
|
done: view => this.settingsWebConfirmedNotice(view, affected, name, chat.platform),
|
|
1665
2208
|
};
|
|
1666
2209
|
}
|
|
2210
|
+
/**
|
|
2211
|
+
* alpha.2 (M-2300; #2973's agent requests, junsion's §6): on a fleet with no chat connection there is no General to
|
|
2212
|
+
* post an agent's Windows request in — it waited for `agend settings confirm` on the host. Its owner (decision
|
|
2213
|
+
* 28c2d3a3: the `agend web --code` session on this computer, web-verified) now sees and decides it from the web's
|
|
2214
|
+
* Needs you. The authority is asked from the request each time, and again at execution by the store (`current`):
|
|
2215
|
+
* still that owner now (this computer, not revoked, signed out or run out), still a fleet with no chat connection,
|
|
2216
|
+
* never over the public link. Anyone else — a guest, a LAN or public-link session, a chat-connected fleet's admin —
|
|
2217
|
+
* gets nothing (a chat fleet keeps General's card as its way).
|
|
2218
|
+
*/
|
|
2219
|
+
/**
|
|
2220
|
+
* #2997 review (Prism P2): how many times an agent's request came or was decided — in a web-only fleet's status frame,
|
|
2221
|
+
* so a page that never hears the stream's ping (one polling /ui/poll, one reconnecting, a tab from before the upgrade)
|
|
2222
|
+
* reads the list again when it moves. A count, nothing more.
|
|
2223
|
+
*/
|
|
2224
|
+
agentProposalsRev = 0;
|
|
2225
|
+
agentProposalOwner(req) {
|
|
2226
|
+
if (!this.isWebOnlyFleet() || gatewayRequestContext(req))
|
|
2227
|
+
return null;
|
|
2228
|
+
const auth = authorizeSession(req, this.webToken, this.webSessions, { touch: false });
|
|
2229
|
+
if (auth.kind !== "ok")
|
|
2230
|
+
return null;
|
|
2231
|
+
// Each check twice over: here, and in the authority itself (it refuses the public link and a chat fleet's session).
|
|
2232
|
+
// #2997 review (Prism P2): asked of the LIVE session each time — the request's own cookie authenticated again, the
|
|
2233
|
+
// same handle — never a record captured at the click: a sign-out, an expiry or a revocation while the request is
|
|
2234
|
+
// being applied fences it.
|
|
2235
|
+
const handle = auth.session.handle, peer = webPeerOf(req), gateway = !!gatewayRequestContext(req);
|
|
2236
|
+
const owner = () => {
|
|
2237
|
+
if (!this.isWebOnlyFleet())
|
|
2238
|
+
return false;
|
|
2239
|
+
const live = authorizeSession(req, this.webToken, this.webSessions, { touch: false });
|
|
2240
|
+
if (live.kind !== "ok" || live.session.handle !== handle)
|
|
2241
|
+
return false;
|
|
2242
|
+
const now = this.webAuthority({ session: live.session, gateway, peer });
|
|
2243
|
+
const a = now.authority.kind === "web-verified" && now.authority.session?.handle === handle ? now.authority.actor : null;
|
|
2244
|
+
return !!a && a.adapter_id === HOST_OPERATOR.adapterId && a.user_id === HOST_OPERATOR.userId;
|
|
2245
|
+
};
|
|
2246
|
+
return owner() ? { label: t("settings_confirm.host_operator_label"), current: owner } : null;
|
|
2247
|
+
}
|
|
2248
|
+
/** alpha.2: the agents' requests waiting for this web-only fleet's owner, or null when the reader is not that owner. */
|
|
2249
|
+
webAgentProposals(req) {
|
|
2250
|
+
if (!this.agentProposalOwner(req))
|
|
2251
|
+
return null;
|
|
2252
|
+
return this.settingsGate().store.listBySource("agent_request");
|
|
2253
|
+
}
|
|
2254
|
+
/**
|
|
2255
|
+
* alpha.2: the owner's Approve or Reject of an agent's request. The store claims it before its first await (a second
|
|
2256
|
+
* click, from here or the host, finds it no longer pending) and asks the owner, the request (its fleet generation and
|
|
2257
|
+
* Windows switch) and the target machine again before and while it runs the same creation code the web uses. The
|
|
2258
|
+
* answer is always the request as it now stands: applied (with the new agent's name), rejected, expired, stale or
|
|
2259
|
+
* failed (with its reason), or already decided.
|
|
2260
|
+
*/
|
|
2261
|
+
async webDecideAgentProposal(req, id, decision) {
|
|
2262
|
+
const owner = this.agentProposalOwner(req);
|
|
2263
|
+
if (!owner)
|
|
2264
|
+
return { status: 403, body: { error: "owner_required", message: t("settings_confirm.web_owner_required") } };
|
|
2265
|
+
const store = this.settingsGate().store, before = store.inspect(id);
|
|
2266
|
+
if (!before || before.source !== "agent_request")
|
|
2267
|
+
return { status: 404, body: { error: "not_found", message: t("settings_confirm.no_longer_pending") } };
|
|
2268
|
+
try {
|
|
2269
|
+
const view = await store.decide(id, decision, { ...owner, via: "web" });
|
|
2270
|
+
this.logger.info({ id, decision, state: view.state }, "Agent request decided from the web by this computer's admin");
|
|
2271
|
+
return { status: 200, body: { view, agent: agentNameOf(view) } };
|
|
2272
|
+
}
|
|
2273
|
+
catch (err) {
|
|
2274
|
+
const code = err instanceof SettingsConfirmationError ? err.code : "decide_failed";
|
|
2275
|
+
const status = err instanceof SettingsConfirmationError ? err.status : 500;
|
|
2276
|
+
// Already decided (a double click, the host's command), run out or withdrawn: say what it is now.
|
|
2277
|
+
return { status, body: { error: code, message: t(code === "admin_required" ? "settings_confirm.web_owner_required" : "settings_confirm.no_longer_pending"), view: store.inspect(id) } };
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
1667
2280
|
/** #2170: the Generals a chat card could be posted in (promptSettingsChange's rule): connected, on the platform, unaffected. */
|
|
1668
2281
|
settingsNoticeGenerals(affected, platform) {
|
|
1669
2282
|
const excluded = new Set(affected), found = [];
|
|
@@ -1736,6 +2349,119 @@ export class FleetManager {
|
|
|
1736
2349
|
return { platform, current, admin: userId => current() && !!userId
|
|
1737
2350
|
&& identities.every(owner => this.isFleetAdmin(userId, owner.id)) };
|
|
1738
2351
|
}
|
|
2352
|
+
/**
|
|
2353
|
+
* #2942: a Windows create or settings change an AGENT asked for. Nothing is created or written here: it becomes a
|
|
2354
|
+
* pending settings change whose General card a fleet admin must confirm (promptSettingsChange — the same admin rule
|
|
2355
|
+
* as every card). Its apply runs the same creation/patch code the web uses, inside the store's SettingsExecution, with
|
|
2356
|
+
* the confirming admin, the Windows switch and the target machine asked again at that moment.
|
|
2357
|
+
*/
|
|
2358
|
+
async proposeAgentWindowsChange(input) {
|
|
2359
|
+
const gate = this.settingsGate(), baselines = this.settingsBaselineReader;
|
|
2360
|
+
if (!baselines || !this.fleetConfig)
|
|
2361
|
+
throw new Error("Fleet configuration is unavailable");
|
|
2362
|
+
const generation = this.settingsGeneration, machineId = input.machineId;
|
|
2363
|
+
const switchOn = () => machineId ? remoteRuntimeEnabled() : localWindowsCliEnabled();
|
|
2364
|
+
const online = () => !machineId || (remoteRuntimeEnabled() && this.runtimeCoordinator().machine(machineId)?.status === "online");
|
|
2365
|
+
if (!switchOn())
|
|
2366
|
+
throw new Error(machineId ? "Remote runtime is not enabled" : LOCAL_WINDOWS_CLI_OFF);
|
|
2367
|
+
const baseline = await baselines.read(), snapshot = settingsFingerprint(baselines.snapshot());
|
|
2368
|
+
const { view } = gate.store.propose({ session: `agent:${input.requester}`, key: `agent-windows:${input.key}`,
|
|
2369
|
+
requestFingerprint: input.fingerprint, fingerprint: input.fingerprint, section: "channel", requestedBy: input.requester,
|
|
2370
|
+
source: "agent_request", summary: input.summary, bytes: input.bytes, authority: input.authority, affectedConnections: [],
|
|
2371
|
+
current: () => !this.shuttingDown && this.settingsGeneration === generation && switchOn(),
|
|
2372
|
+
snapshot: () => baselines.snapshot(),
|
|
2373
|
+
unchanged: async () => (await baselines.read()).fingerprint === baseline.fingerprint && settingsFingerprint(baselines.snapshot()) === snapshot,
|
|
2374
|
+
// The store's own fence (this confirming admin, the session, the state) plus the switch and machine now.
|
|
2375
|
+
apply: async (actorCurrent, execution) => {
|
|
2376
|
+
execution.assert();
|
|
2377
|
+
// alpha.2 polish: a paired machine whose CLI inventory has not arrived yet (just paired) is asked for its own
|
|
2378
|
+
// report first (it runs on that PC), so the creation validates against what is installed there — never a refusal
|
|
2379
|
+
// that only a manual Refresh would undo. Its answer or failure changes nothing else; the fence is asked again.
|
|
2380
|
+
if (machineId && online() && !inventoryProbed(this.runtimeCoordinator().machine(machineId)?.inventory)) {
|
|
2381
|
+
// `force`: the runner's cached answer on a fresh profile is the placeholder itself (#3005 rig recheck).
|
|
2382
|
+
try {
|
|
2383
|
+
await this.runtimeCoordinator().call(machineId, "probe", { force: true });
|
|
2384
|
+
}
|
|
2385
|
+
catch { /* validation says what is missing */ }
|
|
2386
|
+
execution.assert();
|
|
2387
|
+
}
|
|
2388
|
+
return input.apply(() => actorCurrent() && online(), execution);
|
|
2389
|
+
} });
|
|
2390
|
+
return view;
|
|
2391
|
+
}
|
|
2392
|
+
/** #2942: create_instance with runtime "windows" from an agent: validated like the web, then held for General. */
|
|
2393
|
+
async requestWindowsCreate(requester, rawArgs) {
|
|
2394
|
+
const cfg = this.fleetConfig;
|
|
2395
|
+
if (!cfg)
|
|
2396
|
+
throw new Error("Fleet configuration is unavailable");
|
|
2397
|
+
let parsed;
|
|
2398
|
+
try {
|
|
2399
|
+
parsed = parseNativeInstanceCreate(rawArgs);
|
|
2400
|
+
}
|
|
2401
|
+
catch {
|
|
2402
|
+
throw new Error("Invalid Windows agent request: check the directory, sandbox, backend and model");
|
|
2403
|
+
}
|
|
2404
|
+
// The web's own projection: the same validation, topic target and diff the admin's dashboard confirmation shows.
|
|
2405
|
+
let diff;
|
|
2406
|
+
try {
|
|
2407
|
+
diff = prepareSettingsEffect("POST", "/ui/instances", rawArgs, { config: cfg, classic: {} }).diff;
|
|
2408
|
+
}
|
|
2409
|
+
catch {
|
|
2410
|
+
throw new Error("Invalid Windows agent request: its topic connection is not one this fleet can use");
|
|
2411
|
+
}
|
|
2412
|
+
if (!diff)
|
|
2413
|
+
throw new Error("Invalid Windows agent request");
|
|
2414
|
+
return this.proposeAgentWindowsChange({ requester, key: diff.fingerprint, fingerprint: diff.fingerprint, summary: diff.summary,
|
|
2415
|
+
machineId: parsed.input.machineId, bytes: JSON.stringify(rawArgs).length,
|
|
2416
|
+
// The General card is the confirmation for an agent's request, whatever the requested connection (a topic or none).
|
|
2417
|
+
authority: { connections: diff.authority?.connections ?? [], primaryGeneral: true, unknown: false },
|
|
2418
|
+
apply: (allowed, execution) => createNativeWindowsInstance(this, parsed, allowed, execution) });
|
|
2419
|
+
}
|
|
2420
|
+
/** #2942: update_instance_config on a Windows agent: only the fields the web editor allows, held for General. */
|
|
2421
|
+
async requestWindowsConfig(requester, name, patch) {
|
|
2422
|
+
const inst = this.fleetConfig?.instances[name];
|
|
2423
|
+
if (!isWindowsInstance(inst))
|
|
2424
|
+
throw new Error("Windows agent not found");
|
|
2425
|
+
let edit;
|
|
2426
|
+
try {
|
|
2427
|
+
edit = NativeInstanceConfigEdit.parse(patch);
|
|
2428
|
+
}
|
|
2429
|
+
catch {
|
|
2430
|
+
throw new Error("Invalid Windows agent settings: display_name, description and model only");
|
|
2431
|
+
}
|
|
2432
|
+
const before = { display_name: inst.display_name ?? "", description: inst.description ?? "", model: inst.model ?? "" };
|
|
2433
|
+
const changed = Object.keys(edit).filter(key => edit[key] !== before[key]);
|
|
2434
|
+
if (!changed.length)
|
|
2435
|
+
throw new Error("Nothing to change: these settings already have these values");
|
|
2436
|
+
const summary = changed.map(key => `${name} ${key}: ${settingsDisplay(before[key])} → ${settingsDisplay(edit[key])}`);
|
|
2437
|
+
const machineId = typeof inst.machine_id === "string" ? inst.machine_id : undefined;
|
|
2438
|
+
// #2973 review (Prism P1): a persistent pane is bound to a chat connection; changing it (a model change restarts it)
|
|
2439
|
+
// needs that connection's admins as well as the primary General's, exactly as its creation did. An instance whose
|
|
2440
|
+
// binding names no single configured connection is refused (fail closed), never confirmed by the General alone.
|
|
2441
|
+
const connections = [];
|
|
2442
|
+
if (isNativePaneInstance(inst) || inst.channel_id !== undefined) {
|
|
2443
|
+
const bound = inst.channel_id, channels = this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : []);
|
|
2444
|
+
if (typeof bound !== "string" || channels.filter(ch => (ch.id ?? ch.type) === bound).length !== 1)
|
|
2445
|
+
throw new Error("This Windows agent's chat connection cannot be resolved; change its settings in the web dashboard");
|
|
2446
|
+
connections.push(bound);
|
|
2447
|
+
}
|
|
2448
|
+
return this.proposeAgentWindowsChange({ requester, key: `${name}:${settingsFingerprint(edit)}`, fingerprint: settingsFingerprint([name, edit]),
|
|
2449
|
+
summary, machineId, bytes: JSON.stringify(edit).length, authority: { connections, primaryGeneral: true, unknown: false },
|
|
2450
|
+
// #2973 review (Prism P2): the update may wait for its lifecycle turn; if the agent is rebound meanwhile (B → C), the
|
|
2451
|
+
// approval for B must not stop or change it on C. Pin the approved target and require the execution's snapshot to be
|
|
2452
|
+
// unchanged before every pre-commit effect; the post-commit restart keeps its own (refreshed) checks.
|
|
2453
|
+
apply: (allowed, execution) => {
|
|
2454
|
+
const pinned = { channel: inst.channel_id, machine: inst.machine_id, adapter: inst.native_adapter, runtime: inst.runtime };
|
|
2455
|
+
const sameTarget = () => {
|
|
2456
|
+
const now = this.fleetConfig?.instances[name];
|
|
2457
|
+
return !!now && now.channel_id === pinned.channel && now.machine_id === pinned.machine && now.native_adapter === pinned.adapter && now.runtime === pinned.runtime;
|
|
2458
|
+
};
|
|
2459
|
+
// Before the commit: the exact target and an unchanged snapshot. After runtime.update completes the transaction,
|
|
2460
|
+
// current() is false by design; the committed operation's restart receipt then needs the same actor, target and
|
|
2461
|
+
// switch checks plus `completed` — a completed execution can never authorize another write (mutate refuses).
|
|
2462
|
+
return updateNativeWindowsInstance(this, name, edit, () => allowed() && sameTarget() && (execution.completed || execution.current()), execution);
|
|
2463
|
+
} });
|
|
2464
|
+
}
|
|
1739
2465
|
async promptSettingsChange(store, view) {
|
|
1740
2466
|
const generation = this.settingsGeneration;
|
|
1741
2467
|
const authority = this.settingsChatAuthority(store, view.id);
|
|
@@ -2417,9 +3143,9 @@ export class FleetManager {
|
|
|
2417
3143
|
const outbox = this.deliveryOutbox;
|
|
2418
3144
|
if (!outbox)
|
|
2419
3145
|
throw new Error("Durable delivery store is unavailable");
|
|
2420
|
-
const
|
|
2421
|
-
const sourceDaemonBootId = input.sourceDaemonBootId ??
|
|
2422
|
-
if (!
|
|
3146
|
+
const currentSourceBootId = this.getDaemonBootId(input.sourceInstance);
|
|
3147
|
+
const sourceDaemonBootId = input.sourceDaemonBootId ?? currentSourceBootId;
|
|
3148
|
+
if (!currentSourceBootId || !sourceDaemonBootId || currentSourceBootId !== sourceDaemonBootId) {
|
|
2423
3149
|
throw new Error("Source daemon generation is no longer current; outcome is unknown");
|
|
2424
3150
|
}
|
|
2425
3151
|
const sourceKey = `mcp:${input.sourceInstance}:${input.operationId}:${input.targetInstance}:${input.targetSession ?? ""}:${input.kind}`;
|
|
@@ -2482,6 +3208,9 @@ export class FleetManager {
|
|
|
2482
3208
|
return this.daemons.get(target)?.notAcceptingReason?.() ?? null;
|
|
2483
3209
|
}
|
|
2484
3210
|
getDaemonBootId(instanceName) {
|
|
3211
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
|
|
3212
|
+
return !this.shuttingDown && windowsCliEnabled() && this.nativeMcpStore?.hasActive(instanceName) ? this.managerBootId : undefined;
|
|
3213
|
+
}
|
|
2485
3214
|
return this.daemons.get(instanceName)?.bootId;
|
|
2486
3215
|
}
|
|
2487
3216
|
markDurableResponseDelivered(sourceInstance, operationId) {
|
|
@@ -2635,6 +3364,15 @@ export class FleetManager {
|
|
|
2635
3364
|
this.deliveryPumpRunning = true;
|
|
2636
3365
|
this.deliveryPumpScheduled = false;
|
|
2637
3366
|
try {
|
|
3367
|
+
// Native workers have no resident daemon to wake. Pending durable work resumes their logical instance;
|
|
3368
|
+
// the Windows bridge still owns the single physical execution slot.
|
|
3369
|
+
for (const target of new Set(outbox.listPending().map(row => row.targetInstance))) {
|
|
3370
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[target])) {
|
|
3371
|
+
const runtime = this.nativeInstanceRuntime(), agent = runtime.get(target);
|
|
3372
|
+
if (agent?.paused && !agent.blocked)
|
|
3373
|
+
runtime.setPaused(target, false);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
2638
3376
|
outbox.expireStale(undefined, undefined, target => {
|
|
2639
3377
|
const failure = this.wakeCoordinator?.wakeFailure(target);
|
|
2640
3378
|
return failure ? `target could not be woken (${failure})` : undefined;
|
|
@@ -2647,7 +3385,7 @@ export class FleetManager {
|
|
|
2647
3385
|
// Phase 2c: a lane a queue worker owns is never claimed here.
|
|
2648
3386
|
target => this.queueWorkers.has(target) || this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target)
|
|
2649
3387
|
? null
|
|
2650
|
-
: this.
|
|
3388
|
+
: this.deliveryBootId(target), this.activeDurableTargets, row => this.recoveryDeliveryEligible(row));
|
|
2651
3389
|
if (!claimed)
|
|
2652
3390
|
break;
|
|
2653
3391
|
this.activeDurableTargets.add(claimed.targetInstance);
|
|
@@ -2708,7 +3446,7 @@ export class FleetManager {
|
|
|
2708
3446
|
wanted: () => this.deliveryWorkerMode(target) === "on",
|
|
2709
3447
|
available: () => !this.shuttingDown && this.deliveryOutbox?.isOpen === true,
|
|
2710
3448
|
blocked: () => this.isInstanceRestarting(target) || this.wakeCoordinator?.blocksClaim(target) === true,
|
|
2711
|
-
daemonBootId: () => this.
|
|
3449
|
+
daemonBootId: () => this.deliveryBootId(target),
|
|
2712
3450
|
claim: bootId => this.deliveryOutbox?.claimNext(this.managerBootId, candidate => candidate === target ? bootId : null, new Set(), row => this.recoveryDeliveryEligible(row)),
|
|
2713
3451
|
tryAcquireBudget: () => {
|
|
2714
3452
|
if (this.activeDurableTargets.size >= 8)
|
|
@@ -2739,6 +3477,10 @@ export class FleetManager {
|
|
|
2739
3477
|
const outbox = this.deliveryOutbox;
|
|
2740
3478
|
if (!outbox)
|
|
2741
3479
|
return;
|
|
3480
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[claimed.targetInstance])) {
|
|
3481
|
+
await this.dispatchNativeDurableDelivery(claimed);
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
2742
3484
|
const target = claimed.targetInstance;
|
|
2743
3485
|
const attempt = claimed.attemptNo;
|
|
2744
3486
|
const payload = { ...claimed.payload };
|
|
@@ -2820,6 +3562,85 @@ export class FleetManager {
|
|
|
2820
3562
|
}
|
|
2821
3563
|
await laneReleased;
|
|
2822
3564
|
}
|
|
3565
|
+
/** The ordinary durable lane, with provider completion proof instead of terminal/IPC submission proof. */
|
|
3566
|
+
async dispatchNativeDurableDelivery(claimed) {
|
|
3567
|
+
const outbox = this.deliveryOutbox;
|
|
3568
|
+
if (!outbox)
|
|
3569
|
+
return;
|
|
3570
|
+
const { deliveryId, targetDaemonBootId, attemptNo, targetInstance: target } = claimed;
|
|
3571
|
+
let begun = false;
|
|
3572
|
+
try {
|
|
3573
|
+
if (["raw_paste", "steer"].includes(claimed.kind)) {
|
|
3574
|
+
if (outbox.begin(deliveryId, targetDaemonBootId, attemptNo) === "begun")
|
|
3575
|
+
outbox.complete(deliveryId, targetDaemonBootId, attemptNo, "failed", "Windows runtime does not support terminal input or steering");
|
|
3576
|
+
return;
|
|
3577
|
+
}
|
|
3578
|
+
const meta = claimed.payload.meta && typeof claimed.payload.meta === "object" ? claimed.payload.meta : {};
|
|
3579
|
+
const task = await this.nativeInstanceRuntime().execute(target, { ...claimed.payload, requestId: deliveryId, meta: { ...meta, from_instance: claimed.sourceInstance, correlation_id: claimed.correlationId } }, {
|
|
3580
|
+
stillCurrent: () => !this.shuttingDown,
|
|
3581
|
+
beforeStart: () => { if (outbox.begin(deliveryId, targetDaemonBootId, attemptNo) !== "begun")
|
|
3582
|
+
throw new Error("Windows delivery attempt is no longer current"); begun = true; },
|
|
3583
|
+
});
|
|
3584
|
+
const outcome = task.outcomeConfirmed === false ? "uncertain" : task.status === "completed" ? "delivered" : "failed";
|
|
3585
|
+
outbox.complete(deliveryId, targetDaemonBootId, attemptNo, outcome, task.error || task.status);
|
|
3586
|
+
// Return a requested peer report when the provider did not explicitly report it through scoped MCP.
|
|
3587
|
+
if (meta.requires_reply === "true" && claimed.correlationId && !task.reportedCorrelationIds?.includes(claimed.correlationId) && this.fleetConfig?.instances[claimed.sourceInstance]) {
|
|
3588
|
+
const text = (task.status === "completed" ? task.output : `Windows task ${task.status}: ${task.error || task.output}`).slice(0, 8000);
|
|
3589
|
+
outbox.admit({ sourceKey: `native-result:${deliveryId}`, operationId: `native-result:${deliveryId}`, sourceInstance: target, sourceDaemonBootId: this.managerBootId,
|
|
3590
|
+
targetInstance: claimed.sourceInstance, kind: "fleet_inbound", correlationId: claimed.correlationId,
|
|
3591
|
+
payload: { type: "fleet_inbound", content: text || "Windows task completed without text output", meta: { from_instance: target, correlation_id: claimed.correlationId, requires_reply: "false", source: "native_runtime" } } });
|
|
3592
|
+
this.scheduleDeliveryOutboxPump();
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
catch (error) {
|
|
3596
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
3597
|
+
if (begun)
|
|
3598
|
+
outbox.complete(deliveryId, targetDaemonBootId, attemptNo, "uncertain", reason);
|
|
3599
|
+
else
|
|
3600
|
+
outbox.retryBeforeBegin(deliveryId, targetDaemonBootId, attemptNo, reason, 5000);
|
|
3601
|
+
this.logger.warn({ target, deliveryId, err: reason }, "Windows durable delivery did not complete");
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
/** A runtime's persisted completion can outlive this manager. Resolve its original delivery fence, never send
|
|
3605
|
+
* the prompt again, and reconstruct a missing peer report using the same durable operation ID. */
|
|
3606
|
+
reconcileRemoteDelivery(machineId, task) {
|
|
3607
|
+
const outbox = this.deliveryOutbox;
|
|
3608
|
+
if (!outbox?.isOpen || !task.finishedAt || task.outcomeConfirmed !== true || !["completed", "failed", "cancelled"].includes(task.status))
|
|
3609
|
+
return;
|
|
3610
|
+
const key = `${task.id}:${task.finishedAt}:${task.status}`;
|
|
3611
|
+
if (this.reconciledRemoteResults.has(key))
|
|
3612
|
+
return;
|
|
3613
|
+
const row = outbox.get(task.id);
|
|
3614
|
+
if (!row || !isNativeExecInstance(this.fleetConfig?.instances[row.targetInstance]) || this.fleetConfig?.instances[row.targetInstance]?.machine_id !== machineId
|
|
3615
|
+
|| !row.targetDaemonBootId || this.activeDurableTargets.has(row.targetInstance))
|
|
3616
|
+
return;
|
|
3617
|
+
if (row.state === "submission_started" || row.state === "reconciliation_pending") {
|
|
3618
|
+
if (!outbox.complete(row.deliveryId, row.targetDaemonBootId, row.attemptNo, task.status === "completed" ? "delivered" : "failed", task.error || "Runtime completion recovered after reconnect"))
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
else if (row.state === "uncertain" && task.status === "completed") {
|
|
3622
|
+
if (outbox.markConsumed(row.deliveryId, row.targetDaemonBootId, row.attemptNo, "turn", "Paired runtime journal confirmed this exact execution completed") === "ignored")
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
else if (row.state !== "delivered" && row.state !== "failed")
|
|
3626
|
+
return;
|
|
3627
|
+
const meta = row.payload.meta && typeof row.payload.meta === "object" ? row.payload.meta : {};
|
|
3628
|
+
if (meta.requires_reply !== "true" || !row.correlationId || !this.fleetConfig?.instances[row.sourceInstance]) {
|
|
3629
|
+
this.reconciledRemoteResults.add(key);
|
|
3630
|
+
return;
|
|
3631
|
+
}
|
|
3632
|
+
const reports = outbox.queryStatusForInstance(row.targetInstance, { correlationId: row.correlationId, limit: 100 }).items;
|
|
3633
|
+
if (reports.some(report => report.source_instance === row.targetInstance && report.target_instance === row.sourceInstance
|
|
3634
|
+
&& report.kind === "report" && !["failed", "cancelled"].includes(report.state))) {
|
|
3635
|
+
this.reconciledRemoteResults.add(key);
|
|
3636
|
+
return;
|
|
3637
|
+
}
|
|
3638
|
+
const text = (task.status === "completed" ? task.output : `Windows task ${task.status}: ${task.error || task.output}`).slice(0, 8000);
|
|
3639
|
+
outbox.admit({ sourceKey: `native-result:${row.deliveryId}`, operationId: `native-result:${row.deliveryId}`, sourceInstance: row.targetInstance, sourceDaemonBootId: this.managerBootId,
|
|
3640
|
+
targetInstance: row.sourceInstance, kind: "fleet_inbound", correlationId: row.correlationId,
|
|
3641
|
+
payload: { type: "fleet_inbound", content: text || "Windows task completed without text output", meta: { from_instance: row.targetInstance, correlation_id: row.correlationId, requires_reply: "false", source: "native_runtime" } } });
|
|
3642
|
+
this.reconciledRemoteResults.add(key);
|
|
3643
|
+
}
|
|
2823
3644
|
async waitForDurableLaneRelease(claimed) {
|
|
2824
3645
|
const outbox = this.deliveryOutbox;
|
|
2825
3646
|
if (!outbox)
|
|
@@ -3276,6 +4097,7 @@ export class FleetManager {
|
|
|
3276
4097
|
this.warnAboutRemovedLoginMode(loaded);
|
|
3277
4098
|
this.enforceGuestSwitch();
|
|
3278
4099
|
this.observeWebAuthority(); // #2453 review: a reload's web-authority switches are seen at once
|
|
4100
|
+
this.observeRuntimeSwitches(); // M-2255: so are the Windows runtimes' switches
|
|
3279
4101
|
// Child tool/guidance readers must follow this fleet's actual config, including --config.
|
|
3280
4102
|
// Scope the path to its owning data directory so another inherited home's override cannot enable CW.
|
|
3281
4103
|
process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
|
|
@@ -3704,6 +4526,8 @@ export class FleetManager {
|
|
|
3704
4526
|
}
|
|
3705
4527
|
/** Get the adapter bound to an instance, falling back to primary adapter */
|
|
3706
4528
|
getAdapterForInstance(name) {
|
|
4529
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name]))
|
|
4530
|
+
return null;
|
|
3707
4531
|
const worldId = this.getInstanceAdapterId(name);
|
|
3708
4532
|
if (worldId)
|
|
3709
4533
|
return this.worlds.get(worldId)?.adapter ?? this.adapter;
|
|
@@ -3766,6 +4590,8 @@ export class FleetManager {
|
|
|
3766
4590
|
}
|
|
3767
4591
|
/** Get the group_id for an instance's bound adapter */
|
|
3768
4592
|
getGroupIdForInstance(name) {
|
|
4593
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name]))
|
|
4594
|
+
return "";
|
|
3769
4595
|
const adapterId = this.getInstanceAdapterId(name);
|
|
3770
4596
|
const world = adapterId ? this.worlds.get(adapterId) : undefined;
|
|
3771
4597
|
return world?.groupId ?? String(this.getChannelConfig(adapterId)?.group_id ?? "");
|
|
@@ -3802,6 +4628,8 @@ export class FleetManager {
|
|
|
3802
4628
|
*/
|
|
3803
4629
|
getInstanceAdapterId(name) {
|
|
3804
4630
|
const cfg = this.fleetConfig?.instances[name];
|
|
4631
|
+
if (isNativeExecInstance(cfg))
|
|
4632
|
+
return undefined;
|
|
3805
4633
|
if (cfg)
|
|
3806
4634
|
return cfg.channel_id ?? this.getPrimaryAdapterId();
|
|
3807
4635
|
if (this.classicChannels?.getChannelIdByInstance(name) !== undefined) {
|
|
@@ -3829,9 +4657,32 @@ export class FleetManager {
|
|
|
3829
4657
|
this.instanceWorldBinding.set(name, adapterId);
|
|
3830
4658
|
}
|
|
3831
4659
|
getInstanceStatus(name) {
|
|
4660
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
4661
|
+
try {
|
|
4662
|
+
return this.nativeInstanceRuntime().status(name);
|
|
4663
|
+
}
|
|
4664
|
+
catch {
|
|
4665
|
+
return "crashed";
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
3832
4668
|
if (this.lifecycle.isPaused(name))
|
|
3833
4669
|
return "paused";
|
|
3834
4670
|
const daemon = this.lifecycle.daemons.get(name);
|
|
4671
|
+
if (isNativePaneInstance(this.fleetConfig?.instances[name])) {
|
|
4672
|
+
const config = this.fleetConfig.instances[name];
|
|
4673
|
+
if (config.machine_id ? !remoteRuntimeEnabled() : !localWindowsCliEnabled())
|
|
4674
|
+
return "stopped";
|
|
4675
|
+
if (daemon) {
|
|
4676
|
+
const native = daemon, status = native.getProcessStatus?.() ?? "crashed";
|
|
4677
|
+
return status === "running" && native.nativePaneCurrent?.() !== true ? "crashed" : status;
|
|
4678
|
+
}
|
|
4679
|
+
try {
|
|
4680
|
+
return this.nativeInstanceRuntime().get(name)?.activeId ? "crashed" : "stopped";
|
|
4681
|
+
}
|
|
4682
|
+
catch {
|
|
4683
|
+
return "crashed";
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
3835
4686
|
const processStatus = this.instanceProcessStatus.get(name);
|
|
3836
4687
|
// IPC can be disconnected during a respawn, so the event which announces
|
|
3837
4688
|
// the new live pane may be missed. The in-process daemon is authoritative
|
|
@@ -3865,6 +4716,14 @@ export class FleetManager {
|
|
|
3865
4716
|
}
|
|
3866
4717
|
}
|
|
3867
4718
|
getInstanceExecutionState(name) {
|
|
4719
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
4720
|
+
try {
|
|
4721
|
+
return this.nativeInstanceRuntime().execution(name);
|
|
4722
|
+
}
|
|
4723
|
+
catch {
|
|
4724
|
+
return null;
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
3868
4727
|
if (this.lifecycle.isPaused(name))
|
|
3869
4728
|
return null;
|
|
3870
4729
|
// Process status wins over a stale pane snapshot. A dead remain-on-exit pane
|
|
@@ -3875,6 +4734,8 @@ export class FleetManager {
|
|
|
3875
4734
|
}
|
|
3876
4735
|
/** Pure read of the single daemon-owned observation; no probe/capture or second tracker. */
|
|
3877
4736
|
getInstanceInteraction(name) {
|
|
4737
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name]))
|
|
4738
|
+
return null;
|
|
3878
4739
|
if (this.lifecycle.isPaused(name) || this.instanceProcessStatus.has(name))
|
|
3879
4740
|
return null;
|
|
3880
4741
|
const daemon = this.lifecycle.daemons.get(name);
|
|
@@ -3962,6 +4823,8 @@ export class FleetManager {
|
|
|
3962
4823
|
bindings.push([name, backend, profile]);
|
|
3963
4824
|
};
|
|
3964
4825
|
for (const [name, config] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
4826
|
+
if (isWindowsInstance(config))
|
|
4827
|
+
continue; // Windows sign-ins and usage do not belong to the Linux account.
|
|
3965
4828
|
// loadFleetConfig() has already merged the fleet default into each row.
|
|
3966
4829
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
3967
4830
|
add(name, backend, instanceCredentialProfile(config, this.fleetConfig?.defaults, backend));
|
|
@@ -4568,6 +5431,8 @@ export class FleetManager {
|
|
|
4568
5431
|
* id is no platform message, and the echo is the fleet's own post. The web send calls this on the echo's lane.
|
|
4569
5432
|
*/
|
|
4570
5433
|
startWebChatProgress(instanceName) {
|
|
5434
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName]))
|
|
5435
|
+
return Promise.resolve();
|
|
4571
5436
|
if (this.shuttingDown)
|
|
4572
5437
|
return Promise.resolve();
|
|
4573
5438
|
return this.sendCancelButton(instanceName);
|
|
@@ -4833,6 +5698,10 @@ export class FleetManager {
|
|
|
4833
5698
|
}
|
|
4834
5699
|
/** Single delivery facade: wake paused CLIs and serialize non-user work behind idle. */
|
|
4835
5700
|
async deliverToInstance(instanceName, payload, options = {}) {
|
|
5701
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
|
|
5702
|
+
return this.nativeInstanceRuntime().deliver(instanceName, payload, { stillCurrent: options.stillCurrent,
|
|
5703
|
+
...(options.authorityCurrent ? { authorityCurrent: options.authorityCurrent } : {}) });
|
|
5704
|
+
}
|
|
4836
5705
|
const deliveryEpoch = this.getDeliveryEpoch(instanceName);
|
|
4837
5706
|
const deliveryPayload = { ...payload, delivery_epoch: deliveryEpoch };
|
|
4838
5707
|
const meta = payload.meta && typeof payload.meta === "object"
|
|
@@ -5390,6 +6259,10 @@ export class FleetManager {
|
|
|
5390
6259
|
* slot admission and joins any wake already in flight for the target.
|
|
5391
6260
|
*/
|
|
5392
6261
|
async explicitWake(name, timeoutMs = 30_000, retryStoppedSession = false) {
|
|
6262
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
6263
|
+
this.nativeInstanceRuntime().setPaused(name, false);
|
|
6264
|
+
return;
|
|
6265
|
+
}
|
|
5393
6266
|
if (this.sessionOomStopped(name)) {
|
|
5394
6267
|
if (!retryStoppedSession)
|
|
5395
6268
|
throw new SessionOomStoppedError(false, readSessionOomStop(this.getInstanceDir(name)));
|
|
@@ -5414,6 +6287,10 @@ export class FleetManager {
|
|
|
5414
6287
|
.sort((a, b) => (readLastInboundAt(this.getInstanceDir(a)) ?? 0) - (readLastInboundAt(this.getInstanceDir(b)) ?? 0))[0];
|
|
5415
6288
|
}
|
|
5416
6289
|
async changeInstancePauseState(name, action) {
|
|
6290
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
6291
|
+
this.nativeInstanceRuntime().setPaused(name, action === "pause");
|
|
6292
|
+
return action === "pause" ? "paused" : "awake";
|
|
6293
|
+
}
|
|
5417
6294
|
if (action === "wake") {
|
|
5418
6295
|
await this.explicitWake(name, 30_000, true);
|
|
5419
6296
|
this.enforceWarmCap(name); // manual wake still respects the fleet warm cap
|
|
@@ -5591,6 +6468,14 @@ export class FleetManager {
|
|
|
5591
6468
|
resumePaused = false,
|
|
5592
6469
|
/** Phase 2a: the caller's transition, for a start made from inside a restart or wake. */
|
|
5593
6470
|
transition, execution, kiroMigration) {
|
|
6471
|
+
if (isNativeExecInstance(config)) {
|
|
6472
|
+
execution?.assert();
|
|
6473
|
+
if (!windowsCliEnabled())
|
|
6474
|
+
throw new Error("Windows runtime is disabled");
|
|
6475
|
+
if (resumePaused)
|
|
6476
|
+
this.nativeInstanceRuntime().setPaused(name, false);
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
5594
6479
|
const migration = this.kiroMigrationRuns?.get(name);
|
|
5595
6480
|
if (migration && migration.token !== kiroMigration)
|
|
5596
6481
|
throw new Error(t("kiro_migrate.in_progress"));
|
|
@@ -5641,10 +6526,10 @@ export class FleetManager {
|
|
|
5641
6526
|
if (resumePaused)
|
|
5642
6527
|
this.lifecycle.markActivitySeedNow(name);
|
|
5643
6528
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
5644
|
-
if (config.general_topic) {
|
|
6529
|
+
if (!isWindowsInstance(config) && config.general_topic) {
|
|
5645
6530
|
this.ensureGeneralInstructions(config.working_directory, backend, name);
|
|
5646
6531
|
}
|
|
5647
|
-
else if (kind === "fleet-topic") {
|
|
6532
|
+
else if (!isWindowsInstance(config) && kind === "fleet-topic") {
|
|
5648
6533
|
// Workers get only role-eligible on-demand skills. Classic instances are
|
|
5649
6534
|
// deliberately excluded: their workspace and conversation lifecycle are
|
|
5650
6535
|
// managed independently from fleet-topic workers.
|
|
@@ -5659,7 +6544,7 @@ export class FleetManager {
|
|
|
5659
6544
|
this.logger.warn({ err, name, backend }, "Failed to sync worker skills — continuing startup");
|
|
5660
6545
|
}
|
|
5661
6546
|
}
|
|
5662
|
-
else if (kind === "classic") {
|
|
6547
|
+
else if (!isWindowsInstance(config) && kind === "classic") {
|
|
5663
6548
|
// #2163: a Classic bot gets only the skills marked `classic` (its own look, the dashboard) — never the fleet's
|
|
5664
6549
|
// coordination playbooks (#557 kept those out of end-user bots) — and only into a workspace AgEnD manages: the
|
|
5665
6550
|
// room's own folder under <AGEND_HOME>/workspaces/. Anywhere else, nothing is written; `agend-agent skill <name>`
|
|
@@ -5676,7 +6561,7 @@ export class FleetManager {
|
|
|
5676
6561
|
}
|
|
5677
6562
|
}
|
|
5678
6563
|
const receipt = execution ? this.classicSettingsOwners.get(execution) : undefined;
|
|
5679
|
-
const identity = { kind, backend, model: this.resolveInstanceModel(name).display };
|
|
6564
|
+
const identity = { kind, backend, model: isWindowsInstance(config) ? config.model || "default" : this.resolveInstanceModel(name).display };
|
|
5680
6565
|
if (receipt) {
|
|
5681
6566
|
await this.lifecycle.start(name, config, topicMode, identity, transition, execution, daemon => {
|
|
5682
6567
|
// Only this transition's actual publication can acquire its replacement.
|
|
@@ -5746,7 +6631,7 @@ export class FleetManager {
|
|
|
5746
6631
|
// them before grouping/staggering: startInstance() retains its own guard as
|
|
5747
6632
|
// a final backstop, but putting a no-op entry in this queue still consumes a
|
|
5748
6633
|
// full stagger slot for every distinct working directory.
|
|
5749
|
-
const runnableEntries = entries.filter(([name]) => !this.lifecycle.isPaused(name));
|
|
6634
|
+
const runnableEntries = entries.filter(([name, config]) => !isNativeExecInstance(config) && !this.lifecycle.isPaused(name));
|
|
5750
6635
|
// ClassicBot rooms queue with the fleet instances, at the same pressure pacing (#2642 follow-up): they used to start
|
|
5751
6636
|
// only after the whole fleet, the chat connections, topics and icons. A name the fleet also configures is the fleet's.
|
|
5752
6637
|
const fleetNames = new Set(entries.map(([name]) => name));
|
|
@@ -5812,6 +6697,8 @@ export class FleetManager {
|
|
|
5812
6697
|
return false;
|
|
5813
6698
|
if (err instanceof SessionOomStoppedError)
|
|
5814
6699
|
await this.notifySessionOomStop(name);
|
|
6700
|
+
if (this.holdNativePaneRecovery(name, err))
|
|
6701
|
+
return false;
|
|
5815
6702
|
if (this.stopOnUnsupportedCli(name, err, what))
|
|
5816
6703
|
return false;
|
|
5817
6704
|
this.logger.error({ err, name }, `Failed to start ${what}`);
|
|
@@ -5953,6 +6840,8 @@ export class FleetManager {
|
|
|
5953
6840
|
if (err instanceof SupersededStartError)
|
|
5954
6841
|
return;
|
|
5955
6842
|
// An earlier attempt could not tell (unknown probe); this one could.
|
|
6843
|
+
if (this.holdNativePaneRecovery(name, err))
|
|
6844
|
+
return;
|
|
5956
6845
|
if (this.stopOnUnsupportedCli(name, err, "instance"))
|
|
5957
6846
|
return;
|
|
5958
6847
|
this.logger.error({ err, name, attempt: attempt + 1 }, "Automatic startup retry failed");
|
|
@@ -6203,7 +7092,7 @@ export class FleetManager {
|
|
|
6203
7092
|
return count;
|
|
6204
7093
|
}
|
|
6205
7094
|
configuredStartupInstanceNames(fleet, includeClassic) {
|
|
6206
|
-
const names = new Set(Object.
|
|
7095
|
+
const names = new Set(Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config)).map(([name]) => name));
|
|
6207
7096
|
if (includeClassic) {
|
|
6208
7097
|
for (const channel of this.classicChannels?.getAll() ?? [])
|
|
6209
7098
|
names.add(channel.instanceName);
|
|
@@ -6254,6 +7143,10 @@ export class FleetManager {
|
|
|
6254
7143
|
return poster.postCompletion({ resolveAdapter: () => this.readyProgressAdapter(adapterId), chatId, threadId }, text);
|
|
6255
7144
|
}
|
|
6256
7145
|
async stopInstance(name, transition) {
|
|
7146
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
7147
|
+
this.nativeInstanceRuntime().setPaused(name, true);
|
|
7148
|
+
return { backgroundWorkEnded: false };
|
|
7149
|
+
}
|
|
6257
7150
|
this.explicitStopGeneration.set(name, (this.explicitStopGeneration.get(name) ?? 0) + 1);
|
|
6258
7151
|
this.cancelStartupRetry(name);
|
|
6259
7152
|
this.failoverActive.delete(name);
|
|
@@ -6291,6 +7184,16 @@ export class FleetManager {
|
|
|
6291
7184
|
}
|
|
6292
7185
|
/** Restart a single instance, reloading fleet.yaml first to pick up config changes. */
|
|
6293
7186
|
async restartSingleInstance(name, opts) {
|
|
7187
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[name])) {
|
|
7188
|
+
const runtime = this.nativeInstanceRuntime();
|
|
7189
|
+
if (runtime.get(name)?.blocked)
|
|
7190
|
+
throw new Error("Verify the disconnected Windows process stopped and recover this agent before restarting");
|
|
7191
|
+
runtime.cancel(name);
|
|
7192
|
+
runtime.setPaused(name, false);
|
|
7193
|
+
return;
|
|
7194
|
+
}
|
|
7195
|
+
if (opts?.explicit && isNativePaneInstance(this.fleetConfig?.instances[name]))
|
|
7196
|
+
this.lifecycle.resetNativeCrashBudget(name);
|
|
6294
7197
|
const migration = this.kiroMigrationRuns?.get(name);
|
|
6295
7198
|
if (migration && migration.token !== opts?.kiroMigration)
|
|
6296
7199
|
throw new Error(t("kiro_migrate.in_progress"));
|
|
@@ -6354,7 +7257,7 @@ export class FleetManager {
|
|
|
6354
7257
|
return run;
|
|
6355
7258
|
}
|
|
6356
7259
|
isInstanceRestarting(name) {
|
|
6357
|
-
return this.restartsInFlight.has(name) || this.kiroMigrationRuns?.has(name) === true;
|
|
7260
|
+
return this.restartsInFlight.has(name) || this.kiroMigrationRuns?.has(name) === true || this.lifecycle?.nativeRecoveryPending?.(name) === true;
|
|
6358
7261
|
}
|
|
6359
7262
|
/** InstanceLifecycleContext: an explicit wake joins the restart in flight. */
|
|
6360
7263
|
restartInFlight(name) {
|
|
@@ -8125,6 +9028,9 @@ export class FleetManager {
|
|
|
8125
9028
|
const fleet = this.loadConfig(configPath, { startup: true });
|
|
8126
9029
|
this.slimFleetConfigAtStartup();
|
|
8127
9030
|
this.logConfigWarningsAtStartup();
|
|
9031
|
+
this.initializeNativeInstances();
|
|
9032
|
+
// M-2255: from here on a change to the Windows runtimes' switches applies live (observeRuntimeSwitches).
|
|
9033
|
+
this.runtimeEffects = { windows_cli: localWindowsCliEnabled(), remote_runtime: remoteRuntimeEnabled() };
|
|
8128
9034
|
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
8129
9035
|
// #1737 review: the marker this boot adopts — the startup clear may only ever clear this one.
|
|
8130
9036
|
const bootUpdateMarker = updateMarkerIdentity(this.dataDir);
|
|
@@ -8156,7 +9062,7 @@ export class FleetManager {
|
|
|
8156
9062
|
// Pre-flight (advisory, fire-and-forget): warm the per-backend auth cache
|
|
8157
9063
|
// before instances spawn, so a CLI that comes up on its sign-in screen gets
|
|
8158
9064
|
// an instant 🔑 verdict instead of decaying into crash/MCP-died noise.
|
|
8159
|
-
this.lifecycle.primeAuthVerification(Object.values(fleet.instances).map(config => {
|
|
9065
|
+
this.lifecycle.primeAuthVerification(Object.values(fleet.instances).filter(config => !isWindowsInstance(config)).map(config => {
|
|
8160
9066
|
const backend = config.backend ?? fleet.defaults?.backend ?? "claude-code";
|
|
8161
9067
|
// #1699 follow-up: each instance's own login — a profile is probed with its own environment.
|
|
8162
9068
|
return { backend, profile: credentialHomeSpec(backend) ? instanceCredentialProfile(config, fleet.defaults, backend) : null };
|
|
@@ -8218,6 +9124,7 @@ export class FleetManager {
|
|
|
8218
9124
|
// #1565: the web chat as it was before this process — the configured instances' only (fleet.yaml and ClassicBot
|
|
8219
9125
|
// rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
|
|
8220
9126
|
this.restoreWebChat(fleet);
|
|
9127
|
+
this.initializeNativeInstances(true);
|
|
8221
9128
|
this.startSearchIndex(fleet);
|
|
8222
9129
|
// Parse-only source monitoring; it does not apply hand-edited Fleet settings.
|
|
8223
9130
|
this.configHistoryTimer = setInterval(() => this.configHistory?.check(), 1_000);
|
|
@@ -8396,7 +9303,7 @@ export class FleetManager {
|
|
|
8396
9303
|
}
|
|
8397
9304
|
}
|
|
8398
9305
|
// Phase 1: Start general instances first and wait for them
|
|
8399
|
-
const allEntries = Object.entries(fleet.instances);
|
|
9306
|
+
const allEntries = Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config));
|
|
8400
9307
|
const generals = allEntries.filter(([_, cfg]) => cfg.general_topic);
|
|
8401
9308
|
const others = allEntries.filter(([_, cfg]) => !cfg.general_topic);
|
|
8402
9309
|
const startupProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), pendingUpdateProgress?.startedAt ?? startupStartedAt, this.logger, { mode: pendingProgressOperation === "full-restart" ? "reload" : pendingUpdateProgress ? "update" : "restart" });
|
|
@@ -9398,10 +10305,14 @@ export class FleetManager {
|
|
|
9398
10305
|
}
|
|
9399
10306
|
}
|
|
9400
10307
|
}
|
|
10308
|
+
const daemon = this.daemons.get(name);
|
|
10309
|
+
const native = daemon?.usesNativePane?.();
|
|
9401
10310
|
const sockPath = join(this.getInstanceDir(name), "channel.sock");
|
|
9402
|
-
if (!existsSync(sockPath))
|
|
10311
|
+
if (!native && !existsSync(sockPath))
|
|
9403
10312
|
return;
|
|
9404
|
-
const ipc =
|
|
10313
|
+
const ipc = native
|
|
10314
|
+
? new NativeDaemonClient(daemon, () => this.daemons.get(name) === daemon && (!this.instanceIpcClients.has(name) || this.instanceIpcClients.get(name) === ipc))
|
|
10315
|
+
: new IpcClient(sockPath);
|
|
9405
10316
|
try {
|
|
9406
10317
|
await ipc.connect();
|
|
9407
10318
|
this.instanceIpcClients.set(name, ipc);
|
|
@@ -9517,7 +10428,7 @@ export class FleetManager {
|
|
|
9517
10428
|
// connected, so seed the cache instead of waiting for another transition.
|
|
9518
10429
|
ipc.send({ type: "query_instance_state", requestId: `fleet-state-${Date.now()}` });
|
|
9519
10430
|
this.logger.debug({ name }, "Connected to instance IPC");
|
|
9520
|
-
if (!this.statuslineWatcher.has(name)) {
|
|
10431
|
+
if (!native && !this.statuslineWatcher.has(name)) {
|
|
9521
10432
|
this.statuslineWatcher.watch(name);
|
|
9522
10433
|
}
|
|
9523
10434
|
// Auto-reconnect on disconnect (unless intentionally stopping)
|
|
@@ -12580,6 +13491,7 @@ export class FleetManager {
|
|
|
12580
13491
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
12581
13492
|
this.refreshSavedDashboardPermissions();
|
|
12582
13493
|
this.logger.info({ path: this.configPath, strippedDefaults: redundantPaths.length }, "Saved fleet config (lossless patch)");
|
|
13494
|
+
this.observeRuntimeSwitches(); // M-2255: a saved Windows runtime switch applies now
|
|
12583
13495
|
}
|
|
12584
13496
|
/** A permission tightening is effective when its save returns, independently of the later apply job or IPC. */
|
|
12585
13497
|
refreshSavedDashboardPermissions() {
|
|
@@ -12813,10 +13725,43 @@ export class FleetManager {
|
|
|
12813
13725
|
}
|
|
12814
13726
|
getSettingsConfigPath() { return this.configPath; }
|
|
12815
13727
|
async removeInstance(name, authorization, execution) {
|
|
13728
|
+
if (isWindowsInstance(this.fleetConfig?.instances[name])) {
|
|
13729
|
+
execution?.assert();
|
|
13730
|
+
assertExplicitInstanceRemoval(authorization);
|
|
13731
|
+
if (isNativePaneInstance(this.fleetConfig?.instances[name])) {
|
|
13732
|
+
await this.stopInstance(name);
|
|
13733
|
+
execution?.assert();
|
|
13734
|
+
}
|
|
13735
|
+
this.nativeInstanceRuntime().remove(name, () => !this.shuttingDown && (!execution || execution.current()), execution);
|
|
13736
|
+
return;
|
|
13737
|
+
}
|
|
12816
13738
|
if (execution)
|
|
12817
13739
|
return this.lifecycle.runTransition(name, transition => this.removeInstanceOwned(name, authorization, execution, transition));
|
|
12818
13740
|
return this.removeInstanceOwned(name, authorization);
|
|
12819
13741
|
}
|
|
13742
|
+
/** Shared instance metadata goes with a native identity; its Windows workspace and provider sessions stay intact. */
|
|
13743
|
+
nativeInstanceRemoved(name) {
|
|
13744
|
+
const clean = (what, run) => { try {
|
|
13745
|
+
run();
|
|
13746
|
+
}
|
|
13747
|
+
catch (error) {
|
|
13748
|
+
this.logger.warn({ name, err: error }, `Native instance ${what} cleanup failed`);
|
|
13749
|
+
} };
|
|
13750
|
+
clean("delivery", () => { this.cancelPendingDeliveries(name); });
|
|
13751
|
+
forgetInstanceContext(name);
|
|
13752
|
+
clean("schedules", () => { for (const schedule of this.scheduler?.list(name) ?? [])
|
|
13753
|
+
this.scheduler.delete(schedule.id); });
|
|
13754
|
+
this.lastActivity.delete(name);
|
|
13755
|
+
this.lastInboundUser.delete(name);
|
|
13756
|
+
this.rateLimitWarnedAt.delete(name);
|
|
13757
|
+
this.lastInboundMsg.delete(name);
|
|
13758
|
+
clean("history", () => this.webChatHistory.forget(name));
|
|
13759
|
+
clean("search", () => this.searchIndex?.removeInstance(name));
|
|
13760
|
+
clean("files", () => this.webFiles.forget(name));
|
|
13761
|
+
clean("compact", () => this.compactWatchService?.forget(name));
|
|
13762
|
+
clean("preferences", () => this.webPrefs.forgetInstance(name));
|
|
13763
|
+
clean("appearance", () => { this.themeStore.forget(name); this.webAppearance.forget(name); this.appearanceTools.forget(name); this.mods.forgetInstance(name); this.officeActsInstance?.forget(name); this.officePackToolsInstance?.forget(name); });
|
|
13764
|
+
}
|
|
12820
13765
|
async removeInstanceOwned(name, authorization, execution, transition) {
|
|
12821
13766
|
execution?.assert();
|
|
12822
13767
|
assertExplicitInstanceRemoval(authorization);
|
|
@@ -12939,6 +13884,8 @@ export class FleetManager {
|
|
|
12939
13884
|
execution?.complete();
|
|
12940
13885
|
}
|
|
12941
13886
|
startStatuslineWatcher(name) {
|
|
13887
|
+
if (isWindowsInstance(this.fleetConfig?.instances[name]))
|
|
13888
|
+
return;
|
|
12942
13889
|
if (this.lifecycle.isPaused(name))
|
|
12943
13890
|
return;
|
|
12944
13891
|
this.statuslineWatcher.watch(name);
|
|
@@ -14387,6 +15334,10 @@ export class FleetManager {
|
|
|
14387
15334
|
* would have to make this async and change every caller.
|
|
14388
15335
|
*/
|
|
14389
15336
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
15337
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
|
|
15338
|
+
this.emitSseEvent("message", { instance: instanceName, role: "status", sender: "AgEnD", text, ts: new Date().toISOString() });
|
|
15339
|
+
return true;
|
|
15340
|
+
}
|
|
14390
15341
|
// #2910 D2: a web-only fleet has no topics: what the agent's topic would have been told is a Notice about it.
|
|
14391
15342
|
if (this.webOnlyPromptPlace())
|
|
14392
15343
|
return this.recordWebNotice("instance", text, instanceName);
|
|
@@ -14866,6 +15817,8 @@ export class FleetManager {
|
|
|
14866
15817
|
* (also for a backend that has no profiles, where the option is ignored).
|
|
14867
15818
|
*/
|
|
14868
15819
|
credentialProfileOf(name, backend) {
|
|
15820
|
+
if (isWindowsInstance(this.fleetConfig?.instances[name]))
|
|
15821
|
+
return null;
|
|
14869
15822
|
if (!credentialHomeSpec(backend))
|
|
14870
15823
|
return null;
|
|
14871
15824
|
const cfg = this.fleetConfig?.instances?.[name];
|
|
@@ -14888,7 +15841,7 @@ export class FleetManager {
|
|
|
14888
15841
|
}
|
|
14889
15842
|
/** The instances the cache analysis reads: fleet.yaml's, then the ClassicBot rooms (their workspace under the AgEnD home, their own backend). */
|
|
14890
15843
|
cacheInstances() {
|
|
14891
|
-
const fleet = Object.entries(this.fleetConfig?.instances ?? {}).map(([name, cfg]) => ({
|
|
15844
|
+
const fleet = Object.entries(this.fleetConfig?.instances ?? {}).filter(([, cfg]) => !isWindowsInstance(cfg)).map(([name, cfg]) => ({
|
|
14892
15845
|
name, backend: this.backendNameOf(name), workingDirectory: cfg.working_directory, ledgerPath: join(this.getInstanceDir(name), "cache-ledger.json"),
|
|
14893
15846
|
claudeConfigDir: this.claudeConfigDirOf(name), profile: this.usageProfileOf(name, this.backendNameOf(name)),
|
|
14894
15847
|
kiroDbPath: this.kiroDbPathOf(name, this.backendNameOf(name)),
|
|
@@ -15067,6 +16020,19 @@ export class FleetManager {
|
|
|
15067
16020
|
crashedAt: crashed ? (this.needsCrashedAt.get(name) ?? null) : null,
|
|
15068
16021
|
signIn: pauseReason === "auth" ? this.signInFor(name) : null,
|
|
15069
16022
|
usageHold: this.daemons.get(name)?.usageHold?.() ?? null,
|
|
16023
|
+
nativeDeliveryHold: this.daemons.get(name)?.nativeDeliveryHold?.() ?? null,
|
|
16024
|
+
paneRecovery: (() => {
|
|
16025
|
+
const config = this.fleetConfig?.instances[name];
|
|
16026
|
+
if (!isNativePaneInstance(config) || !config.native_agent_id)
|
|
16027
|
+
return null;
|
|
16028
|
+
try {
|
|
16029
|
+
const profile = windowsAgentsFor(this, this.runtimeWindowsExecutor()).get(config.native_agent_id);
|
|
16030
|
+
return profile.paneRecovery ? { key: `${profile.paneRecovery.kind}:${profile.paneRecovery.launchId ?? "-"}`, detail: new NativePaneRecoveryError(profile.paneRecovery).message, since: profile.paneRecoveryAt ?? profile.createdAt } : null;
|
|
16031
|
+
}
|
|
16032
|
+
catch {
|
|
16033
|
+
return null;
|
|
16034
|
+
}
|
|
16035
|
+
})(),
|
|
15070
16036
|
});
|
|
15071
16037
|
}
|
|
15072
16038
|
return out;
|
|
@@ -15122,6 +16088,8 @@ export class FleetManager {
|
|
|
15122
16088
|
const target = this.fleetNoticeTarget(world);
|
|
15123
16089
|
return target ? { chatId: target.chatId, ...(target.opts.threadId !== undefined ? { threadId: String(target.opts.threadId) } : {}) } : null;
|
|
15124
16090
|
},
|
|
16091
|
+
// The name the web page shows for the instance (its View alias, else its own name), never the raw id alone.
|
|
16092
|
+
instanceName: instance => resolveInstanceIdentity({ cfg: this.fleetConfig?.instances[instance], profile: profileIdentities(this.dataDir).get(instance) }).display_name || instance,
|
|
15125
16093
|
instanceTopic: instance => {
|
|
15126
16094
|
const topic = this.fleetConfig?.instances[instance]?.topic_id;
|
|
15127
16095
|
if (topic != null)
|
|
@@ -16755,6 +17723,8 @@ export class FleetManager {
|
|
|
16755
17723
|
publication.retirePending = true;
|
|
16756
17724
|
}
|
|
16757
17725
|
async sendCancelButton(instanceName, correlationId, preserveProgress = false) {
|
|
17726
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName]))
|
|
17727
|
+
return;
|
|
16758
17728
|
// Post first, retire after (see the tail of this method). Retiring up front
|
|
16759
17729
|
// meant that from the delete until the new message came back — a chat API
|
|
16760
17730
|
// round trip, and every reply goes through here — the instance had NO live
|
|
@@ -17287,6 +18257,14 @@ export class FleetManager {
|
|
|
17287
18257
|
}
|
|
17288
18258
|
/** Interrupt an instance's current generation (cancel button / /cancel). */
|
|
17289
18259
|
cancelInstance(instanceName) {
|
|
18260
|
+
if (isNativeExecInstance(this.fleetConfig?.instances[instanceName])) {
|
|
18261
|
+
this.cancelPendingDeliveries(instanceName);
|
|
18262
|
+
const cancelled = this.nativeInstanceRuntime().cancel(instanceName);
|
|
18263
|
+
this.lastInboundMsg.delete(instanceName);
|
|
18264
|
+
for (const message of this.webChatHistory.cancelPending(instanceName))
|
|
18265
|
+
this.emitSseEvent("delivery", { instance: instanceName, messageId: message.messageId, delivery: message.delivery });
|
|
18266
|
+
return cancelled !== null;
|
|
18267
|
+
}
|
|
17290
18268
|
const daemon = this.daemons.get(instanceName);
|
|
17291
18269
|
if (!daemon)
|
|
17292
18270
|
return false;
|
|
@@ -19547,6 +20525,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19547
20525
|
: model,
|
|
19548
20526
|
});
|
|
19549
20527
|
const fleetInstance = this.fleetConfig?.instances[instanceName];
|
|
20528
|
+
if (isWindowsInstance(fleetInstance))
|
|
20529
|
+
return fleetInstance.model?.trim() ? done(fleetInstance.model.trim(), "instance") : done("default", "unresolved", "this CLI does not report a default");
|
|
19550
20530
|
if (fleetInstance) {
|
|
19551
20531
|
if (fleetInstance.model?.trim())
|
|
19552
20532
|
return done(fleetInstance.model.trim(), "instance");
|
|
@@ -19628,7 +20608,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19628
20608
|
if (this.fleetConfig?.defaults?.backend)
|
|
19629
20609
|
backends.add(this.fleetConfig.defaults.backend);
|
|
19630
20610
|
for (const inst of Object.values(this.fleetConfig?.instances ?? {}))
|
|
19631
|
-
if (inst.backend)
|
|
20611
|
+
if (!isWindowsInstance(inst) && inst.backend)
|
|
19632
20612
|
backends.add(inst.backend);
|
|
19633
20613
|
for (const ch of this.classicChannels?.getAll() ?? [])
|
|
19634
20614
|
if (ch.backend)
|
|
@@ -19643,6 +20623,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19643
20623
|
// here; a ClassicBot room on a profile is probed on its first /model (catalogProfile knows rooms too).
|
|
19644
20624
|
const profiles = new Set();
|
|
19645
20625
|
for (const name of Object.keys(this.fleetConfig?.instances ?? {})) {
|
|
20626
|
+
if (isWindowsInstance(this.fleetConfig?.instances[name]))
|
|
20627
|
+
continue;
|
|
19646
20628
|
if (this.backendNameForInstance(name) !== "claude-code")
|
|
19647
20629
|
continue;
|
|
19648
20630
|
const profile = this.catalogProfile("claude-code", name);
|
|
@@ -19663,6 +20645,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19663
20645
|
* instead of presenting an old list as a fresh one.
|
|
19664
20646
|
*/
|
|
19665
20647
|
async getModelOptionsWithSource(instanceName, opts = {}) {
|
|
20648
|
+
if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
|
|
20649
|
+
return { models: (await this.listModelCatalog({ instanceName })).models, source: "cache" };
|
|
19666
20650
|
const backendName = this.backendNameForInstance(instanceName);
|
|
19667
20651
|
const profile = this.catalogProfile(backendName, instanceName);
|
|
19668
20652
|
const cached = this.readCliEnv(backendName, profile);
|
|
@@ -19718,6 +20702,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19718
20702
|
*/
|
|
19719
20703
|
async listModelCatalog(opts = {}) {
|
|
19720
20704
|
const { instanceName } = opts;
|
|
20705
|
+
if (instanceName && isWindowsInstance(this.fleetConfig?.instances[instanceName])) {
|
|
20706
|
+
const config = this.fleetConfig.instances[instanceName], backend = config.backend ?? "codex";
|
|
20707
|
+
// Detection is admin-owned. A read never launches a provider or falls back to this host's account.
|
|
20708
|
+
let inventory = null;
|
|
20709
|
+
try {
|
|
20710
|
+
inventory = await this.runtimeWindowsExecutor().probe({ force: false, machineId: config.machine_id });
|
|
20711
|
+
}
|
|
20712
|
+
catch { /* offline remains unavailable */ }
|
|
20713
|
+
const row = inventory?.clis.find(value => value.id === backend);
|
|
20714
|
+
return { backend, scope: "instance", instance: instanceName, current_model: config.model || null,
|
|
20715
|
+
models: (row?.models ?? []).map(value => ({ id: value.id, label: value.name ?? value.id, ...(value.description ? { description: value.description } : {}) })),
|
|
20716
|
+
source: row?.models?.length ? "cache" : "fallback",
|
|
20717
|
+
...(inventory?.probedAt ? { probed_at: new Date(inventory.probedAt).toISOString() } : {}),
|
|
20718
|
+
note: "Windows runtime's last detected inventory; no coordinator account or live probe is used." };
|
|
20719
|
+
}
|
|
19721
20720
|
if (instanceName) {
|
|
19722
20721
|
const backend = this.backendNameForInstance(instanceName);
|
|
19723
20722
|
const resolved = this.resolveInstanceModel(instanceName);
|
|
@@ -19753,6 +20752,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19753
20752
|
}
|
|
19754
20753
|
/** Ask a backend for its catalog using ONE instance's real config. Never throws. */
|
|
19755
20754
|
async instanceScopedModels(instanceName, backend) {
|
|
20755
|
+
if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
|
|
20756
|
+
return [];
|
|
19756
20757
|
try {
|
|
19757
20758
|
const inst = this.fleetConfig?.instances?.[instanceName];
|
|
19758
20759
|
const instanceDir = this.getInstanceDir(instanceName);
|
|
@@ -20372,6 +21373,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20372
21373
|
}
|
|
20373
21374
|
/** The instance's backend's effort metadata; agy's levels are its model's (#1825), the one the instance runs. */
|
|
20374
21375
|
instanceEffortMetadata(instanceName) {
|
|
21376
|
+
if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
|
|
21377
|
+
return { strategy: "unsupported", levels: [] };
|
|
20375
21378
|
const backend = this.backendNameForInstance(instanceName);
|
|
20376
21379
|
let model = null;
|
|
20377
21380
|
if (backend === "antigravity") {
|
|
@@ -20382,6 +21385,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20382
21385
|
}
|
|
20383
21386
|
/** Configured effort for an instance: per-instance, else fleet default, else none. */
|
|
20384
21387
|
resolveInstanceEffort(instanceName) {
|
|
21388
|
+
if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
|
|
21389
|
+
return { effort: null, source: "unset" };
|
|
20385
21390
|
const own = this.fleetConfig?.instances[instanceName]?.effort;
|
|
20386
21391
|
if (own)
|
|
20387
21392
|
return { effort: own, source: "instance" };
|
|
@@ -20419,6 +21424,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20419
21424
|
* instance respawns.
|
|
20420
21425
|
*/
|
|
20421
21426
|
async applyEffort(instanceName, requested) {
|
|
21427
|
+
if (isWindowsInstance(this.fleetConfig?.instances[instanceName]))
|
|
21428
|
+
return t("effort.unsupported", this.backendNameForInstance(instanceName));
|
|
20422
21429
|
const level = requested.trim().toLowerCase();
|
|
20423
21430
|
const backendName = this.backendNameForInstance(instanceName);
|
|
20424
21431
|
let strategy = "unsupported";
|
|
@@ -20462,6 +21469,20 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20462
21469
|
if (/[\x00-\x1f\x7f]/.test(model)) {
|
|
20463
21470
|
return t("model.invalid_chars");
|
|
20464
21471
|
}
|
|
21472
|
+
const native = this.fleetConfig?.instances[instanceName];
|
|
21473
|
+
if (isWindowsInstance(native)) {
|
|
21474
|
+
if (!isNativePaneInstance(native))
|
|
21475
|
+
return WINDOWS_CONFIG_REFUSED;
|
|
21476
|
+
const catalog = await this.listModelCatalog({ instanceName });
|
|
21477
|
+
if (!catalog.models.some(row => row.id === model))
|
|
21478
|
+
return t("model.unknown_name", model, catalog.models.map(row => row.id).join(", "));
|
|
21479
|
+
const id = native.native_agent_id, machine = native.machine_id;
|
|
21480
|
+
await this.updateNativePaneConfig(instanceName, { model }, () => {
|
|
21481
|
+
const now = this.fleetConfig?.instances[instanceName];
|
|
21482
|
+
return isNativePaneInstance(now) && now.native_agent_id === id && now.machine_id === machine;
|
|
21483
|
+
});
|
|
21484
|
+
return t("model.restart_success", instanceName, model);
|
|
21485
|
+
}
|
|
20465
21486
|
const backendName = this.backendNameForInstance(instanceName);
|
|
20466
21487
|
let strategy = "restart";
|
|
20467
21488
|
try {
|
|
@@ -20648,6 +21669,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20648
21669
|
* profile's, when it is named and has one), claude's account catalog included. Read only: never a probe.
|
|
20649
21670
|
*/
|
|
20650
21671
|
modelCatalogFor(backend, instance) {
|
|
21672
|
+
if (instance && isWindowsInstance(this.fleetConfig?.instances[instance]))
|
|
21673
|
+
return [];
|
|
20651
21674
|
if (!KNOWN_BACKENDS.includes(backend) || backend === "mock")
|
|
20652
21675
|
return [];
|
|
20653
21676
|
const own = instance && Object.prototype.hasOwnProperty.call(this.fleetConfig?.instances ?? {}, instance) ? instance
|
|
@@ -20661,6 +21684,40 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20661
21684
|
}
|
|
20662
21685
|
return out;
|
|
20663
21686
|
}
|
|
21687
|
+
/** Host-default fallback only: Grok has no modeled profile home. Never construct a backend or start a probe. */
|
|
21688
|
+
async cliModelCatalogFor(backend, instance) {
|
|
21689
|
+
if (backend !== "grok")
|
|
21690
|
+
return [];
|
|
21691
|
+
const fleet = this.fleetConfig;
|
|
21692
|
+
const configured = instance && Object.prototype.hasOwnProperty.call(fleet?.instances ?? {}, instance) ? fleet.instances[instance] : undefined;
|
|
21693
|
+
const classic = this.classicChannels;
|
|
21694
|
+
const room = instance && !configured ? classic?.getAll().find(channel => channel.instanceName === instance) : undefined;
|
|
21695
|
+
if (instance && !configured && !room)
|
|
21696
|
+
return [];
|
|
21697
|
+
const daemon = room ? this.daemons.get(instance) : undefined;
|
|
21698
|
+
const grokHome = process.env.GROK_HOME?.trim() || join(homedir(), ".grok");
|
|
21699
|
+
const stillHostDefault = () => {
|
|
21700
|
+
if (this.fleetConfig !== fleet || (process.env.GROK_HOME?.trim() || join(homedir(), ".grok")) !== grokHome)
|
|
21701
|
+
return false;
|
|
21702
|
+
if (configured && (isWindowsInstance(configured) || !Object.prototype.hasOwnProperty.call(fleet?.instances ?? {}, instance) || fleet.instances[instance] !== configured))
|
|
21703
|
+
return false;
|
|
21704
|
+
if (room && (this.classicChannels !== classic || classic?.getAll().find(channel => channel.instanceName === instance) !== room || this.daemons.get(instance) !== daemon))
|
|
21705
|
+
return false;
|
|
21706
|
+
// The ordinary profile helpers deliberately ignore Grok: it has no credential-home mapping yet.
|
|
21707
|
+
// Check raw declarations, including invalid ones, rather than mistaking them for the host login.
|
|
21708
|
+
const defaults = fleet?.defaults?.backend_options;
|
|
21709
|
+
const profiles = configured
|
|
21710
|
+
? [configured.backend_options?.grok?.credential_profile, defaults?.grok?.credential_profile]
|
|
21711
|
+
: room
|
|
21712
|
+
? [mergeBackendOptions(defaults, room.backendOptions)?.grok?.credential_profile, daemon?.getConfigSnapshot?.().backend_options?.grok?.credential_profile]
|
|
21713
|
+
: [defaults?.grok?.credential_profile];
|
|
21714
|
+
return profiles.every(profile => profile === undefined || profile === null || profile === "");
|
|
21715
|
+
};
|
|
21716
|
+
if (!stillHostDefault())
|
|
21717
|
+
return [];
|
|
21718
|
+
const models = await readGrokModelCache(grokHome);
|
|
21719
|
+
return stillHostDefault() ? models : [];
|
|
21720
|
+
}
|
|
20664
21721
|
/**
|
|
20665
21722
|
* #2072 (#2078 review): the snapshot as THIS instance would see it. Its sign-in on a backend is its own credential
|
|
20666
21723
|
* profile's when it has one there — the host's default login says nothing about it. A profile's login is a file check
|
|
@@ -21319,6 +22376,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21319
22376
|
* settles, so a later genuine stop (after a restart) still does the work.
|
|
21320
22377
|
*/
|
|
21321
22378
|
stopAll() {
|
|
22379
|
+
this.lifecycle.cancelNativeRecovery?.();
|
|
22380
|
+
this.nativeMcpStore?.closeAll();
|
|
22381
|
+
this.runtimeExecutor?.close();
|
|
22382
|
+
this.runtimeExecutor = null;
|
|
22383
|
+
this.fleetWindowsRuntime = null;
|
|
22384
|
+
this.runtimeHub?.close();
|
|
22385
|
+
this.runtimeHub = null;
|
|
22386
|
+
this.localPaneRelay?.stop();
|
|
22387
|
+
this.localPaneRelay = null;
|
|
22388
|
+
closeWindowsBridge(this);
|
|
21322
22389
|
this.stopAllInFlight ??= this.doStopAll().finally(() => {
|
|
21323
22390
|
// #1956: after the stop's own tmux work; anything later goes direct. A later startAll starts it again.
|
|
21324
22391
|
if (this.tmuxExecHelperTimer) {
|
|
@@ -21777,6 +22844,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21777
22844
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21778
22845
|
this.loadedFleetSource = previousSource;
|
|
21779
22846
|
this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
|
|
22847
|
+
this.observeRuntimeSwitches(); // M-2255: and the Windows runtimes' switches
|
|
21780
22848
|
this.logger.error({
|
|
21781
22849
|
oldCount,
|
|
21782
22850
|
newCount,
|
|
@@ -23412,7 +24480,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23412
24480
|
const topicMode = daemonTopicMode(fleet);
|
|
23413
24481
|
const restartProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), restartStartedAt, this.logger);
|
|
23414
24482
|
// Phase 1: generals first
|
|
23415
|
-
const restartEntries = Object.entries(fleet.instances);
|
|
24483
|
+
const restartEntries = Object.entries(fleet.instances).filter(([, config]) => !isNativeExecInstance(config));
|
|
23416
24484
|
const restartGenerals = restartEntries.filter(([_, cfg]) => cfg.general_topic);
|
|
23417
24485
|
const restartOthers = restartEntries.filter(([_, cfg]) => !cfg.general_topic);
|
|
23418
24486
|
for (const [name, cfg] of restartGenerals) {
|
|
@@ -23866,6 +24934,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23866
24934
|
return;
|
|
23867
24935
|
}
|
|
23868
24936
|
const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
|
|
24937
|
+
if (remoteRuntimeEnabled() && !gatewayRequestContext(req)
|
|
24938
|
+
&& this.runtimeCoordinator().handlePublicRequest(req, res, new URL(req.url ?? "/", `http://localhost:${port}`)))
|
|
24939
|
+
return;
|
|
23869
24940
|
// Branding is public, after Host admission: automatic favicon requests
|
|
23870
24941
|
// must not become noisy web-token 401s in the browser console.
|
|
23871
24942
|
// #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
|
|
@@ -24249,6 +25320,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24249
25320
|
if (!this.webToken || !this.webSessions)
|
|
24250
25321
|
this.initializeWebAuthTokens();
|
|
24251
25322
|
const server = createServer((req, res) => this.dispatchWebHttp(req, res, port, drain.closing));
|
|
25323
|
+
server.on("upgrade", (req, socket, head) => {
|
|
25324
|
+
if (!this.shuttingDown && remoteRuntimeEnabled() && isHostAllowed(req.headers.host, allowedHostNames(this.fleetConfig))
|
|
25325
|
+
&& this.runtimeCoordinator().handleUpgrade(req, socket, head))
|
|
25326
|
+
return;
|
|
25327
|
+
socket.destroy();
|
|
25328
|
+
});
|
|
24252
25329
|
// #1937: Node's default (300 s to receive a request) would cut a large upload on a slow link. The listener allows
|
|
24253
25330
|
// more; the upload route keeps its own clocks (no byte for a minute, or 30 minutes in all), and every other request
|
|
24254
25331
|
// keeps the old 300 s (limitRequestReceive in dispatchWebHttp).
|
|
@@ -24473,9 +25550,36 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24473
25550
|
this.logger.debug({ err }, "bots unreadable (getUiStatus)");
|
|
24474
25551
|
}
|
|
24475
25552
|
const instances = names.map(name => {
|
|
25553
|
+
const nativeConfig = this.fleetConfig?.instances[name];
|
|
25554
|
+
if (isNativeExecInstance(nativeConfig)) {
|
|
25555
|
+
let runtime = null;
|
|
25556
|
+
let agent = null;
|
|
25557
|
+
try {
|
|
25558
|
+
runtime = this.nativeInstanceRuntime();
|
|
25559
|
+
agent = runtime.get(name);
|
|
25560
|
+
}
|
|
25561
|
+
catch { /* A damaged native profile is a crashed native row, never a Linux spawn. */ }
|
|
25562
|
+
const display_name = nativeConfig.display_name || agent?.name || name;
|
|
25563
|
+
const backend = nativeConfig.backend || agent?.cli || "codex";
|
|
25564
|
+
return { name, display_name, description: nativeConfig.description || undefined, tags: nativeConfig.tags || [],
|
|
25565
|
+
teams: instanceTeams(this.fleetConfig?.teams, name), runtime: "windows", native_agent_id: nativeConfig.native_agent_id,
|
|
25566
|
+
...(nativeConfig.machine_id ? { machine_id: nativeConfig.machine_id, machine_name: this.runtimeHub?.machine(nativeConfig.machine_id)?.name,
|
|
25567
|
+
machine_status: this.runtimeHub?.machine(nativeConfig.machine_id)?.status ?? "offline" } : {}),
|
|
25568
|
+
native_blocked: !agent || agent.blocked === true,
|
|
25569
|
+
native_busy: runtime?.busy(name) ?? false,
|
|
25570
|
+
backend,
|
|
25571
|
+
model: nativeConfig.model || "default", model_source: nativeConfig.model ? "instance" : "cli-default",
|
|
25572
|
+
status: agent && runtime ? runtime.status(name) : "crashed", state: agent && runtime ? runtime.execution(name) : null, execution_state: agent && runtime ? runtime.execution(name) : null,
|
|
25573
|
+
interaction: null, interaction_summary: null, context_pct: null, cost: null,
|
|
25574
|
+
last_reply: this.webChatHistory.lastAgentReply(name) ?? undefined,
|
|
25575
|
+
avatar: avatarFor({ name, displayName: display_name }),
|
|
25576
|
+
capabilities: { chat: true, settings: true, pause: true, cancel: true, teams: true, schedules: true, delegation: true, mcp: nativeProviderMcpSupported(backend), channels: false, terminal: false, attachments: false, usage: false, send_while_busy: false },
|
|
25577
|
+
...(!agent ? { runtime_error: "Windows profile is missing or invalid; restore its saved conversation before using this instance" }
|
|
25578
|
+
: agent.blocked ? { runtime_error: "Windows process outcome is unknown; verify it stopped before recovery" } : {}) };
|
|
25579
|
+
}
|
|
24476
25580
|
// #1956: read once for this instance; its cost, context and model come from it.
|
|
24477
|
-
const statusline = readStatuslineJson(this.dataDir, name);
|
|
24478
|
-
const cost = statusline?.cost?.total_cost_usd ?? 0;
|
|
25581
|
+
const statusline = isNativePaneInstance(nativeConfig) ? undefined : readStatuslineJson(this.dataDir, name);
|
|
25582
|
+
const cost = isNativePaneInstance(nativeConfig) ? null : statusline?.cost?.total_cost_usd ?? 0;
|
|
24479
25583
|
// Align with /ctx: statusline for claude-code, pane scrape for kiro/grok/codex.
|
|
24480
25584
|
const classic = classicOnly.includes(name);
|
|
24481
25585
|
const backend = classic
|
|
@@ -24483,7 +25587,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24483
25587
|
: (this.fleetConfig?.instances[name]?.backend
|
|
24484
25588
|
?? this.fleetConfig?.defaults?.backend
|
|
24485
25589
|
?? "claude-code");
|
|
24486
|
-
const { context } = resolveInstanceContext(this.dataDir, name, backend, { source: this.getPaneContextSource(name), statusline });
|
|
25590
|
+
const { context } = isNativePaneInstance(nativeConfig) ? { context: null } : resolveInstanceContext(this.dataDir, name, backend, { source: this.getPaneContextSource(name), statusline });
|
|
24487
25591
|
// context_pct: null when unavailable, not 0
|
|
24488
25592
|
const context_pct = context ?? null;
|
|
24489
25593
|
// Model: Only Claude Code has live statusline; others use the effective resolver.
|
|
@@ -24514,6 +25618,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24514
25618
|
}
|
|
24515
25619
|
return {
|
|
24516
25620
|
name,
|
|
25621
|
+
...(isNativePaneInstance(nativeConfig) ? { runtime: "windows", native_adapter: "pane", native_agent_id: nativeConfig.native_agent_id,
|
|
25622
|
+
...(() => {
|
|
25623
|
+
try {
|
|
25624
|
+
const profile = this.nativeInstanceRuntime().get(name), bound = this.daemons.get(name);
|
|
25625
|
+
return { native_recovery: profile?.paneRecovery ?? (profile?.paneLaunchId && bound?.usesNativePane?.() && !bound.nativePaneCurrent()
|
|
25626
|
+
? { kind: "exit-unconfirmed", launchId: profile.paneLaunchId } : null) };
|
|
25627
|
+
}
|
|
25628
|
+
catch {
|
|
25629
|
+
return { native_recovery: null };
|
|
25630
|
+
}
|
|
25631
|
+
})(),
|
|
25632
|
+
capabilities: { chat: true, settings: true, pause: true, cancel: true, teams: true, schedules: true, delegation: true, mcp: true,
|
|
25633
|
+
commands: true, effort: false, log: true, details: true, channels: false, terminal: false, attachments: false, usage: false, send_while_busy: true },
|
|
25634
|
+
...(nativeConfig.machine_id ? { machine_id: nativeConfig.machine_id, machine_name: this.runtimeHub?.machine(nativeConfig.machine_id)?.name,
|
|
25635
|
+
machine_status: this.runtimeHub?.machine(nativeConfig.machine_id)?.status ?? "offline" } : {}) } : {}),
|
|
24517
25636
|
display_name: identity.display_name || undefined,
|
|
24518
25637
|
avatar: image.avatar,
|
|
24519
25638
|
...(image.accent ? { accent: image.accent } : {}),
|
|
@@ -24565,6 +25684,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24565
25684
|
uptime: Math.floor((Date.now() - this.startedAt) / 1000),
|
|
24566
25685
|
// #1519 P7: how many chat connections are configured — 0 is a web-only fleet, which gets the first-run card.
|
|
24567
25686
|
connections: (this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : [])).length,
|
|
25687
|
+
windows_cli: localWindowsCliEnabled(),
|
|
24568
25688
|
// #1665: the host preferences' revision — a page that has another re-reads /ui/prefs (no values in this frame).
|
|
24569
25689
|
prefs_rev: this.webPrefs.rev(),
|
|
24570
25690
|
// #1629: the themes' revision — the page re-reads GET /ui/themes when it moves.
|
|
@@ -24574,6 +25694,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24574
25694
|
host_memory: this.hostMemoryIndicator(),
|
|
24575
25695
|
// #2910 D2: a web-only fleet's newest Notice — a polling page (no SSE `notice`) reads the list again when it moves.
|
|
24576
25696
|
...(this.webOnlyPromptPlace() ? { notices_newest: this.webNoticeStore()?.newest() ?? 0 } : {}),
|
|
25697
|
+
// alpha.2 (M-2300, #2997 review): agents' requests changed — the owner's page reads GET /ui/agent-proposals again.
|
|
25698
|
+
...(this.isWebOnlyFleet() ? { agent_proposals_rev: this.agentProposalsRev } : {}),
|
|
24577
25699
|
// #2015: the largest processes while it lasts (name, pid, RSS, and whose: an instance, the fleet or tmux).
|
|
24578
25700
|
...(this.hostMemoryIndicator() && this.memoryTop ? { host_memory_top: this.memoryTop.processes } : {}),
|
|
24579
25701
|
};
|
|
@@ -25016,4 +26138,12 @@ load();
|
|
|
25016
26138
|
</script>
|
|
25017
26139
|
</body>
|
|
25018
26140
|
</html>`;
|
|
26141
|
+
/** alpha.2 (M-2300): the agent an applied Windows request created (its name, for the page's "Open chat"), or null. */
|
|
26142
|
+
export function agentNameOf(view) {
|
|
26143
|
+
const r = view.state === "applied" ? view.outcome?.result : null;
|
|
26144
|
+
if (!r || typeof r !== "object")
|
|
26145
|
+
return null;
|
|
26146
|
+
const o = r, name = o.name ?? o.instance_name ?? (o.agent && typeof o.agent === "object" ? o.agent.name : undefined);
|
|
26147
|
+
return typeof name === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name) ? name : null;
|
|
26148
|
+
}
|
|
25019
26149
|
//# sourceMappingURL=fleet-manager.js.map
|