@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.70
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 +279 -68
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +26 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +19 -0
- package/dist/agent-ours/mcp-endpoint.js +98 -25
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +24 -1
- package/dist/agent-ours/service.js +97 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +27 -0
- package/dist/application/conversation-history.js +215 -0
- package/dist/application/conversation-role-observer.d.ts +62 -0
- package/dist/application/conversation-role-observer.js +206 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.js +19 -2
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +61 -17
- package/dist/application/session-control.js +207 -23
- package/dist/application/supervisor-ours-tools.js +5 -11
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +259 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +285 -32
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +34 -1
- package/dist/atomic-file.js +161 -18
- package/dist/briefing.d.ts +3 -1
- package/dist/briefing.js +97 -31
- package/dist/build-info.json +9 -4
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +400 -37
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +207 -19
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +24 -8
- package/dist/harness/agent-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +21 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +24 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +8 -34
- package/dist/monitor.js +89 -152
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +10 -0
- package/dist/notifications/target-cleanup.js +63 -0
- package/dist/notifications/task-producer.d.ts +18 -0
- package/dist/notifications/task-producer.js +67 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +43 -0
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.js +42 -0
- package/dist/reminders/manager.d.ts +75 -0
- package/dist/reminders/manager.js +278 -0
- package/dist/reminders/runtime.d.ts +27 -0
- package/dist/reminders/runtime.js +32 -0
- package/dist/reminders/schedule.d.ts +37 -0
- package/dist/reminders/schedule.js +104 -0
- package/dist/reminders/store.d.ts +341 -0
- package/dist/reminders/store.js +762 -0
- package/dist/reminders/tools.d.ts +20 -0
- package/dist/reminders/tools.js +114 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +6 -2
- package/dist/rooms-tasks/close.js +246 -37
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
- package/dist/rooms-tasks/cowork-adapter.js +15 -121
- package/dist/rooms-tasks/cowork-http.js +56 -6
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +84 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +285 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -8
- package/dist/rooms-tasks/task-state.js +245 -25
- package/dist/rooms-tasks/types.d.ts +40 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +33 -4
- package/dist/runner.js +389 -56
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +213 -23
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-observation.d.ts +25 -0
- package/dist/session/conversation-observation.js +100 -0
- package/dist/session/conversation-replay.d.ts +50 -0
- package/dist/session/conversation-replay.js +203 -0
- package/dist/session/conversation-store.d.ts +15 -1
- package/dist/session/conversation-store.js +206 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +32 -0
- package/dist/startup-readiness.js +101 -0
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +76 -0
- package/dist/subscriptions/login.js +207 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +19 -33
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +65 -2
- package/dist/web/auth.js +201 -11
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/conversation-stream-codec.d.ts +23 -0
- package/dist/web/conversation-stream-codec.js +110 -0
- package/dist/web/conversation-stream.d.ts +25 -0
- package/dist/web/conversation-stream.js +231 -0
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +238 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +128 -33
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +639 -55
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +83 -0
- package/dist/workspace-enrollment.js +580 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +190 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/self-reminders.md +374 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +24 -20
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +12 -11
- package/presets/fleet/roles/Critic.yaml +13 -0
- package/presets/fleet/roles/Developer.yaml +13 -0
- package/presets/fleet/roles/Engineer.yaml +53 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed client for the ours-cowork v1
|
|
2
|
+
* Typed client for the ours-cowork v1 gateway management protocol.
|
|
3
3
|
*
|
|
4
4
|
* Cowork owns room state. Fleet only invokes the versioned JSON RPC exposed
|
|
5
|
-
*
|
|
5
|
+
* through the shared HTTP gateway and projects the response into orchestration data.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import { join, resolve } from 'node:path';
|
|
10
|
-
import { createConnection } from 'node:net';
|
|
11
7
|
import { readClientProfile } from '../client-profile.js';
|
|
12
8
|
import { coworkHttpCall } from './cowork-http.js';
|
|
13
|
-
const RPC_VERSION = 1;
|
|
14
9
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
|
-
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
16
10
|
export class CoworkUnavailableError extends Error {
|
|
17
|
-
|
|
11
|
+
/**
|
|
12
|
+
* True only when the failure is a dependency that may still be starting
|
|
13
|
+
* (connection refused/reset, timeout, a gateway 502/503/504 without an RPC
|
|
14
|
+
* body). Selection, capability, credential and permission failures are
|
|
15
|
+
* never retryable, whatever transport reported them.
|
|
16
|
+
*/
|
|
17
|
+
retryable;
|
|
18
|
+
constructor(message = 'Cowork gateway management is not reachable', options) {
|
|
18
19
|
super(message, options);
|
|
19
20
|
this.name = 'CoworkUnavailableError';
|
|
21
|
+
this.retryable = options?.retryable === true;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
export class CoworkProtocolError extends Error {
|
|
@@ -171,121 +173,13 @@ function projectHistoryRecord(value) {
|
|
|
171
173
|
}
|
|
172
174
|
return undefined;
|
|
173
175
|
}
|
|
174
|
-
/** Resolve the same config/state inputs as ours-cowork and return its Unix socket. */
|
|
175
|
-
export function resolveCoworkSocketPath(options = {}) {
|
|
176
|
-
if (options.socketPath)
|
|
177
|
-
return resolve(options.socketPath);
|
|
178
|
-
const env = options.env ?? process.env;
|
|
179
|
-
const home = options.home ?? homedir();
|
|
180
|
-
const configuredPath = options.configPath ?? env.OURS_COWORK_CONFIG;
|
|
181
|
-
const configPath = resolve(configuredPath ?? join(home, '.ours-cowork', 'config.json'));
|
|
182
|
-
let stateDir = resolve(home, '.ours-cowork');
|
|
183
|
-
if (existsSync(configPath)) {
|
|
184
|
-
let parsed;
|
|
185
|
-
try {
|
|
186
|
-
parsed = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
throw new CoworkProtocolError('config', `malformed config at ${configPath}`, 'invalid_config', { cause: error });
|
|
190
|
-
}
|
|
191
|
-
const config = object(parsed, 'config', 'config');
|
|
192
|
-
if (config.version !== 1 || typeof config.stateDir !== 'string' || !config.stateDir)
|
|
193
|
-
throw new CoworkProtocolError('config', `invalid Cowork v1 config at ${configPath}`, 'invalid_config');
|
|
194
|
-
stateDir = resolve(config.stateDir);
|
|
195
|
-
}
|
|
196
|
-
else if (configuredPath !== undefined) {
|
|
197
|
-
throw new CoworkProtocolError('config', `configured Cowork config does not exist: ${configPath}`, 'invalid_config');
|
|
198
|
-
}
|
|
199
|
-
if (env.OURS_COWORK_STATE_DIR)
|
|
200
|
-
stateDir = resolve(env.OURS_COWORK_STATE_DIR);
|
|
201
|
-
return join(stateDir, 'management.sock');
|
|
202
|
-
}
|
|
203
|
-
function rpcCall(socketPath, method, params, timeoutMs, connect) {
|
|
204
|
-
const id = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
205
|
-
const request = `${JSON.stringify({ version: RPC_VERSION, id, method, params })}\n`;
|
|
206
|
-
return new Promise((resolveCall, rejectCall) => {
|
|
207
|
-
const socket = connect(socketPath);
|
|
208
|
-
let bytes = '';
|
|
209
|
-
let size = 0;
|
|
210
|
-
let settled = false;
|
|
211
|
-
let connected = false;
|
|
212
|
-
const finishError = (error) => {
|
|
213
|
-
if (settled)
|
|
214
|
-
return;
|
|
215
|
-
settled = true;
|
|
216
|
-
clearTimeout(timer);
|
|
217
|
-
socket.destroy();
|
|
218
|
-
rejectCall(error);
|
|
219
|
-
};
|
|
220
|
-
const timer = setTimeout(() => finishError(new CoworkUnavailableError(connected ? 'Cowork did not answer the management socket' : 'Cowork management socket is not reachable')), timeoutMs);
|
|
221
|
-
socket.setEncoding('utf8');
|
|
222
|
-
socket.once('connect', () => {
|
|
223
|
-
connected = true;
|
|
224
|
-
socket.write(request);
|
|
225
|
-
});
|
|
226
|
-
socket.on('data', (chunk) => {
|
|
227
|
-
if (settled)
|
|
228
|
-
return;
|
|
229
|
-
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
230
|
-
size += Buffer.byteLength(text);
|
|
231
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
232
|
-
finishError(new CoworkProtocolError(method, 'response exceeded 4 MiB'));
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
bytes += text;
|
|
236
|
-
const newline = bytes.indexOf('\n');
|
|
237
|
-
if (newline < 0)
|
|
238
|
-
return;
|
|
239
|
-
if (bytes.slice(newline + 1).trim() !== '') {
|
|
240
|
-
finishError(new CoworkProtocolError(method, 'daemon returned more than one response'));
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
let response;
|
|
244
|
-
try {
|
|
245
|
-
response = object(JSON.parse(bytes.slice(0, newline)), method, 'RPC response');
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
finishError(error instanceof CoworkProtocolError
|
|
249
|
-
? error : new CoworkProtocolError(method, 'daemon returned malformed JSON', 'protocol_error', { cause: error }));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (response.version !== RPC_VERSION || response.id !== id
|
|
253
|
-
|| (Object.hasOwn(response, 'error') === Object.hasOwn(response, 'result'))) {
|
|
254
|
-
finishError(new CoworkProtocolError(method, 'daemon returned an invalid RPC response'));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
settled = true;
|
|
258
|
-
clearTimeout(timer);
|
|
259
|
-
socket.destroy();
|
|
260
|
-
if (Object.hasOwn(response, 'error')) {
|
|
261
|
-
const error = object(response.error, method, 'RPC error');
|
|
262
|
-
rejectCall(new CoworkProtocolError(method, typeof error.message === 'string' ? error.message : 'unknown RPC error', typeof error.code === 'string' ? error.code : 'rpc_error'));
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
resolveCall(response.result);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
socket.once('end', () => {
|
|
269
|
-
if (!settled)
|
|
270
|
-
finishError(new CoworkProtocolError(method, 'daemon closed without a complete response'));
|
|
271
|
-
});
|
|
272
|
-
socket.once('error', (error) => {
|
|
273
|
-
if (!settled)
|
|
274
|
-
finishError(new CoworkUnavailableError(error.code === 'EACCES' || error.code === 'EPERM'
|
|
275
|
-
? 'Cowork management socket access denied' : 'Cowork management socket is not reachable', { cause: error }));
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
176
|
export function createCoworkAdapter(options = {}) {
|
|
280
177
|
const env = options.env ?? process.env;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const profile =
|
|
284
|
-
const remote = profile?.serverUrl ? profile : undefined;
|
|
285
|
-
const socketPath = remote ? undefined : resolveCoworkSocketPath(options);
|
|
178
|
+
if (options.socketPath !== undefined || options.configPath !== undefined)
|
|
179
|
+
throw new CoworkProtocolError('config', 'Local Cowork selectors are unsupported; configure the shared gateway client profile', 'invalid_config');
|
|
180
|
+
const profile = readClientProfile({ ...env, HOME: options.home ?? env.HOME });
|
|
286
181
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
287
|
-
const
|
|
288
|
-
const call = (method, params) => remote ? coworkHttpCall(remote, method, params, timeoutMs) : rpcCall(socketPath, method, params, timeoutMs, connect);
|
|
182
|
+
const call = (method, params) => coworkHttpCall(profile, method, params, timeoutMs);
|
|
289
183
|
return {
|
|
290
184
|
async available() {
|
|
291
185
|
try {
|
|
@@ -2,6 +2,39 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { attachOursClient } from '@ours.network/sdk/client';
|
|
3
3
|
import { readPrivateFile } from '@ours.network/sdk/connector';
|
|
4
4
|
import { CoworkProtocolError, CoworkUnavailableError } from './cowork-adapter.js';
|
|
5
|
+
const TRANSPORT_CODES = new Set(['EACCES', 'ENOENT', 'ECONNREFUSED', 'ECONNRESET',
|
|
6
|
+
'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET',
|
|
7
|
+
'CERT_HAS_EXPIRED', 'ERR_TLS_CERT_ALTNAME_INVALID']);
|
|
8
|
+
/** Failure kinds a dependency still starting (or restarting) produces. */
|
|
9
|
+
const RETRYABLE_KINDS = new Set(['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN',
|
|
10
|
+
'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_SOCKET', 'timeout']);
|
|
11
|
+
/** Statuses a reverse proxy answers with while its upstream is not serving. */
|
|
12
|
+
const GATEWAY_UNAVAILABLE = new Set([502, 503, 504]);
|
|
13
|
+
/** Media type only, never parameters; anything unusual collapses to a fixed label. */
|
|
14
|
+
function safeContentType(response) {
|
|
15
|
+
const value = (response.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
|
|
16
|
+
return /^[a-z0-9.+-]{1,40}\/[a-z0-9.+-]{1,40}$/.test(value) ? value : 'unknown type';
|
|
17
|
+
}
|
|
18
|
+
function safeFailureKind(error, signal) {
|
|
19
|
+
if (signal.aborted)
|
|
20
|
+
return 'timeout';
|
|
21
|
+
for (const value of [error, error instanceof Error ? error.cause : undefined]) {
|
|
22
|
+
if (!value || typeof value !== 'object')
|
|
23
|
+
continue;
|
|
24
|
+
const code = value.code;
|
|
25
|
+
if (typeof code === 'string' && TRANSPORT_CODES.has(code))
|
|
26
|
+
return code;
|
|
27
|
+
const name = value.name;
|
|
28
|
+
if (name === 'TimeoutError')
|
|
29
|
+
return 'timeout';
|
|
30
|
+
if (name === 'AbortError')
|
|
31
|
+
return 'aborted';
|
|
32
|
+
}
|
|
33
|
+
return 'transport_error';
|
|
34
|
+
}
|
|
35
|
+
function safeOperation(method) {
|
|
36
|
+
return /^[a-z][a-z0-9_.-]{0,79}$/.test(method) ? method : 'management';
|
|
37
|
+
}
|
|
5
38
|
/** Same RPC protocol as the local socket; never replay a possibly delivered mutation. */
|
|
6
39
|
export async function coworkHttpCall(profile, method, params, timeoutMs) {
|
|
7
40
|
const id = randomUUID();
|
|
@@ -9,11 +42,15 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
|
|
|
9
42
|
const body = JSON.stringify({ version: 1, id, method, params });
|
|
10
43
|
if (Buffer.byteLength(body) > 1024 * 1024)
|
|
11
44
|
throw new CoworkProtocolError(method, 'request exceeded 1 MiB');
|
|
45
|
+
let stage = 'server verification';
|
|
12
46
|
try {
|
|
13
47
|
const client = await attachOursClient({ endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
|
|
14
48
|
credentialPath: profile.credentialPath, sessionMode: 'external', leaseToken: `fleet-cowork-check-${id}`, env: {}, requestSignal: signal });
|
|
49
|
+
stage = 'verification cleanup';
|
|
15
50
|
await client.close();
|
|
51
|
+
stage = 'credential read';
|
|
16
52
|
const token = readPrivateFile(profile.credentialPath, 4096).toString('utf8').trim();
|
|
53
|
+
stage = 'management request';
|
|
17
54
|
const response = await fetch(profile.serverUrl + '/cowork/management/rpc', {
|
|
18
55
|
method: 'POST', redirect: 'error', signal,
|
|
19
56
|
headers: { 'content-type': 'application/json', 'x-ours-api-token': token },
|
|
@@ -26,6 +63,7 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
|
|
|
26
63
|
const reader = response.body?.getReader();
|
|
27
64
|
if (!reader)
|
|
28
65
|
throw new CoworkProtocolError(method, 'empty HTTP response');
|
|
66
|
+
stage = 'management response';
|
|
29
67
|
const chunks = [];
|
|
30
68
|
let size = 0;
|
|
31
69
|
try {
|
|
@@ -42,16 +80,25 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
|
|
|
42
80
|
finally {
|
|
43
81
|
await reader.cancel();
|
|
44
82
|
}
|
|
83
|
+
// Status and media type, never the body: a proxy page may echo request data.
|
|
84
|
+
const shape = `HTTP ${response.status}, ${safeContentType(response)}`;
|
|
45
85
|
let rpc;
|
|
86
|
+
let malformed = false;
|
|
46
87
|
try {
|
|
47
88
|
rpc = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
48
89
|
}
|
|
49
90
|
catch {
|
|
50
|
-
|
|
91
|
+
malformed = true;
|
|
92
|
+
}
|
|
93
|
+
if (malformed || !rpc || Array.isArray(rpc) || rpc.version !== 1 || rpc.id !== id
|
|
94
|
+
|| Object.hasOwn(rpc, 'result') === Object.hasOwn(rpc, 'error')) {
|
|
95
|
+
// A gateway whose Cowork upstream is still starting answers with its own
|
|
96
|
+
// error page. That is unavailability, not a Cowork protocol violation; a
|
|
97
|
+
// well-formed RPC error under the same status keeps its meaning below.
|
|
98
|
+
if (GATEWAY_UNAVAILABLE.has(response.status))
|
|
99
|
+
throw new CoworkUnavailableError(`Cowork HTTP ${safeOperation(method)} failed: gateway answered ${shape} without an RPC response`, { retryable: true });
|
|
100
|
+
throw new CoworkProtocolError(method, `server returned ${malformed ? 'malformed JSON' : 'an invalid RPC response'} (${shape})`);
|
|
51
101
|
}
|
|
52
|
-
if (!rpc || Array.isArray(rpc) || rpc.version !== 1 || rpc.id !== id
|
|
53
|
-
|| Object.hasOwn(rpc, 'result') === Object.hasOwn(rpc, 'error'))
|
|
54
|
-
throw new CoworkProtocolError(method, 'server returned an invalid RPC response');
|
|
55
102
|
if (Object.hasOwn(rpc, 'error')) {
|
|
56
103
|
const error = rpc.error;
|
|
57
104
|
if (!error || typeof error.code !== 'string' || typeof error.message !== 'string')
|
|
@@ -63,8 +110,11 @@ export async function coworkHttpCall(profile, method, params, timeoutMs) {
|
|
|
63
110
|
return rpc.result;
|
|
64
111
|
}
|
|
65
112
|
catch (error) {
|
|
66
|
-
if (error instanceof CoworkProtocolError)
|
|
113
|
+
if (error instanceof CoworkProtocolError || error instanceof CoworkUnavailableError)
|
|
67
114
|
throw error;
|
|
68
|
-
|
|
115
|
+
// Durable cleanup records retain only .message. Keep the RPC step and a
|
|
116
|
+
// bounded allowlisted category there, never raw URLs, credentials or cause text.
|
|
117
|
+
const kind = safeFailureKind(error, signal);
|
|
118
|
+
throw new CoworkUnavailableError(`Cowork HTTP ${safeOperation(method)} failed during ${stage} (${kind})`, { cause: error, retryable: stage !== 'credential read' && RETRYABLE_KINDS.has(kind) });
|
|
69
119
|
}
|
|
70
120
|
}
|
|
@@ -3,7 +3,7 @@ import { type CoworkAdapter } from './cowork-adapter.js';
|
|
|
3
3
|
import { type TaskDeletionAcceptance } from './task-state.js';
|
|
4
4
|
import type { TaskDeletionActor, TaskRecord } from './types.js';
|
|
5
5
|
export { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
|
|
6
|
-
type DeletionCowork = Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'
|
|
6
|
+
type DeletionCowork = Pick<CoworkAdapter, 'closeRoom' | 'deleteRoom'> & Partial<Pick<CoworkAdapter, 'getRoom'>>;
|
|
7
7
|
export interface TaskDeletionSettleDeps {
|
|
8
8
|
roomClose?: RoomCloseDeps;
|
|
9
9
|
/** Test seam for the temp-state existence proof. */
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
import { eraseMemberArtifacts } from './erasure.js';
|
|
2
|
+
import { assertMemberNotPermanent } from './member-ownership.js';
|
|
3
|
+
import { retireTaskMemberService } from './task-service-retirement.js';
|
|
4
|
+
import { assertTaskRegistrationsAbsent } from '../supervisor/catalog.js';
|
|
5
|
+
import { proveArchivedAbsence, verifyArchivedAbsence, verifyArchivedMemberStillAbsent } from './archived-absence.js';
|
|
1
6
|
import { existsSync } from 'node:fs';
|
|
2
7
|
import { withFileLock } from '../atomic-file.js';
|
|
3
8
|
import { agentDir } from '../paths.js';
|
|
9
|
+
import { deleteWorkspace, assertWorkspaceDeletable } from './workspace.js';
|
|
10
|
+
import { collectWorkspaceArchives } from './workspace-artifacts.js';
|
|
4
11
|
import { secureStoppedTempArchive, stopTempSupervisor } from '../temp-lifecycle.js';
|
|
5
|
-
import { closeManagedRoom, identityCidPresent, inspectMember, removeExactMemberIdentity, waitForLivenessAbsent, } from './close.js';
|
|
12
|
+
import { closeManagedRoom, identityCidPresent, inspectMember, assertMemberIdentityAbsent, removeExactMemberIdentity, waitForLivenessAbsent, } from './close.js';
|
|
6
13
|
import { CoworkProtocolError } from './cowork-adapter.js';
|
|
7
14
|
import { deleteRoomRecord, getRoomRecord, listRoomRecords } from './room-state.js';
|
|
8
15
|
import { acquireLaunchSnapshotLock, releaseLaunchSnapshotForDeletingTask } from './launch-snapshot.js';
|
|
9
16
|
import { TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath } from './terminal.js';
|
|
10
|
-
import { advanceTaskDeletionMember, beginTaskDeletionIntent, completeTaskDeletionReceipt, ensureTaskDeletionReceipt, getDeletingTask, importTaskDeletionRetirementEvidence, setTaskDeletionError, TaskStateError, unlinkDeletedTask, upsertTaskDeletionMembersFromSeats, } from './task-state.js';
|
|
17
|
+
import { advanceTaskDeletionMember, settleAbsentTaskDeletionMember, beginTaskDeletionIntent, completeTaskDeletionReceipt, ensureTaskDeletionReceipt, getDeletingTask, importTaskDeletionRetirementEvidence, setTaskDeletionError, TaskStateError, beginTaskArchiveCleanup, unlinkDeletedTask, upsertTaskDeletionMembersFromSeats, } from './task-state.js';
|
|
11
18
|
export { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
|
|
12
19
|
import { DELETION_MEMBER_ABSENT_VERIFIED } from './task-state.js';
|
|
13
20
|
/**
|
|
@@ -40,6 +47,7 @@ function tolerantCowork(cowork) {
|
|
|
40
47
|
}
|
|
41
48
|
};
|
|
42
49
|
return {
|
|
50
|
+
...(cowork.getRoom ? { getRoom: cowork.getRoom.bind(cowork) } : {}),
|
|
43
51
|
closeRoom: roomId => tolerate(() => cowork.closeRoom(roomId)),
|
|
44
52
|
deleteRoom: roomId => tolerate(() => cowork.deleteRoom(roomId)),
|
|
45
53
|
};
|
|
@@ -48,6 +56,7 @@ function cursorSeat(cursor) {
|
|
|
48
56
|
return {
|
|
49
57
|
role_name: cursor.name, identity_cid: cursor.identity_cid,
|
|
50
58
|
slot: cursor.name, cowork_role: 'member', seat_state: 'active',
|
|
59
|
+
...(cursor.launch_id ? { retirement: { phase: 'identity_absent', launch_id: cursor.launch_id, archive_path: cursor.archive_path, updated_at: cursor.updated_at } } : {}),
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
62
|
/**
|
|
@@ -58,10 +67,23 @@ function cursorSeat(cursor) {
|
|
|
58
67
|
* the recorded CID surviving under any name blocks settlement.
|
|
59
68
|
*/
|
|
60
69
|
async function retireCursorMember(taskId, cursor, deps) {
|
|
70
|
+
assertMemberNotPermanent(cursor.name);
|
|
71
|
+
await (deps.roomClose?.retireTaskService ?? retireTaskMemberService)(cursor.name, { taskId,
|
|
72
|
+
creationActionId: cursor.action_id,
|
|
73
|
+
taskSupervised: cursor.task_supervised,
|
|
74
|
+
...((cursor.launch_id && !['never-launched', 'absent-verified'].includes(cursor.launch_id)) ? { launchId: cursor.launch_id } : {}),
|
|
75
|
+
});
|
|
61
76
|
let phase = cursor.phase;
|
|
62
77
|
let launchId = cursor.launch_id;
|
|
63
|
-
if (phase === 'identity_absent')
|
|
78
|
+
if (phase === 'identity_absent') {
|
|
79
|
+
if (!deps.roomClose?.inspectMember) {
|
|
80
|
+
if ((deps.hasTempState ?? (name => existsSync(agentDir(name, true))))(cursor.name))
|
|
81
|
+
throw new Error('Retired deletion member has replacement live state');
|
|
82
|
+
if (await (deps.identityCidPresent ?? identityCidPresent)(cursor.identity_cid))
|
|
83
|
+
throw new Error('Retired deletion member identity reappeared');
|
|
84
|
+
}
|
|
64
85
|
return;
|
|
86
|
+
}
|
|
65
87
|
const seat = cursorSeat(cursor);
|
|
66
88
|
const roomClose = deps.roomClose ?? {};
|
|
67
89
|
if (phase === 'pending') {
|
|
@@ -85,7 +107,16 @@ async function retireCursorMember(taskId, cursor, deps) {
|
|
|
85
107
|
phase = 'stop_requested';
|
|
86
108
|
launchId = ownership.launchId;
|
|
87
109
|
}
|
|
110
|
+
if (!roomClose.inspectMember && !existsSync(agentDir(cursor.name, true))) {
|
|
111
|
+
await (roomClose.removeIdentity ?? removeExactMemberIdentity)(seat);
|
|
112
|
+
if (existsSync(agentDir(cursor.name, true)))
|
|
113
|
+
throw new Error('Deletion member acquired replacement state');
|
|
114
|
+
settleAbsentTaskDeletionMember(taskId, cursor.name);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
88
117
|
if (phase === 'stop_requested') {
|
|
118
|
+
if (!roomClose.inspectMember && (await inspectMember(seat)).launchId !== launchId)
|
|
119
|
+
throw new Error('Deletion member launch changed before stop');
|
|
89
120
|
await (roomClose.requestStop
|
|
90
121
|
?? (async (role) => { await stopTempSupervisor(role); }))(cursor.name);
|
|
91
122
|
await (roomClose.waitForLivenessAbsent ?? waitForLivenessAbsent)(cursor.name, launchId);
|
|
@@ -145,10 +176,40 @@ export async function settleTaskDeletion(input) {
|
|
|
145
176
|
upsertTaskDeletionMembersFromSeats(taskId, record.member_seats);
|
|
146
177
|
await closeManagedRoom({ roomId: record.room_id, cowork: cowork, deps: deps.roomClose });
|
|
147
178
|
const closed = getRoomRecord(record.room_id);
|
|
148
|
-
|
|
179
|
+
const seats = closed?.member_seats ?? record.member_seats;
|
|
180
|
+
const proofs = [];
|
|
181
|
+
for (const seat of seats) {
|
|
182
|
+
if (!seat.identity_cid && !seat.retirement?.absence_verified && !['never-launched', 'absent-verified'].includes(seat.retirement?.launch_id ?? '')) {
|
|
183
|
+
const prior = task.deletion.archived_absences?.find(proof => proof.name === seat.role_name);
|
|
184
|
+
if (task.deletion.workspace_cleanup_started_at && prior && !existsSync(prior.archive_path)) {
|
|
185
|
+
await verifyArchivedMemberStillAbsent(prior);
|
|
186
|
+
proofs.push(prior);
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
proofs.push(await proveArchivedAbsence(seat));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// No await between the last live-state fence and durable checkpoint.
|
|
193
|
+
for (const proof of proofs) {
|
|
194
|
+
const latest = getRoomRecord(record.room_id)?.member_seats.find(seat => seat.role_name === proof.name);
|
|
195
|
+
if (existsSync(agentDir(proof.name, true)) || latest?.identity_cid
|
|
196
|
+
|| latest?.launch?.launch_id !== proof.launch_id || latest.launch.action_id !== proof.action_id)
|
|
197
|
+
throw new Error('Archived absence seat changed before checkpoint');
|
|
198
|
+
}
|
|
199
|
+
importTaskDeletionRetirementEvidence(taskId, seats, proofs);
|
|
149
200
|
await cowork.deleteRoom(record.room_id);
|
|
201
|
+
for (const proof of proofs)
|
|
202
|
+
await (task.deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
|
|
203
|
+
beginTaskArchiveCleanup(taskId);
|
|
204
|
+
await eraseMemberArtifacts('room', record.room_id, seats, [record.room_id]);
|
|
205
|
+
for (const proof of proofs)
|
|
206
|
+
if (existsSync(agentDir(proof.name, true)))
|
|
207
|
+
throw new Error('Archived member replacement before room unlink');
|
|
150
208
|
deleteRoomRecord(record.room_id);
|
|
151
209
|
}
|
|
210
|
+
const deletion = getDeletingTask(taskId).deletion;
|
|
211
|
+
for (const proof of deletion.archived_absences ?? [])
|
|
212
|
+
await (deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
|
|
152
213
|
// Members whose room record is gone (crash after record deletion, or
|
|
153
214
|
// legacy state): resume from the durable cursors.
|
|
154
215
|
for (const cursor of getDeletingTask(taskId).deletion.members) {
|
|
@@ -176,7 +237,7 @@ export async function settleTaskDeletion(input) {
|
|
|
176
237
|
completeTaskDeletionReceipt(taskId);
|
|
177
238
|
return { task_id: taskId, deleted: false };
|
|
178
239
|
}
|
|
179
|
-
const finalize = () => withFileLock(taskOperationLockPath(taskId), () => {
|
|
240
|
+
const finalize = () => withFileLock(taskOperationLockPath(taskId), async () => {
|
|
180
241
|
try {
|
|
181
242
|
const task = getDeletingTask(taskId);
|
|
182
243
|
if (task.deletion?.status !== 'pending')
|
|
@@ -185,8 +246,26 @@ export async function settleTaskDeletion(input) {
|
|
|
185
246
|
throw new TaskStateError(`task ${taskId} room records reappeared during deletion finalization`);
|
|
186
247
|
if (task.deletion.members.some(member => member.phase !== 'identity_absent'))
|
|
187
248
|
throw new TaskStateError(`task ${taskId} has unretired members at deletion finalization`);
|
|
249
|
+
for (const proof of task.deletion.archived_absences ?? [])
|
|
250
|
+
await (task.deletion.workspace_cleanup_started_at && !existsSync(proof.archive_path) ? verifyArchivedMemberStillAbsent : verifyArchivedAbsence)(proof);
|
|
251
|
+
for (const proof of task.deletion.archived_absences ?? [])
|
|
252
|
+
if (existsSync(agentDir(proof.name, true)))
|
|
253
|
+
throw new Error('Archived member replacement before task unlink');
|
|
254
|
+
for (const name of task.deletion.absent_members ?? []) {
|
|
255
|
+
if (existsSync(agentDir(name, true)))
|
|
256
|
+
throw new Error('Absent member acquired replacement state');
|
|
257
|
+
await assertMemberIdentityAbsent({ role_name: name, slot: name, cowork_role: 'member', seat_state: 'removed' });
|
|
258
|
+
}
|
|
188
259
|
if (cleanup.snapshotHash)
|
|
189
260
|
releaseLaunchSnapshotForDeletingTask(cleanup.snapshotHash, taskId);
|
|
261
|
+
assertTaskRegistrationsAbsent(taskId);
|
|
262
|
+
await eraseMemberArtifacts('task', taskId, task.deletion.members.map(cursorSeat), task.deletion.room_id ? [task.deletion.room_id] : []);
|
|
263
|
+
if (task.workspace) {
|
|
264
|
+
assertWorkspaceDeletable(task.workspace, 'task', taskId);
|
|
265
|
+
beginTaskArchiveCleanup(taskId);
|
|
266
|
+
collectWorkspaceArchives(task.workspace);
|
|
267
|
+
deleteWorkspace(task.workspace, 'task', taskId);
|
|
268
|
+
}
|
|
190
269
|
unlinkDeletedTask(taskId);
|
|
191
270
|
}
|
|
192
271
|
catch (error) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RoomMemberSeat } from './types.js';
|
|
2
|
+
/** Erase only proven launch artifacts. Each atomic rename transfers ownership to
|
|
3
|
+
* a random, durable tombstone before recursive removal can destroy its proof.
|
|
4
|
+
* The caller holds the task/room lifecycle lock and has retired all writers.
|
|
5
|
+
*/
|
|
6
|
+
export declare function eraseMemberArtifacts(owner: 'room' | 'task', id: string, seats: readonly RoomMemberSeat[], roomIds: readonly string[]): Promise<void>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { eraseResourcePresentations } from '../erased-resources.js';
|
|
2
|
+
import { assertMemberNotPermanent } from './member-ownership.js';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync } from 'node:fs';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
import { dirname, join, relative } from 'node:path';
|
|
6
|
+
import { parse } from 'yaml';
|
|
7
|
+
import { replaceFileAtomically } from '../atomic-file.js';
|
|
8
|
+
import { agentDir, stateRoot } from '../paths.js';
|
|
9
|
+
import { readTempSupervisorForScan, tempSupervisorLiveness, eraseTerminationEvents } from '../temp-lifecycle.js';
|
|
10
|
+
import { assertSafeAncestors, auditWorkspaceGit, prepareOwnedDeletionTree } from './workspace.js';
|
|
11
|
+
function entries(path) {
|
|
12
|
+
try {
|
|
13
|
+
return readdirSync(path);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code === 'ENOENT')
|
|
17
|
+
return [];
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function json(path) {
|
|
22
|
+
try {
|
|
23
|
+
if (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
|
|
24
|
+
return undefined;
|
|
25
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code === 'ENOENT')
|
|
29
|
+
return undefined;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function fingerprint(path) {
|
|
34
|
+
const stat = lstatSync(path);
|
|
35
|
+
const files = stat.isDirectory() ? ['creation.json', '.temp-supervisor.json', '.identity', 'role.yaml', 'state.json', 'instance.json'] : [''];
|
|
36
|
+
const hash = createHash('sha256');
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
const target = file ? join(path, file) : path;
|
|
39
|
+
if (existsSync(target)) {
|
|
40
|
+
if (!lstatSync(target).isFile() || lstatSync(target).isSymbolicLink())
|
|
41
|
+
throw new Error('Unsafe erasure proof');
|
|
42
|
+
hash.update(file).update(readFileSync(target));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return hash.digest('hex');
|
|
46
|
+
}
|
|
47
|
+
/** Erase only proven launch artifacts. Each atomic rename transfers ownership to
|
|
48
|
+
* a random, durable tombstone before recursive removal can destroy its proof.
|
|
49
|
+
* The caller holds the task/room lifecycle lock and has retired all writers.
|
|
50
|
+
*/
|
|
51
|
+
export async function eraseMemberArtifacts(owner, id, seats, roomIds) {
|
|
52
|
+
if (!/^[a-zA-Z0-9_-]{1,80}$/.test(id))
|
|
53
|
+
throw new Error('Invalid erasure owner');
|
|
54
|
+
const root = stateRoot();
|
|
55
|
+
const manifestPath = join(root, 'erasure', `${owner}-${id}.json`);
|
|
56
|
+
assertSafeAncestors(dirname(manifestPath));
|
|
57
|
+
let manifest = json(manifestPath);
|
|
58
|
+
const names = new Set(seats.map(s => s.role_name));
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
if (!/^[a-zA-Z0-9_-]{1,120}$/.test(name))
|
|
61
|
+
throw new Error('Invalid erasure member');
|
|
62
|
+
assertMemberNotPermanent(name);
|
|
63
|
+
if (existsSync(agentDir(name, true)))
|
|
64
|
+
throw new Error(`Member '${name}' still has live state during erasure`);
|
|
65
|
+
}
|
|
66
|
+
if (!manifest) {
|
|
67
|
+
const paths = [];
|
|
68
|
+
const launches = seats.flatMap(seat => {
|
|
69
|
+
const launchId = seat.launch?.launch_id ?? seat.retirement?.launch_id;
|
|
70
|
+
return launchId ? [{ role: seat.role_name, launchId }] : [];
|
|
71
|
+
});
|
|
72
|
+
const actions = new Map(seats.map(s => [s.role_name, new Set([s.launch?.action_id].filter((v) => !!v))]));
|
|
73
|
+
const recovery = join(root, 'recovery', 'temporary');
|
|
74
|
+
assertSafeAncestors(recovery);
|
|
75
|
+
for (const entry of entries(recovery)) {
|
|
76
|
+
const path = join(recovery, entry);
|
|
77
|
+
const stat = lstatSync(path);
|
|
78
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
79
|
+
continue;
|
|
80
|
+
const supervisor = readTempSupervisorForScan(path);
|
|
81
|
+
if (!supervisor || !names.has(supervisor.role))
|
|
82
|
+
continue;
|
|
83
|
+
const seat = seats.find(s => s.role_name === supervisor.role);
|
|
84
|
+
const creation = json(join(path, 'creation.json'));
|
|
85
|
+
let room;
|
|
86
|
+
const roleFile = join(path, 'role.yaml');
|
|
87
|
+
if (existsSync(roleFile) && lstatSync(roleFile).isFile() && !lstatSync(roleFile).isSymbolicLink()) {
|
|
88
|
+
const role = parse(readFileSync(roleFile, 'utf8'));
|
|
89
|
+
if (role?.name === supervisor.role)
|
|
90
|
+
room = role.roomMemberStartup?.room_id;
|
|
91
|
+
}
|
|
92
|
+
const exactLaunch = seat.launch?.launch_id === supervisor.launchId || seat.retirement?.launch_id === supervisor.launchId;
|
|
93
|
+
const exactAction = creation?.role === supervisor.role && creation.creationActionId === seat.launch?.action_id;
|
|
94
|
+
// Older attempts belong only when the launch descriptor pins the room.
|
|
95
|
+
if (!(exactLaunch && (exactAction || !seat.launch?.action_id)) && !(room && roomIds.includes(room)))
|
|
96
|
+
continue;
|
|
97
|
+
if (existsSync(join(path, '.identity')) && readFileSync(join(path, '.identity'), 'utf8').trim() !== supervisor.role)
|
|
98
|
+
throw new Error('Archive identity mismatch during erasure');
|
|
99
|
+
if (await tempSupervisorLiveness(path) !== 'stopped')
|
|
100
|
+
throw new Error('Archive writer is not stopped');
|
|
101
|
+
if (creation?.role === supervisor.role && typeof creation.creationActionId === 'string')
|
|
102
|
+
actions.get(supervisor.role).add(creation.creationActionId);
|
|
103
|
+
auditWorkspaceGit(path, { forDeletion: true });
|
|
104
|
+
launches.push({ role: supervisor.role, launchId: supervisor.launchId });
|
|
105
|
+
paths.push(path);
|
|
106
|
+
}
|
|
107
|
+
const privateRoot = join(root, 'private-ours');
|
|
108
|
+
assertSafeAncestors(privateRoot);
|
|
109
|
+
for (const entry of entries(privateRoot)) {
|
|
110
|
+
const path = join(privateRoot, entry);
|
|
111
|
+
if (!lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink())
|
|
112
|
+
continue;
|
|
113
|
+
if (entry === 'launches' || entry === 'room-inputs') {
|
|
114
|
+
for (const file of entries(path)) {
|
|
115
|
+
const child = join(path, file), value = json(child);
|
|
116
|
+
if (!value)
|
|
117
|
+
continue;
|
|
118
|
+
const launchOwned = entry === 'launches' && value.role === value.identity
|
|
119
|
+
&& actions.get(value.role)?.has(value.action);
|
|
120
|
+
const roomOwned = entry === 'room-inputs' && roomIds.includes(value.room_id)
|
|
121
|
+
&& names.has(value.identity_name);
|
|
122
|
+
const readyOwned = entry === 'room-inputs' && file.endsWith('.ready.json')
|
|
123
|
+
&& roomIds.includes(value.room) && seats.some(seat => seat.invite_id === value.invite && (!seat.identity_cid || seat.identity_cid === value.cid));
|
|
124
|
+
if (launchOwned || roomOwned || readyOwned)
|
|
125
|
+
paths.push(child);
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const state = json(join(path, 'state.json')), instance = json(join(path, 'instance.json'));
|
|
130
|
+
if (state?.lifetime === 'temporary' && actions.get(state.name)?.has(state.action)
|
|
131
|
+
&& instance?.role === state.name && instance?.temporary === true && instance?.instance === state.instance) {
|
|
132
|
+
auditWorkspaceGit(path, { forDeletion: true });
|
|
133
|
+
paths.push(path);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
manifest = { launches, version: 1, token: randomUUID(), paths, phases: {}, stamps: Object.fromEntries(paths.map(path => {
|
|
137
|
+
const stat = lstatSync(path);
|
|
138
|
+
return [path, { dev: stat.dev, ino: stat.ino, proof: fingerprint(path) }];
|
|
139
|
+
})) };
|
|
140
|
+
mkdirSync(dirname(manifestPath), { recursive: true, mode: 0o700 });
|
|
141
|
+
replaceFileAtomically(manifestPath, JSON.stringify(manifest));
|
|
142
|
+
}
|
|
143
|
+
if (manifest.version !== 1 || !/^[a-f0-9-]{36}$/.test(manifest.token) || !Array.isArray(manifest.paths))
|
|
144
|
+
throw new Error('Invalid erasure manifest');
|
|
145
|
+
for (const path of manifest.paths) {
|
|
146
|
+
const rel = relative(root, path);
|
|
147
|
+
if (!rel.startsWith('recovery/temporary/') && !rel.startsWith('private-ours/'))
|
|
148
|
+
throw new Error('Invalid erasure artifact path');
|
|
149
|
+
if (rel.split('/').includes('..'))
|
|
150
|
+
throw new Error('Invalid erasure artifact traversal');
|
|
151
|
+
assertSafeAncestors(dirname(path));
|
|
152
|
+
const tombstone = `${path}.erasing-${manifest.token}`;
|
|
153
|
+
if (manifest.phases[path] === 'removed')
|
|
154
|
+
continue;
|
|
155
|
+
if (existsSync(path) && !existsSync(tombstone) && manifest.phases[path] !== 'renamed') {
|
|
156
|
+
const stat = lstatSync(path), stamp = manifest.stamps[path];
|
|
157
|
+
if (stat.isSymbolicLink() || !stamp || stamp.dev !== stat.dev || stamp.ino !== stat.ino || stamp.proof !== fingerprint(path))
|
|
158
|
+
throw new Error('Erasure source ownership changed');
|
|
159
|
+
renameSync(path, tombstone);
|
|
160
|
+
}
|
|
161
|
+
manifest.phases[path] = 'renamed';
|
|
162
|
+
replaceFileAtomically(manifestPath, JSON.stringify(manifest));
|
|
163
|
+
if (existsSync(tombstone) && lstatSync(tombstone).isDirectory())
|
|
164
|
+
prepareOwnedDeletionTree(tombstone);
|
|
165
|
+
rmSync(tombstone, { recursive: true, force: true });
|
|
166
|
+
manifest.phases[path] = 'removed';
|
|
167
|
+
replaceFileAtomically(manifestPath, JSON.stringify(manifest));
|
|
168
|
+
}
|
|
169
|
+
eraseResourcePresentations([{ kind: owner, id }, ...roomIds.map(id => ({ kind: 'room', id })),
|
|
170
|
+
...seats.map(seat => ({ kind: 'agent', id: seat.role_name }))]);
|
|
171
|
+
eraseTerminationEvents(manifest.launches);
|
|
172
|
+
rmSync(manifestPath, { force: true });
|
|
173
|
+
}
|
|
@@ -8,4 +8,4 @@ export declare function presentFleetWorkerLifecycle(presentations: FleetAuditPre
|
|
|
8
8
|
sleep(ms: number): Promise<void>;
|
|
9
9
|
}): Promise<void>;
|
|
10
10
|
/** Launch a Fleet CLI operation outside the caller's supervisor/session lifecycle. */
|
|
11
|
-
export declare function launchFleetWorker(args: string[], operation: string, configPath?: string): Promise<void>;
|
|
11
|
+
export declare function launchFleetWorker(args: string[], operation: string, configPath?: string, binPath?: string): Promise<void>;
|
|
@@ -9,8 +9,7 @@ const LIFECYCLE_ACCEPT_TIMEOUT_MS = 10 * 60_000;
|
|
|
9
9
|
const LIFECYCLE_RETRY_MS = 250;
|
|
10
10
|
export function fleetWorkerEnv(source = process.env) {
|
|
11
11
|
const inheritedKeys = [
|
|
12
|
-
'HOME', 'PATH', 'XDG_RUNTIME_DIR', 'OURS_FLEET_HOME', 'OURS_CONFIG',
|
|
13
|
-
'OURS_PORT', 'OURS_STATE_DIR', 'OURS_API_TOKEN', 'OURS_COWORK_CONFIG',
|
|
12
|
+
'HOME', 'PATH', 'XDG_RUNTIME_DIR', 'OURS_FLEET_HOME', 'OURS_FLEET_SOCKET_ROOT', 'OURS_CONFIG',
|
|
14
13
|
];
|
|
15
14
|
const env = Object.fromEntries(inheritedKeys.flatMap(key => source[key] === undefined ? [] : [[key, source[key]]]));
|
|
16
15
|
const lifecycleStateDir = source[FLEET_WORKER_LIFECYCLE_STATE_DIR_ENV]
|
|
@@ -52,8 +51,8 @@ export async function presentFleetWorkerLifecycle(presentations, deps = {
|
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
/** Launch a Fleet CLI operation outside the caller's supervisor/session lifecycle. */
|
|
55
|
-
export function launchFleetWorker(args, operation, configPath) {
|
|
56
|
-
const cli = [
|
|
54
|
+
export function launchFleetWorker(args, operation, configPath, binPath = process.argv[1]) {
|
|
55
|
+
const cli = [binPath, ...args, ...(configPath ? ['-c', configPath] : [])];
|
|
57
56
|
const env = fleetWorkerEnv();
|
|
58
57
|
const inherited = Object.entries(env).map(([key, value]) => `${key}=${value}`);
|
|
59
58
|
const suffix = randomUUID().slice(0, 8);
|