@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, symlinkSync } from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
5
|
+
import { FleetError } from '../application/errors.js';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { stateRoot } from '../paths.js';
|
|
8
|
+
export const SUBSCRIPTION_PROVIDERS = ['claude', 'codex'];
|
|
9
|
+
export const DEFAULT_PROFILE_ID = 'default';
|
|
10
|
+
export const PIN_FILE = '.subscription-pin.json';
|
|
11
|
+
export const OBSERVED_USAGE_FILE = '.subscription-usage.json';
|
|
12
|
+
/** Non-history configuration shared from the base home into each new profile. */
|
|
13
|
+
const SHARED_CONFIG = {
|
|
14
|
+
claude: ['settings.json', 'CLAUDE.md', 'plugins', 'skills', 'agents', 'commands'],
|
|
15
|
+
codex: ['config.toml', 'AGENTS.md', 'plugins', 'skills', 'rules'],
|
|
16
|
+
};
|
|
17
|
+
export const PROFILE_ID_RE = /^[a-z0-9][a-z0-9-]{0,40}$/;
|
|
18
|
+
export const subscriptionsRoot = () => join(stateRoot(), 'subscriptions');
|
|
19
|
+
const stateFile = () => join(subscriptionsRoot(), 'state.json');
|
|
20
|
+
const lockPath = () => join(subscriptionsRoot(), '.lock');
|
|
21
|
+
/**
|
|
22
|
+
* The CLI's own default home, adopted as the `default` profile. This follows
|
|
23
|
+
* `$HOME` like the CLIs do, not OURS_FLEET_HOME (which may relocate Fleet state).
|
|
24
|
+
*/
|
|
25
|
+
export const baseHome = (provider) => join(homedir(), provider === 'claude' ? '.claude' : '.codex');
|
|
26
|
+
export function isProvider(value) {
|
|
27
|
+
return value === 'claude' || value === 'codex';
|
|
28
|
+
}
|
|
29
|
+
export function profileHome(provider, profileId) {
|
|
30
|
+
if (profileId === DEFAULT_PROFILE_ID)
|
|
31
|
+
return baseHome(provider);
|
|
32
|
+
if (!PROFILE_ID_RE.test(profileId))
|
|
33
|
+
throw new FleetError('invalid_request', 'invalid profile id');
|
|
34
|
+
return join(subscriptionsRoot(), provider, profileId);
|
|
35
|
+
}
|
|
36
|
+
function initialState() {
|
|
37
|
+
const provider = () => ({
|
|
38
|
+
activeProfileId: DEFAULT_PROFILE_ID,
|
|
39
|
+
profiles: [{ id: DEFAULT_PROFILE_ID, label: 'Default (existing login)', createdAt: new Date(0).toISOString() }],
|
|
40
|
+
});
|
|
41
|
+
return { version: 1, providers: { claude: provider(), codex: provider() } };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read the state. The file is only ever replaced by an atomic rename, so a
|
|
45
|
+
* reader (including a launching agent) sees either the old or the new active
|
|
46
|
+
* profile, never a torn write.
|
|
47
|
+
*/
|
|
48
|
+
export function readSubscriptionState() {
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = readFileSync(stateFile(), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === 'ENOENT')
|
|
55
|
+
return initialState();
|
|
56
|
+
throw unreadable();
|
|
57
|
+
}
|
|
58
|
+
// Fail closed: a damaged state file must not silently move launches to another account.
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = JSON.parse(text);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw unreadable();
|
|
65
|
+
}
|
|
66
|
+
const state = initialState();
|
|
67
|
+
const providers = raw?.providers;
|
|
68
|
+
if (!providers || typeof providers !== 'object')
|
|
69
|
+
throw unreadable();
|
|
70
|
+
for (const provider of SUBSCRIPTION_PROVIDERS) {
|
|
71
|
+
const p = providers[provider];
|
|
72
|
+
if (!p || !Array.isArray(p.profiles) || typeof p.activeProfileId !== 'string')
|
|
73
|
+
throw unreadable();
|
|
74
|
+
const profiles = p.profiles;
|
|
75
|
+
if (profiles.some(x => !x || typeof x.id !== 'string' || (x.id !== DEFAULT_PROFILE_ID && !PROFILE_ID_RE.test(x.id))))
|
|
76
|
+
throw unreadable();
|
|
77
|
+
if (!profiles.some(x => x.id === DEFAULT_PROFILE_ID))
|
|
78
|
+
profiles.unshift(state.providers[provider].profiles[0]);
|
|
79
|
+
if (!profiles.some(x => x.id === p.activeProfileId))
|
|
80
|
+
throw unreadable();
|
|
81
|
+
const active = p.activeProfileId;
|
|
82
|
+
state.providers[provider] = {
|
|
83
|
+
activeProfileId: active,
|
|
84
|
+
profiles: profiles.map(x => ({ id: x.id, label: String(x.label ?? x.id).slice(0, 80), createdAt: String(x.createdAt ?? '') })),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return state;
|
|
88
|
+
}
|
|
89
|
+
const unreadable = () => new FleetError('prerequisite_unavailable', `subscription state ${stateFile()} is unreadable or invalid; fix or remove it before starting agents`);
|
|
90
|
+
/** Serialised read-modify-write of the state under a cross-process lock. */
|
|
91
|
+
export async function updateSubscriptionState(fn) {
|
|
92
|
+
mkdirSync(subscriptionsRoot(), { recursive: true, mode: 0o700 });
|
|
93
|
+
return withFileLock(lockPath(), () => {
|
|
94
|
+
const state = readSubscriptionState();
|
|
95
|
+
const result = fn(state);
|
|
96
|
+
replaceFileAtomically(stateFile(), `${JSON.stringify(state, null, 2)}\n`, 0o600);
|
|
97
|
+
return result;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create an empty profile home (not yet registered). Shared, non-history
|
|
102
|
+
* configuration is symlinked from the base home; credentials and session
|
|
103
|
+
* history stay per profile.
|
|
104
|
+
*/
|
|
105
|
+
export function createProfileHome(provider) {
|
|
106
|
+
const id = `p-${randomBytes(6).toString('hex')}`;
|
|
107
|
+
const dir = profileHome(provider, id);
|
|
108
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
109
|
+
const base = baseHome(provider);
|
|
110
|
+
for (const name of SHARED_CONFIG[provider]) {
|
|
111
|
+
const src = join(base, name);
|
|
112
|
+
if (existsSync(src)) {
|
|
113
|
+
try {
|
|
114
|
+
symlinkSync(src, join(dir, name));
|
|
115
|
+
}
|
|
116
|
+
catch { /* optional convenience */ }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { id, home: dir };
|
|
120
|
+
}
|
|
121
|
+
/** Remove a non-default profile home. Never touches the adopted default. */
|
|
122
|
+
export function removeProfileHome(provider, profileId) {
|
|
123
|
+
if (profileId === DEFAULT_PROFILE_ID)
|
|
124
|
+
return;
|
|
125
|
+
rmSync(profileHome(provider, profileId), { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
export function readPin(stateDir, provider) {
|
|
128
|
+
try {
|
|
129
|
+
const pin = JSON.parse(readFileSync(join(stateDir, PIN_FILE), 'utf8'));
|
|
130
|
+
if (provider)
|
|
131
|
+
return pin[provider];
|
|
132
|
+
return Object.values(pin)[0];
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export function writePin(stateDir, pin) {
|
|
139
|
+
replaceFileAtomically(join(stateDir, PIN_FILE), `${JSON.stringify({ [pin.provider]: pin })}\n`, 0o600);
|
|
140
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Exec } from '../exec.js';
|
|
2
|
+
export declare function adoptConfiguredFleet(binPath: string, exec: Exec, platform: NodeJS.Platform, configPath?: string, selectedRole?: string): Promise<void>;
|
|
3
|
+
/** Boot resumes recorded transfers only. It does not rediscover removed agents
|
|
4
|
+
* or decide that every configured role should be started. */
|
|
5
|
+
export declare function resumeFleetTransfers(binPath: string, exec?: Exec, platform?: NodeJS.Platform): Promise<void>;
|
|
6
|
+
/** A remove racing the native-retired → registered seam must not be undone by
|
|
7
|
+
* boot receipt recovery. Serialize with that seam and close its cursor first. */
|
|
8
|
+
export declare function retirePermanentRegistration(name: string, exec: Exec): Promise<boolean>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { agentDir, defaultConfigPath, stateRoot } from '../paths.js';
|
|
4
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
5
|
+
import { loadConfig, findRole } from '../config.js';
|
|
6
|
+
import { realExec } from '../exec.js';
|
|
7
|
+
import { readMember, memberKey, unregisterMember, registerMember } from './catalog.js';
|
|
8
|
+
import { migrateLegacyPermanentMembers } from './legacy.js';
|
|
9
|
+
import { ensureFleetParent } from './fleet.js';
|
|
10
|
+
export async function adoptConfiguredFleet(binPath, exec, platform, configPath, selectedRole) {
|
|
11
|
+
const path = configPath || defaultConfigPath();
|
|
12
|
+
if (existsSync(path)) {
|
|
13
|
+
const config = loadConfig(path);
|
|
14
|
+
await migrateLegacyPermanentMembers({
|
|
15
|
+
roles: selectedRole ? [findRole(config, selectedRole)] : config.roles, binPath, configPath: path, exec, platform,
|
|
16
|
+
prepareParent: () => ensureFleetParent(exec, platform),
|
|
17
|
+
register: async (name, desired, selectedConfig) => {
|
|
18
|
+
await registerMember({ name, kind: 'permanent', dir: agentDir(name), configPath: selectedConfig }, { initialDesired: desired, preserveExisting: true });
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
else
|
|
23
|
+
await ensureFleetParent(exec, platform);
|
|
24
|
+
}
|
|
25
|
+
/** Boot resumes recorded transfers only. It does not rediscover removed agents
|
|
26
|
+
* or decide that every configured role should be started. */
|
|
27
|
+
export async function resumeFleetTransfers(binPath, exec = realExec, platform = process.platform) {
|
|
28
|
+
const root = join(stateRoot(), 'supervisor/legacy-permanent');
|
|
29
|
+
let names;
|
|
30
|
+
try {
|
|
31
|
+
names = readdirSync(root).filter(name => name.endsWith('.json'));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === 'ENOENT')
|
|
35
|
+
names = [];
|
|
36
|
+
else
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
const path = join(root, name), stat = lstatSync(path);
|
|
41
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
|
|
42
|
+
throw Error('LEGACY_PERMANENT_UNSAFE_PROOF');
|
|
43
|
+
const receipt = JSON.parse(readFileSync(path, 'utf8'));
|
|
44
|
+
if (receipt.phase === 'registered')
|
|
45
|
+
continue;
|
|
46
|
+
if (name !== receipt.name + '.json')
|
|
47
|
+
throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
|
|
48
|
+
const configPath = receipt.configPath || defaultConfigPath();
|
|
49
|
+
const role = findRole(loadConfig(configPath), receipt.name);
|
|
50
|
+
await migrateLegacyPermanentMembers({
|
|
51
|
+
roles: [role], binPath, configPath, exec, platform,
|
|
52
|
+
// This runs inside the parent. No recursive OS start, unit writes or reload.
|
|
53
|
+
prepareParent: async () => { },
|
|
54
|
+
register: async (name, desired, selectedConfig) => {
|
|
55
|
+
await registerMember({ name, kind: 'permanent', dir: agentDir(name), configPath: selectedConfig }, { initialDesired: desired, preserveExisting: true });
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** A remove racing the native-retired → registered seam must not be undone by
|
|
61
|
+
* boot receipt recovery. Serialize with that seam and close its cursor first. */
|
|
62
|
+
export async function retirePermanentRegistration(name, exec) {
|
|
63
|
+
memberKey(name, 'permanent');
|
|
64
|
+
return withFileLock(join(stateRoot(), 'locks/legacy-permanent', name), async () => {
|
|
65
|
+
const member = readMember(memberKey(name, 'permanent'));
|
|
66
|
+
const path = join(stateRoot(), 'supervisor/legacy-permanent', name + '.json');
|
|
67
|
+
if (member && existsSync(path)) {
|
|
68
|
+
const stat = lstatSync(path);
|
|
69
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
|
|
70
|
+
throw Error('LEGACY_PERMANENT_UNSAFE_PROOF');
|
|
71
|
+
const receipt = JSON.parse(readFileSync(path, 'utf8'));
|
|
72
|
+
if (receipt.version !== 1 || receipt.name !== name || !['native-retired', 'registered'].includes(receipt.phase))
|
|
73
|
+
throw Error('LEGACY_PERMANENT_RECEIPT_MISMATCH');
|
|
74
|
+
replaceFileAtomically(path, JSON.stringify({ ...receipt, phase: 'registered' }) + '\n');
|
|
75
|
+
}
|
|
76
|
+
return unregisterMember(memberKey(name, 'permanent'), { exec });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Exec } from '../exec.js';
|
|
2
|
+
import type { TaskSupervisorOwner } from '../temp-lifecycle.js';
|
|
3
|
+
import type { Liveness } from './types.js';
|
|
4
|
+
export interface FleetMember {
|
|
5
|
+
version: 1;
|
|
6
|
+
key: string;
|
|
7
|
+
name: string;
|
|
8
|
+
kind: 'permanent' | 'task' | 'temporary' | 'watchdogs';
|
|
9
|
+
dir: string;
|
|
10
|
+
desired: 'running' | 'stopped';
|
|
11
|
+
retiring?: boolean;
|
|
12
|
+
incarnation: string;
|
|
13
|
+
taskOwner?: TaskSupervisorOwner;
|
|
14
|
+
launchId?: string;
|
|
15
|
+
configPath?: string;
|
|
16
|
+
environment?: Record<string, string>;
|
|
17
|
+
generation?: string;
|
|
18
|
+
pid?: number;
|
|
19
|
+
retryAt?: number;
|
|
20
|
+
}
|
|
21
|
+
/** Private 0600 member state retains exactly the creator's profile selection.
|
|
22
|
+
* Pin the resolved profile path even when the caller relied on HOME defaults. */
|
|
23
|
+
export declare function captureMemberEnvironment(env?: NodeJS.ProcessEnv): Promise<Record<string, string>>;
|
|
24
|
+
export declare function managedMemberEnvironment(member: FleetMember, parent?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
25
|
+
export declare const catalogRoot: () => string;
|
|
26
|
+
export declare function memberKey(name: string, kind: FleetMember['kind']): string;
|
|
27
|
+
export declare function memberPath(key: string): string;
|
|
28
|
+
export declare const withMemberLock: <T>(key: string, work: () => T | Promise<T>) => Promise<T>;
|
|
29
|
+
export declare function readMember(key: string): FleetMember | undefined;
|
|
30
|
+
export declare function writeMember(record: FleetMember): void;
|
|
31
|
+
export declare function listMemberKeys(): string[];
|
|
32
|
+
export declare function listMembers(): FleetMember[];
|
|
33
|
+
export type MemberRegistration = Pick<FleetMember, 'name' | 'kind' | 'dir' | 'taskOwner' | 'launchId' | 'configPath' | 'environment'>;
|
|
34
|
+
/** Persist intent before starting anything. The parent never discovers members from tmp directories. */
|
|
35
|
+
export declare function registerMember(input: MemberRegistration, options?: {
|
|
36
|
+
initialDesired?: FleetMember['desired'];
|
|
37
|
+
preserveExisting?: boolean;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
created: boolean;
|
|
40
|
+
member: FleetMember;
|
|
41
|
+
}>;
|
|
42
|
+
export interface CatalogDeps {
|
|
43
|
+
exec?: Exec;
|
|
44
|
+
sleep?(ms: number): Promise<void>;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
}
|
|
47
|
+
/** A unique generation in argv fences PID reuse, including the spawn-to-pid-write crash seam. */
|
|
48
|
+
export declare function memberProcess(record: FleetMember, exec?: Exec): Promise<Liveness & {
|
|
49
|
+
pid?: number;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function catalogLiveness(key: string, exec?: Exec): Promise<Liveness>;
|
|
52
|
+
export declare function waitMember(key: string, desired: 'running' | 'stopped', deps?: CatalogDeps): Promise<void>;
|
|
53
|
+
export declare function stopMember(key: string, deps?: CatalogDeps): Promise<void>;
|
|
54
|
+
export declare function unregisterMember(key: string, deps?: CatalogDeps): Promise<boolean>;
|
|
55
|
+
export declare function unregisterTaskMember(role: string, expected: {
|
|
56
|
+
taskId: string;
|
|
57
|
+
creationActionId?: string;
|
|
58
|
+
launchId?: string;
|
|
59
|
+
}, deps?: CatalogDeps): Promise<void>;
|
|
60
|
+
export declare function hasTaskMemberRegistration(role: string): boolean;
|
|
61
|
+
export declare function assertTaskRegistrationsAbsent(taskId: string): void;
|
|
62
|
+
export declare const permanentRegistration: (name: string) => MemberRegistration;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, lstatSync, readFileSync, readdirSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
5
|
+
import { agentDir, stateRoot, watchdogsRoot } from '../paths.js';
|
|
6
|
+
import { realExec } from '../exec.js';
|
|
7
|
+
import { assertSafeAncestors } from '../rooms-tasks/workspace.js';
|
|
8
|
+
const MEMBER_ENV_KEYS = ['HOME', 'PATH', 'XDG_RUNTIME_DIR', 'CODEX_HOME', 'CODEX_PATH', 'OURS_PORT', 'OURS_STATE_DIR', 'OURS_API_TOKEN', 'OURS_CONFIG'];
|
|
9
|
+
/** Private 0600 member state retains exactly the creator's profile selection.
|
|
10
|
+
* Pin the resolved profile path even when the caller relied on HOME defaults. */
|
|
11
|
+
export async function captureMemberEnvironment(env = process.env) {
|
|
12
|
+
const { clientConfigPath } = await import('../client-profile.js');
|
|
13
|
+
return Object.fromEntries([...MEMBER_ENV_KEYS.flatMap(key => env[key] === undefined ? [] : [[key, env[key]]]), ['OURS_CONFIG', clientConfigPath(env)]]);
|
|
14
|
+
}
|
|
15
|
+
export function managedMemberEnvironment(member, parent = process.env) {
|
|
16
|
+
const env = { ...parent };
|
|
17
|
+
if (member.environment) {
|
|
18
|
+
for (const key of MEMBER_ENV_KEYS)
|
|
19
|
+
delete env[key];
|
|
20
|
+
Object.assign(env, member.environment);
|
|
21
|
+
}
|
|
22
|
+
return env;
|
|
23
|
+
}
|
|
24
|
+
export const catalogRoot = () => join(stateRoot(), 'supervisor', 'members');
|
|
25
|
+
export function memberKey(name, kind) {
|
|
26
|
+
if (!/^[A-Za-z0-9_-]{1,160}$/.test(name))
|
|
27
|
+
throw Error('FLEET_MEMBER_INVALID_NAME');
|
|
28
|
+
return `${kind}-${name}`;
|
|
29
|
+
}
|
|
30
|
+
export function memberPath(key) {
|
|
31
|
+
if (!/^(permanent|task|temporary|watchdogs)-[A-Za-z0-9_-]{1,160}$/.test(key))
|
|
32
|
+
throw Error('FLEET_MEMBER_INVALID_KEY');
|
|
33
|
+
return join(catalogRoot(), `${key}.json`);
|
|
34
|
+
}
|
|
35
|
+
export const withMemberLock = (key, work) => withFileLock(join(stateRoot(), 'locks', 'fleet-members', memberKeyFromValidatedKey(key)), work);
|
|
36
|
+
function memberKeyFromValidatedKey(key) { memberPath(key); return key; }
|
|
37
|
+
export function readMember(key) {
|
|
38
|
+
const path = memberPath(key);
|
|
39
|
+
assertSafeAncestors(catalogRoot());
|
|
40
|
+
let stat;
|
|
41
|
+
try {
|
|
42
|
+
stat = lstatSync(path);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error.code === 'ENOENT')
|
|
46
|
+
return;
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
|
|
50
|
+
throw Error('FLEET_MEMBER_UNSAFE_RECORD');
|
|
51
|
+
const record = JSON.parse(readFileSync(path, 'utf8'));
|
|
52
|
+
validateMember(record, key);
|
|
53
|
+
return record;
|
|
54
|
+
}
|
|
55
|
+
function validateMember(record, key) {
|
|
56
|
+
const expectedDir = record.kind === 'watchdogs' ? watchdogsRoot()
|
|
57
|
+
: agentDir(record.name, record.kind !== 'permanent');
|
|
58
|
+
if (record.version !== 1 || !['permanent', 'task', 'temporary', 'watchdogs'].includes(record.kind)
|
|
59
|
+
|| memberKey(record.name, record.kind) !== key || record.key !== key
|
|
60
|
+
|| !['running', 'stopped'].includes(record.desired)
|
|
61
|
+
|| !/^[a-f0-9-]{36}$/.test(record.incarnation)
|
|
62
|
+
|| record.dir !== resolve(record.dir) || record.dir !== expectedDir
|
|
63
|
+
|| (record.environment !== undefined && (typeof record.environment !== 'object' || record.environment === null
|
|
64
|
+
|| Object.entries(record.environment).some(([key, value]) => !MEMBER_ENV_KEYS.includes(key) || typeof value !== 'string' || value.includes('\0'))))
|
|
65
|
+
|| (record.pid !== undefined && (!Number.isSafeInteger(record.pid) || record.pid <= 0))
|
|
66
|
+
|| (record.retryAt !== undefined && (!Number.isFinite(record.retryAt) || record.retryAt < 0))
|
|
67
|
+
|| (record.kind === 'task' && (!record.taskOwner?.taskId || !record.taskOwner.creationActionId || !record.launchId)))
|
|
68
|
+
throw Error('FLEET_MEMBER_INVALID_RECORD');
|
|
69
|
+
}
|
|
70
|
+
export function writeMember(record) {
|
|
71
|
+
validateMember(record, record.key);
|
|
72
|
+
assertSafeAncestors(catalogRoot());
|
|
73
|
+
mkdirSync(catalogRoot(), { recursive: true, mode: 0o700 });
|
|
74
|
+
chmodSync(catalogRoot(), 0o700);
|
|
75
|
+
replaceFileAtomically(memberPath(record.key), JSON.stringify(record) + '\n');
|
|
76
|
+
}
|
|
77
|
+
export function listMemberKeys() {
|
|
78
|
+
try {
|
|
79
|
+
return readdirSync(catalogRoot()).filter(name => name.endsWith('.json')).map(name => name.slice(0, -5));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error.code === 'ENOENT')
|
|
83
|
+
return [];
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function listMembers() {
|
|
88
|
+
let entries;
|
|
89
|
+
try {
|
|
90
|
+
entries = readdirSync(catalogRoot(), { withFileTypes: true });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error.code === 'ENOENT')
|
|
94
|
+
return [];
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return entries.filter(entry => entry.name.endsWith('.json')).map(entry => {
|
|
98
|
+
const value = readMember(entry.name.slice(0, -5));
|
|
99
|
+
if (!value)
|
|
100
|
+
throw Error('FLEET_MEMBER_DISAPPEARED');
|
|
101
|
+
return value;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** Persist intent before starting anything. The parent never discovers members from tmp directories. */
|
|
105
|
+
export async function registerMember(input, options = {}) {
|
|
106
|
+
const key = memberKey(input.name, input.kind);
|
|
107
|
+
return withMemberLock(key, () => {
|
|
108
|
+
const previous = readMember(key);
|
|
109
|
+
if (previous && (previous.dir !== input.dir || previous.launchId !== input.launchId
|
|
110
|
+
|| JSON.stringify(previous.taskOwner) !== JSON.stringify(input.taskOwner)))
|
|
111
|
+
throw Error('FLEET_MEMBER_OWNER_COLLISION');
|
|
112
|
+
if (previous && options.preserveExisting)
|
|
113
|
+
return { created: false, member: previous };
|
|
114
|
+
if (previous?.retiring)
|
|
115
|
+
throw Error('FLEET_MEMBER_RETIRING');
|
|
116
|
+
const desired = options.initialDesired ?? 'running';
|
|
117
|
+
const member = previous
|
|
118
|
+
? { ...previous, desired, configPath: input.configPath ?? previous.configPath,
|
|
119
|
+
environment: previous.kind === 'task' ? previous.environment : input.environment ?? previous.environment, retryAt: 0 }
|
|
120
|
+
: { ...input, version: 1, key, desired, incarnation: randomUUID() };
|
|
121
|
+
writeMember(member);
|
|
122
|
+
return { created: !previous, member };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** A unique generation in argv fences PID reuse, including the spawn-to-pid-write crash seam. */
|
|
126
|
+
export async function memberProcess(record, exec = realExec) {
|
|
127
|
+
if (!record.generation)
|
|
128
|
+
return { state: 'stopped', detail: 'no child generation' };
|
|
129
|
+
if (!/^[a-f0-9-]{36}$/.test(record.generation))
|
|
130
|
+
return { state: 'unknown', detail: 'invalid child generation' };
|
|
131
|
+
const matches = (command) => command.split(/\s+/).some((part, index, words) => part === '_run-managed' && words[index + 1] === record.key && words[index + 2] === record.generation);
|
|
132
|
+
if (record.pid) {
|
|
133
|
+
try {
|
|
134
|
+
process.kill(record.pid, 0);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
return error.code === 'ESRCH'
|
|
138
|
+
? { state: 'stopped', detail: 'child pid absent' }
|
|
139
|
+
: { state: 'unknown', detail: 'child pid probe failed' };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const command = readFileSync(`/proc/${record.pid}/cmdline`, 'utf8').split('\0').join(' ');
|
|
143
|
+
return matches(command) ? { state: 'running', detail: 'exact child generation', pid: record.pid }
|
|
144
|
+
: { state: 'stopped', detail: 'pid belongs to another generation' };
|
|
145
|
+
}
|
|
146
|
+
catch { /* non-/proc hosts use ps */ }
|
|
147
|
+
}
|
|
148
|
+
const result = await exec('ps', record.pid ? ['-p', String(record.pid), '-o', 'pid=', '-o', 'command=']
|
|
149
|
+
: ['-ax', '-o', 'pid=', '-o', 'command=']);
|
|
150
|
+
if (result.code !== 0)
|
|
151
|
+
return result.code === 1 && record.pid
|
|
152
|
+
? { state: 'stopped', detail: 'child absent' } : { state: 'unknown', detail: 'process probe unavailable' };
|
|
153
|
+
const pids = result.stdout.split('\n').flatMap(line => {
|
|
154
|
+
const row = /^\s*(\d+)\s+(.*)$/.exec(line);
|
|
155
|
+
return row && matches(row[2]) ? [Number(row[1])] : [];
|
|
156
|
+
});
|
|
157
|
+
if (pids.length > 1)
|
|
158
|
+
return { state: 'unknown', detail: 'ambiguous child generation' };
|
|
159
|
+
return pids.length ? { state: 'running', detail: 'exact child generation', pid: pids[0] }
|
|
160
|
+
: { state: 'stopped', detail: 'child generation absent' };
|
|
161
|
+
}
|
|
162
|
+
export async function catalogLiveness(key, exec = realExec) {
|
|
163
|
+
try {
|
|
164
|
+
const record = readMember(key);
|
|
165
|
+
if (!record)
|
|
166
|
+
return { state: 'stopped', detail: 'member unregistered' };
|
|
167
|
+
const live = await memberProcess(record, exec);
|
|
168
|
+
if (live.state === 'stopped' && record.desired === 'running') {
|
|
169
|
+
const { readRestartLedger } = await import('../runner.js');
|
|
170
|
+
const ledger = readRestartLedger(record.dir);
|
|
171
|
+
if (ledger.circuit === 'open')
|
|
172
|
+
return { ...live, detail: `held after ${ledger.consecutiveImmediateFailures} failures: ${ledger.lastReason}; use up/restart or task recovery to reset` };
|
|
173
|
+
}
|
|
174
|
+
return live;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { state: 'unknown', detail: 'member record unavailable' };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
export async function waitMember(key, desired, deps = {}) {
|
|
181
|
+
const deadline = Date.now() + (deps.timeoutMs ?? 30_000);
|
|
182
|
+
do {
|
|
183
|
+
const live = await catalogLiveness(key, deps.exec);
|
|
184
|
+
if (live.state === desired)
|
|
185
|
+
return;
|
|
186
|
+
await (deps.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms))))(100);
|
|
187
|
+
} while (Date.now() < deadline);
|
|
188
|
+
throw Error(desired === 'stopped' ? 'FLEET_MEMBER_STOP_UNPROVEN' : 'FLEET_MEMBER_START_UNPROVEN');
|
|
189
|
+
}
|
|
190
|
+
export async function stopMember(key, deps = {}) {
|
|
191
|
+
await withMemberLock(key, () => {
|
|
192
|
+
const record = readMember(key);
|
|
193
|
+
if (record)
|
|
194
|
+
writeMember({ ...record, desired: 'stopped' });
|
|
195
|
+
});
|
|
196
|
+
// A stopped parent cannot consume intent, but its IPC children stop on disconnect.
|
|
197
|
+
// Never signal an arbitrary recorded pid or erase a record on timeout.
|
|
198
|
+
await waitMember(key, 'stopped', deps);
|
|
199
|
+
}
|
|
200
|
+
export async function unregisterMember(key, deps = {}) {
|
|
201
|
+
const original = readMember(key);
|
|
202
|
+
if (!original)
|
|
203
|
+
return false;
|
|
204
|
+
await withMemberLock(key, () => {
|
|
205
|
+
const current = readMember(key);
|
|
206
|
+
if (!current || current.incarnation !== original.incarnation)
|
|
207
|
+
throw Error('FLEET_MEMBER_RETIREMENT_CONFLICT');
|
|
208
|
+
writeMember({ ...current, desired: 'stopped', retiring: true });
|
|
209
|
+
});
|
|
210
|
+
await waitMember(key, 'stopped', deps);
|
|
211
|
+
return withMemberLock(key, async () => {
|
|
212
|
+
const current = readMember(key);
|
|
213
|
+
if (!current)
|
|
214
|
+
return false;
|
|
215
|
+
if (current.incarnation !== original.incarnation || current.desired !== 'stopped')
|
|
216
|
+
throw Error('FLEET_MEMBER_RETIREMENT_CONFLICT');
|
|
217
|
+
if ((await memberProcess(current, deps.exec)).state !== 'stopped')
|
|
218
|
+
throw Error('FLEET_MEMBER_STOP_UNPROVEN');
|
|
219
|
+
rmSync(memberPath(key));
|
|
220
|
+
return true;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
export async function unregisterTaskMember(role, expected, deps = {}) {
|
|
224
|
+
const key = memberKey(role, 'task');
|
|
225
|
+
await withMemberLock(key, () => {
|
|
226
|
+
const current = readMember(key);
|
|
227
|
+
if (!current)
|
|
228
|
+
return;
|
|
229
|
+
if (current.kind !== 'task' || current.taskOwner?.taskId !== expected.taskId
|
|
230
|
+
|| (!expected.creationActionId && !expected.launchId)
|
|
231
|
+
|| (expected.creationActionId !== undefined && current.taskOwner.creationActionId !== expected.creationActionId)
|
|
232
|
+
|| (expected.launchId !== undefined && current.launchId !== expected.launchId))
|
|
233
|
+
throw Error('TASK_SERVICE_OWNER_MISMATCH');
|
|
234
|
+
writeMember({ ...current, desired: 'stopped', retiring: true });
|
|
235
|
+
});
|
|
236
|
+
await unregisterMember(key, deps);
|
|
237
|
+
}
|
|
238
|
+
export function hasTaskMemberRegistration(role) {
|
|
239
|
+
const path = memberPath(memberKey(role, 'task'));
|
|
240
|
+
try {
|
|
241
|
+
lstatSync(path);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (error.code === 'ENOENT')
|
|
246
|
+
return false;
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
export function assertTaskRegistrationsAbsent(taskId) {
|
|
251
|
+
if (listMembers().some(record => record.taskOwner?.taskId === taskId))
|
|
252
|
+
throw Error('TASK_SERVICES_REMAIN');
|
|
253
|
+
}
|
|
254
|
+
export const permanentRegistration = (name) => ({ name, kind: 'permanent', dir: agentDir(name) });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Exec } from '../exec.js';
|
|
2
|
+
import type { SupervisorBackend } from './types.js';
|
|
3
|
+
export declare function fleetHostBackend(exec?: Exec, platform?: NodeJS.Platform): SupervisorBackend;
|
|
4
|
+
/** Registration only starts an explicitly installed parent; it cannot repoint
|
|
5
|
+
* the fleet at the invoking task/development binary or rewrite user settings. */
|
|
6
|
+
export declare function ensureFleetParent(exec?: Exec, platform?: NodeJS.Platform): Promise<void>;
|
|
7
|
+
/** OS operations refer only to the Fleet parent; member operations refer only to its catalog. */
|
|
8
|
+
export declare function makeFleetBackend(exec?: Exec, platform?: NodeJS.Platform): SupervisorBackend;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { agentDir } from '../paths.js';
|
|
4
|
+
import { realExec } from '../exec.js';
|
|
5
|
+
import { home } from '../paths.js';
|
|
6
|
+
import { makeSystemdBackend } from './systemd.js';
|
|
7
|
+
import { makeLaunchdBackend } from './launchd.js';
|
|
8
|
+
import { catalogLiveness, memberKey, readMember, registerMember, stopMember, unregisterMember, waitMember } from './catalog.js';
|
|
9
|
+
export function fleetHostBackend(exec = realExec, platform = process.platform) {
|
|
10
|
+
if (platform === 'linux')
|
|
11
|
+
return makeSystemdBackend(exec);
|
|
12
|
+
if (platform === 'darwin')
|
|
13
|
+
return makeLaunchdBackend(exec);
|
|
14
|
+
throw Error('Fleet service requires Linux or macOS');
|
|
15
|
+
}
|
|
16
|
+
/** Registration only starts an explicitly installed parent; it cannot repoint
|
|
17
|
+
* the fleet at the invoking task/development binary or rewrite user settings. */
|
|
18
|
+
export async function ensureFleetParent(exec = realExec, platform = process.platform) {
|
|
19
|
+
const host = fleetHostBackend(exec, platform);
|
|
20
|
+
const path = platform === 'linux' ? join(home(), '.config/systemd/user/ours-fleet.service')
|
|
21
|
+
: join(home(), 'Library/LaunchAgents/network.ours.fleet.plist');
|
|
22
|
+
if (!existsSync(path))
|
|
23
|
+
throw Error('FLEET_SERVICE_NOT_INSTALLED: run ours-fleet init');
|
|
24
|
+
const live = await host.liveness('fleet');
|
|
25
|
+
if (live.state === 'unknown')
|
|
26
|
+
throw Error('FLEET_SERVICE_LIVENESS_UNKNOWN');
|
|
27
|
+
if (live.state === 'stopped')
|
|
28
|
+
await host.install('fleet', '');
|
|
29
|
+
}
|
|
30
|
+
/** OS operations refer only to the Fleet parent; member operations refer only to its catalog. */
|
|
31
|
+
export function makeFleetBackend(exec = realExec, platform = process.platform) {
|
|
32
|
+
const host = fleetHostBackend(exec, platform);
|
|
33
|
+
return {
|
|
34
|
+
id: host.id,
|
|
35
|
+
async init(binPath) {
|
|
36
|
+
const messages = await host.init(binPath);
|
|
37
|
+
const { adoptConfiguredFleet } = await import('./adoption.js');
|
|
38
|
+
await adoptConfiguredFleet(binPath, exec, platform);
|
|
39
|
+
return messages;
|
|
40
|
+
},
|
|
41
|
+
async install(name, binPath) {
|
|
42
|
+
const { adoptConfiguredFleet } = await import('./adoption.js');
|
|
43
|
+
let configPath;
|
|
44
|
+
try {
|
|
45
|
+
configPath = readFileSync(join(agentDir(name), '.config-path'), 'utf8').trim() || undefined;
|
|
46
|
+
}
|
|
47
|
+
catch { /* a new state directory may not carry a config marker yet */ }
|
|
48
|
+
await adoptConfiguredFleet(binPath, exec, platform, configPath, name);
|
|
49
|
+
const result = await registerMember({ name, kind: 'permanent', dir: agentDir(name) });
|
|
50
|
+
try {
|
|
51
|
+
await waitMember(result.member.key, 'running', { exec });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Preserve existing intent. New registration rollback is exact and waits
|
|
55
|
+
// for process quiescence before its creation evidence can be discarded.
|
|
56
|
+
if (result.created)
|
|
57
|
+
await unregisterMember(result.member.key, { exec });
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
return { created: result.created, detail: `Fleet member ${name} registered` };
|
|
61
|
+
},
|
|
62
|
+
async start(name) {
|
|
63
|
+
const key = memberKey(name, 'permanent'), member = readMember(key);
|
|
64
|
+
if (!member)
|
|
65
|
+
throw Error('Fleet member not registered; use up');
|
|
66
|
+
await registerMember(member);
|
|
67
|
+
await ensureFleetParent(exec, platform);
|
|
68
|
+
await waitMember(key, 'running', { exec });
|
|
69
|
+
},
|
|
70
|
+
async stop(name) { await stopMember(memberKey(name, 'permanent'), { exec }); },
|
|
71
|
+
async restart(name) {
|
|
72
|
+
await this.stop(name);
|
|
73
|
+
await this.start(name);
|
|
74
|
+
},
|
|
75
|
+
async status(name) { return (await this.liveness(name)).detail; },
|
|
76
|
+
liveness(name) {
|
|
77
|
+
const key = memberKey(name, 'permanent');
|
|
78
|
+
if (!readMember(key) && existsSync(agentDir(name)))
|
|
79
|
+
return Promise.resolve({ state: 'unknown', detail: 'unmigrated member; preserving context until legacy inspection' });
|
|
80
|
+
return catalogLiveness(key, exec);
|
|
81
|
+
},
|
|
82
|
+
async inspect(name) { return { backend: host.id, ...await this.liveness(name), nativeState: 'fleet-member' }; },
|
|
83
|
+
async uninstall(name) {
|
|
84
|
+
const { retirePermanentRegistration } = await import('./adoption.js');
|
|
85
|
+
return { removed: await retirePermanentRegistration(name, exec), detail: `Fleet member ${name} unregistered` };
|
|
86
|
+
},
|
|
87
|
+
logsArgs(name, follow) { return { cmd: 'tail', args: [follow ? '-f' : '-n', ...(follow ? [] : ['200']), join(agentDir(name), 'supervisor.log')] }; },
|
|
88
|
+
};
|
|
89
|
+
}
|
package/dist/supervisor/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { realExec } from '../exec.js';
|
|
2
|
-
import {
|
|
3
|
-
import { makeLaunchdBackend } from './launchd.js';
|
|
2
|
+
import { makeFleetBackend } from './fleet.js';
|
|
4
3
|
import { makeNoneBackend } from './none.js';
|
|
5
4
|
export { makeSystemdBackend, unitFor } from './systemd.js';
|
|
6
5
|
export { makeLaunchdBackend, labelFor } from './launchd.js';
|
|
@@ -8,9 +7,7 @@ export { makeNoneBackend } from './none.js';
|
|
|
8
7
|
export function pickBackend(exec = realExec, platform = process.platform) {
|
|
9
8
|
if (process.env.OURS_FLEET_SUPERVISOR === 'none')
|
|
10
9
|
return makeNoneBackend(exec);
|
|
11
|
-
if (platform === 'darwin')
|
|
12
|
-
return
|
|
13
|
-
if (platform === 'linux')
|
|
14
|
-
return makeSystemdBackend(exec);
|
|
10
|
+
if (platform === 'darwin' || platform === 'linux')
|
|
11
|
+
return makeFleetBackend(exec, platform);
|
|
15
12
|
throw new Error(`unsupported platform '${platform}' (linux and darwin only)`);
|
|
16
13
|
}
|