@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.71
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 +34 -0
- package/dist/application/conversation-history.js +280 -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 +3 -3
- 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/resource-observer.d.ts +40 -0
- package/dist/application/resource-observer.js +278 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +1 -1
- package/dist/application/role-repository.js +30 -7
- 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 +261 -0
- package/dist/application/task-room-service.d.ts +101 -3
- package/dist/application/task-room-service.js +286 -33
- 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 +14 -9
- package/dist/daemon-recovery.js +124 -115
- 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 +9 -34
- package/dist/monitor.js +90 -153
- package/dist/notifications/fleet-producer.d.ts +22 -0
- package/dist/notifications/fleet-producer.js +237 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +11 -0
- package/dist/notifications/target-cleanup.js +63 -0
- package/dist/notifications/task-producer.d.ts +20 -0
- package/dist/notifications/task-producer.js +100 -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 +84 -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 +286 -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 +52 -8
- package/dist/rooms-tasks/task-state.js +251 -26
- 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 +410 -62
- 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 +77 -0
- package/dist/subscriptions/login.js +215 -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 +143 -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/watchdog/query.d.ts +3 -1
- package/dist/watchdog/query.js +2 -1
- 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/events.d.ts +2 -0
- package/dist/web/events.js +9 -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 +248 -0
- package/dist/web/resource-views.d.ts +124 -0
- package/dist/web/resource-views.js +424 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +132 -34
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.js +802 -67
- 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 +196 -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,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { publishFleetResourceCommit } from '../application/resource-observer.js';
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync, rmSync, renameSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { randomUUID } from 'node:crypto';
|
|
4
5
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
5
6
|
import { stateRoot } from '../paths.js';
|
|
7
|
+
import { planWorkspace, ensureWorkspace } from './workspace.js';
|
|
6
8
|
import { storedRoomLaunchPolicy, TASK_TERMINAL_STATES, TASK_CANCELLABLE_STATES } from './types.js';
|
|
7
9
|
import { DEFAULT_TASK_LIST_ID, readTaskLists } from './task-lists.js';
|
|
8
10
|
export const tasksDir = () => join(stateRoot(), 'tasks');
|
|
@@ -88,7 +90,77 @@ function writeTask(record) {
|
|
|
88
90
|
mkdirSync(tasksDir(), { recursive: true });
|
|
89
91
|
const persisted = { ...record };
|
|
90
92
|
delete persisted.list_name;
|
|
93
|
+
const path = taskPath(record.task_id);
|
|
94
|
+
const previous = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : undefined;
|
|
95
|
+
const status = (task) => task.deletion?.status === 'pending' ? 'deleting' : task.blocked ? `${task.state} (blocked)` : task.state;
|
|
96
|
+
// Read pending events under the same task lock as every mutation. Metadata edits
|
|
97
|
+
// and retries preserve the queue; acknowledgement cannot be resurrected by stale input.
|
|
98
|
+
const pending = [...(previous?.notification_events ?? [])];
|
|
99
|
+
if (!previous || status(previous) !== status(record)) {
|
|
100
|
+
const next = status(record), before = previous ? status(previous) : undefined;
|
|
101
|
+
pending.push({ eventId: `task:${record.task_id}:${randomUUID()}`, taskId: record.task_id,
|
|
102
|
+
title: `Task ${!previous ? 'created' : 'updated'}: ${record.title}`.slice(0, 160),
|
|
103
|
+
body: `Task ${record.task_id}: ${before ? `${before} → ` : 'created in '}${next}.`,
|
|
104
|
+
url: `/fleet/tasks/${record.task_id}` });
|
|
105
|
+
}
|
|
106
|
+
if (pending.length)
|
|
107
|
+
persisted.notification_events = pending;
|
|
108
|
+
else
|
|
109
|
+
delete persisted.notification_events;
|
|
91
110
|
replaceFileAtomically(taskPath(record.task_id), JSON.stringify(persisted, null, 2) + '\n');
|
|
111
|
+
publishFleetResourceCommit(taskPath(record.task_id));
|
|
112
|
+
}
|
|
113
|
+
const retiredNotificationsDir = () => join(tasksDir(), '.notification-retired');
|
|
114
|
+
/** Atomic snapshots contain the events, including intermediate states between polls. */
|
|
115
|
+
export function pendingTaskNotifications(warn = () => { }) {
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const dir of [tasksDir(), retiredNotificationsDir()]) {
|
|
118
|
+
if (!existsSync(dir))
|
|
119
|
+
continue;
|
|
120
|
+
for (const file of readdirSync(dir).filter(file => TASK_ID_PATTERN.test(file.slice(0, -5)) && file.endsWith('.json'))) {
|
|
121
|
+
try {
|
|
122
|
+
const path = join(dir, file), stat = lstatSync(path);
|
|
123
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
124
|
+
throw new Error('invalid notification source');
|
|
125
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
126
|
+
const id = file.slice(0, -5);
|
|
127
|
+
// A prepared deletion copy has no authority while the original still exists.
|
|
128
|
+
if (dir === retiredNotificationsDir() && existsSync(taskPath(id)))
|
|
129
|
+
continue;
|
|
130
|
+
if (value.notification_events === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
if (!Array.isArray(value.notification_events) || value.notification_events.some(e => !e
|
|
133
|
+
|| typeof e.eventId !== 'string' || !e.eventId.startsWith(`task:${id}:`) || e.eventId.length > 512
|
|
134
|
+
|| e.taskId !== id || (e.url !== `/fleet/tasks/${id}` && !(e.eventId === `task:${id}:deleted` && e.url === `/fleet/tasks?deletedTask=${id}`)) || typeof e.title !== 'string' || !e.title.trim() || e.title.length > 160
|
|
135
|
+
|| typeof e.body !== 'string' || !e.body.trim() || e.body.length > 512))
|
|
136
|
+
throw new Error('invalid notification events');
|
|
137
|
+
if (value.notification_events.length)
|
|
138
|
+
result.push({ taskId: id, events: value.notification_events });
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (!isNotFoundError(error))
|
|
142
|
+
warn('Task notification record unavailable; other task deliveries continue');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
/** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
|
|
149
|
+
export function acknowledgeTaskNotification(id, eventId) {
|
|
150
|
+
assertCanonicalTaskId(id);
|
|
151
|
+
withTaskLock(id, () => {
|
|
152
|
+
for (const path of [taskPath(id), join(retiredNotificationsDir(), `${id}.json`)]) {
|
|
153
|
+
if (!existsSync(path))
|
|
154
|
+
continue;
|
|
155
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
156
|
+
if (!value.notification_events?.some(event => event.eventId === eventId))
|
|
157
|
+
continue;
|
|
158
|
+
value.notification_events = value.notification_events.filter(event => event.eventId !== eventId);
|
|
159
|
+
replaceFileAtomically(path, JSON.stringify(value, null, 2) + '\n');
|
|
160
|
+
if (path.startsWith(retiredNotificationsDir() + '/') && !value.notification_events.length)
|
|
161
|
+
unlinkSync(path);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
92
164
|
}
|
|
93
165
|
function presentTask(record) {
|
|
94
166
|
const listId = record.list_id ?? DEFAULT_TASK_LIST_ID;
|
|
@@ -125,21 +197,32 @@ export function assertNoPendingDeletion(task) {
|
|
|
125
197
|
throw new TaskStateError(`task ${task.task_id} is pending deletion; run 'ours-fleet task delete ${task.task_id} ${task.task_id}' to retry cleanup`);
|
|
126
198
|
}
|
|
127
199
|
}
|
|
200
|
+
function creationPlan(plan, layout) {
|
|
201
|
+
return JSON.stringify([plan?.plan_hash ?? null, storedRoomLaunchPolicy(plan?.room_policy) ?? null,
|
|
202
|
+
layout ? `${layout.name}@${layout.definition_hash}` : null]);
|
|
203
|
+
}
|
|
128
204
|
export function createTask(input) {
|
|
129
205
|
const key = input.idempotency_key ?? randomUUID();
|
|
130
|
-
const existing =
|
|
206
|
+
const existing = listTasks({ includeDeleting: true }).find(task => task.idempotency_key === key);
|
|
207
|
+
const requestedPlan = creationPlan(input.execution_plan, input.layout);
|
|
131
208
|
if (existing) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const existingPolicy = storedRoomLaunchPolicy(existing.execution_plan?.room_policy);
|
|
135
|
-
const requestedPolicy = storedRoomLaunchPolicy(input.execution_plan?.room_policy);
|
|
136
|
-
if (existingPlan !== requestedPlan || JSON.stringify(existingPolicy) !== JSON.stringify(requestedPolicy))
|
|
209
|
+
// After a Backlog plan edit the current plan differs from the one created; retries compare the original request.
|
|
210
|
+
if ((existing.creation_plan ?? creationPlan(existing.execution_plan, existing.layout)) !== requestedPlan)
|
|
137
211
|
throw new TaskStateError(`idempotency key '${key}' was already used with a different execution plan`);
|
|
138
|
-
return existing
|
|
212
|
+
return withTaskLock(existing.task_id, () => {
|
|
213
|
+
const fresh = readTask(existing.task_id);
|
|
214
|
+
assertNoPendingDeletion(fresh);
|
|
215
|
+
if (fresh.workspace)
|
|
216
|
+
ensureWorkspace(fresh.workspace);
|
|
217
|
+
return fresh;
|
|
218
|
+
});
|
|
139
219
|
}
|
|
220
|
+
if (input.layout && (input.execution_plan || input.no_room || input.room_id))
|
|
221
|
+
throw new TaskStateError('a layout task cannot also use a room template, no-room mode or a room');
|
|
140
222
|
const state = input.start === false ? 'backlog' : 'provisioning';
|
|
223
|
+
const taskId = generateTaskId();
|
|
141
224
|
const record = {
|
|
142
|
-
task_id:
|
|
225
|
+
task_id: taskId,
|
|
143
226
|
list_id: input.listId ?? DEFAULT_TASK_LIST_ID,
|
|
144
227
|
title: input.title,
|
|
145
228
|
brief: input.brief,
|
|
@@ -148,14 +231,18 @@ export function createTask(input) {
|
|
|
148
231
|
template: input.template,
|
|
149
232
|
execution_plan: input.execution_plan,
|
|
150
233
|
no_room: input.no_room || undefined,
|
|
234
|
+
...(input.layout ? { layout: { name: input.layout.name, run_id: `task-${taskId}`, definition_hash: input.layout.definition_hash } } : {}),
|
|
151
235
|
room_id: input.room_id,
|
|
152
236
|
member_roles: [],
|
|
153
237
|
origin: input.origin,
|
|
154
238
|
idempotency_key: key,
|
|
239
|
+
creation_plan: requestedPlan,
|
|
155
240
|
created_at: new Date().toISOString(),
|
|
156
241
|
started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
|
|
157
242
|
};
|
|
243
|
+
record.workspace = planWorkspace('task', record.task_id);
|
|
158
244
|
writeTask(record);
|
|
245
|
+
ensureWorkspace(record.workspace);
|
|
159
246
|
return readTask(record.task_id);
|
|
160
247
|
}
|
|
161
248
|
export function getTask(id) { return withTaskLock(id, () => readTask(id)); }
|
|
@@ -195,7 +282,10 @@ export function listTasks(filter) {
|
|
|
195
282
|
if ((!states || states.includes(t.state)) && (!filter?.listId || t.list_id === filter.listId))
|
|
196
283
|
tasks.push(t);
|
|
197
284
|
}
|
|
198
|
-
catch {
|
|
285
|
+
catch (error) {
|
|
286
|
+
if (filter?.strict)
|
|
287
|
+
throw error; /* legacy CLI inventory tolerates corrupt files */
|
|
288
|
+
}
|
|
199
289
|
}
|
|
200
290
|
const order = new Map(readTaskLists().map((item, index) => [item.list_id, index]));
|
|
201
291
|
return tasks.sort((a, b) => (order.get(a.list_id) - order.get(b.list_id))
|
|
@@ -505,7 +595,8 @@ function writeDeletionReceiptForIntent(stored) {
|
|
|
505
595
|
actor: deletion.actor,
|
|
506
596
|
original_state: stored.state,
|
|
507
597
|
room_id: deletion.room_id,
|
|
508
|
-
member_count: deletion.members.length,
|
|
598
|
+
member_count: deletion.members.length + (deletion.archived_absences?.length ?? 0),
|
|
599
|
+
...(deletion.archived_absences?.length ? { archived_absences: deletion.archived_absences } : {}),
|
|
509
600
|
};
|
|
510
601
|
replaceFileAtomically(deletionReceiptPath(stored.task_id), JSON.stringify(receipt, null, 2) + '\n');
|
|
511
602
|
}
|
|
@@ -525,14 +616,10 @@ export function ensureTaskDeletionReceipt(id) {
|
|
|
525
616
|
writeDeletionReceiptForIntent(stored);
|
|
526
617
|
});
|
|
527
618
|
}
|
|
528
|
-
/**
|
|
619
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
529
620
|
export function completeTaskDeletionReceipt(id) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return;
|
|
533
|
-
receipt.settled_at = new Date().toISOString();
|
|
534
|
-
receipt.result = 'deleted';
|
|
535
|
-
replaceFileAtomically(deletionReceiptPath(id), JSON.stringify(receipt, null, 2) + '\n');
|
|
621
|
+
assertCanonicalTaskId(id);
|
|
622
|
+
rmSync(deletionReceiptPath(id), { force: true });
|
|
536
623
|
}
|
|
537
624
|
/**
|
|
538
625
|
* Persist the first-wins durable deletion intent. Accepts every lifecycle
|
|
@@ -551,8 +638,10 @@ export function beginTaskDeletionIntent(id, actor) {
|
|
|
551
638
|
stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
552
639
|
}
|
|
553
640
|
catch (error) {
|
|
554
|
-
if (isNotFoundError(error))
|
|
641
|
+
if (isNotFoundError(error)) {
|
|
642
|
+
completeTaskDeletionReceipt(id);
|
|
555
643
|
return { status: 'already_absent' };
|
|
644
|
+
}
|
|
556
645
|
throw error;
|
|
557
646
|
}
|
|
558
647
|
if (stored.deletion?.status === 'pending') {
|
|
@@ -649,6 +738,8 @@ function findCursorForSeat(id, stored, seat) {
|
|
|
649
738
|
if (cursor && seat.identity_cid
|
|
650
739
|
&& cursor.identity_cid.toLowerCase() !== seat.identity_cid.toLowerCase())
|
|
651
740
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' identity CID mismatch between cursor and room seat`);
|
|
741
|
+
if (cursor?.action_id && seat.launch?.action_id && cursor.action_id !== seat.launch.action_id)
|
|
742
|
+
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' creation ownership proof changed`);
|
|
652
743
|
return cursor;
|
|
653
744
|
}
|
|
654
745
|
/**
|
|
@@ -669,11 +760,20 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
|
|
|
669
760
|
for (const seat of seats) {
|
|
670
761
|
if (!seat.identity_cid)
|
|
671
762
|
continue;
|
|
672
|
-
|
|
763
|
+
const cursor = findCursorForSeat(id, stored, seat);
|
|
764
|
+
if (!cursor) {
|
|
673
765
|
stored.deletion.members.push({
|
|
674
766
|
name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now,
|
|
767
|
+
...(seat.launch?.action_id ? { action_id: seat.launch.action_id } : {}),
|
|
768
|
+
...(seat.launch?.task_supervised ? { task_supervised: true } : {}),
|
|
675
769
|
});
|
|
676
770
|
}
|
|
771
|
+
else {
|
|
772
|
+
if (seat.launch?.action_id)
|
|
773
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
774
|
+
if (seat.launch?.task_supervised)
|
|
775
|
+
cursor.task_supervised = true;
|
|
776
|
+
}
|
|
677
777
|
}
|
|
678
778
|
writeTask(stored);
|
|
679
779
|
return presentTaskLenient(stored);
|
|
@@ -688,10 +788,10 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
|
|
|
688
788
|
*
|
|
689
789
|
* The room saga is trusted for phase jumps, but partial or corrupt retained
|
|
690
790
|
* records must not become false success: every seat must be identity_absent;
|
|
691
|
-
* a real launch requires archive evidence; an identity-less seat
|
|
692
|
-
*
|
|
791
|
+
* a real launch requires archive evidence; an identity-less seat requires
|
|
792
|
+
* never-launched proof or a freshly verified archived-absence checkpoint.
|
|
693
793
|
*/
|
|
694
|
-
export function importTaskDeletionRetirementEvidence(id, seats) {
|
|
794
|
+
export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
695
795
|
return withTaskLock(id, () => {
|
|
696
796
|
assertCanonicalTaskId(id);
|
|
697
797
|
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
@@ -702,12 +802,29 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
|
|
|
702
802
|
const evidence = seat.retirement;
|
|
703
803
|
if (evidence?.phase !== 'identity_absent')
|
|
704
804
|
throw new TaskStateError(`task ${id} deletion cannot checkpoint member '${seat.role_name}' before completed retirement`);
|
|
705
|
-
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
805
|
+
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
806
|
+
|| evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
|
|
706
807
|
if (!neverLaunched && evidence.archive_path === undefined)
|
|
707
808
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
|
|
708
809
|
if (!seat.identity_cid) {
|
|
709
|
-
if (neverLaunched)
|
|
710
|
-
|
|
810
|
+
if (neverLaunched) {
|
|
811
|
+
stored.deletion.absent_members ??= [];
|
|
812
|
+
if (!stored.deletion.absent_members.includes(seat.role_name))
|
|
813
|
+
stored.deletion.absent_members.push(seat.role_name);
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const proof = proofs.find(value => value.name === seat.role_name);
|
|
817
|
+
if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
|
|
818
|
+
&& proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
|
|
819
|
+
const existing = stored.deletion.archived_absences?.find(value => value.name === proof.name);
|
|
820
|
+
if (existing && (existing.launch_id !== proof.launch_id || existing.action_id !== proof.action_id
|
|
821
|
+
|| existing.archive_path !== proof.archive_path))
|
|
822
|
+
throw new TaskStateError('Archived absence checkpoint ownership is immutable');
|
|
823
|
+
stored.deletion.archived_absences ??= [];
|
|
824
|
+
if (!existing)
|
|
825
|
+
stored.deletion.archived_absences.push({ ...proof });
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
711
828
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' has retirement evidence but no identity CID`);
|
|
712
829
|
}
|
|
713
830
|
let cursor = findCursorForSeat(id, stored, seat);
|
|
@@ -715,6 +832,10 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
|
|
|
715
832
|
cursor = { name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now };
|
|
716
833
|
stored.deletion.members.push(cursor);
|
|
717
834
|
}
|
|
835
|
+
if (seat.launch?.action_id)
|
|
836
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
837
|
+
if (seat.launch?.task_supervised)
|
|
838
|
+
cursor.task_supervised = true;
|
|
718
839
|
if (DELETION_MEMBER_PHASE_ORDER[evidence.phase] < DELETION_MEMBER_PHASE_ORDER[cursor.phase])
|
|
719
840
|
continue;
|
|
720
841
|
if (cursor.launch_id !== undefined && cursor.launch_id !== evidence.launch_id)
|
|
@@ -729,9 +850,23 @@ export function importTaskDeletionRetirementEvidence(id, seats) {
|
|
|
729
850
|
cursor.updated_at = now;
|
|
730
851
|
}
|
|
731
852
|
writeTask(stored);
|
|
853
|
+
writeDeletionReceiptForIntent(stored);
|
|
732
854
|
return presentTaskLenient(stored);
|
|
733
855
|
});
|
|
734
856
|
}
|
|
857
|
+
/** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
|
|
858
|
+
* From here retries still verify live absence, but owned archives may be consumed.
|
|
859
|
+
*/
|
|
860
|
+
export function beginTaskWorkspaceCleanup(id) {
|
|
861
|
+
withTaskLock(id, () => {
|
|
862
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
863
|
+
if (stored.deletion?.status !== 'pending'
|
|
864
|
+
|| stored.deletion.members.some(member => member.phase !== 'identity_absent'))
|
|
865
|
+
throw new TaskStateError('Task is not ready for workspace cleanup');
|
|
866
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
867
|
+
writeTask(stored);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
735
870
|
/**
|
|
736
871
|
* Physically remove a deletion-pending task record. Settlement-only: callers
|
|
737
872
|
* must have completed member retirement and room cleanup first. Missing
|
|
@@ -751,6 +886,12 @@ export function unlinkDeletedTask(id) {
|
|
|
751
886
|
}
|
|
752
887
|
if (stored.deletion?.status !== 'pending')
|
|
753
888
|
throw new TaskStateError(`task ${id} has no pending deletion`);
|
|
889
|
+
// Preserve committed deliveries outside the record before deletion. A crash
|
|
890
|
+
// before unlink may replay two copies; both carry identical service dedupe IDs.
|
|
891
|
+
const removed = { eventId: `task:${id}:deleted`, taskId: id,
|
|
892
|
+
title: `Task deleted: ${stored.title}`.slice(0, 160), body: `Task ${id}: deleting → deleted.`,
|
|
893
|
+
url: `/fleet/tasks?deletedTask=${id}` };
|
|
894
|
+
replaceFileAtomically(join(retiredNotificationsDir(), `${id}.json`), JSON.stringify({ notification_events: [...(stored.notification_events ?? []), removed] }) + '\n');
|
|
754
895
|
try {
|
|
755
896
|
unlinkSync(taskPath(id));
|
|
756
897
|
}
|
|
@@ -762,3 +903,87 @@ export function unlinkDeletedTask(id) {
|
|
|
762
903
|
return true;
|
|
763
904
|
});
|
|
764
905
|
}
|
|
906
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
907
|
+
export function settleAbsentTaskDeletionMember(id, name) {
|
|
908
|
+
withTaskLock(id, () => {
|
|
909
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
910
|
+
if (stored.deletion?.status !== 'pending')
|
|
911
|
+
throw new TaskStateError('Task is not deleting');
|
|
912
|
+
const member = stored.deletion.members.find(value => value.name === name);
|
|
913
|
+
if (!member)
|
|
914
|
+
throw new TaskStateError('Unknown deletion member');
|
|
915
|
+
member.phase = 'identity_absent';
|
|
916
|
+
member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
|
|
917
|
+
member.updated_at = new Date().toISOString();
|
|
918
|
+
writeTask(stored);
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
922
|
+
export function detachDeletedRoom(taskId, roomId) {
|
|
923
|
+
withTaskLock(taskId, () => {
|
|
924
|
+
if (!existsSync(taskPath(taskId)))
|
|
925
|
+
return;
|
|
926
|
+
const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
|
|
927
|
+
if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
|
|
928
|
+
return;
|
|
929
|
+
delete stored.room_id;
|
|
930
|
+
delete stored.room_identity_cid;
|
|
931
|
+
stored.member_roles = [];
|
|
932
|
+
writeTask(stored);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
936
|
+
export function beginTaskArchiveCleanup(id) {
|
|
937
|
+
withTaskLock(id, () => {
|
|
938
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
939
|
+
if (stored.deletion?.status !== 'pending')
|
|
940
|
+
throw new TaskStateError('Task is not deleting');
|
|
941
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
942
|
+
writeTask(stored);
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
/** Metadata-only edit; compare under the task lock to prevent lost updates. */
|
|
946
|
+
/**
|
|
947
|
+
* Choose, change or clear a Backlog task's Room Layout before it starts.
|
|
948
|
+
* `expected` is the layout name the caller saw (null = none); a different
|
|
949
|
+
* current choice is a stale edit. Choosing a layout drops the sealed room
|
|
950
|
+
* template plan; the caller releases the returned launch snapshot hash.
|
|
951
|
+
*/
|
|
952
|
+
export function updateTaskLayout(id, expected, next) {
|
|
953
|
+
return withTaskLock(id, () => {
|
|
954
|
+
const task = readTask(id);
|
|
955
|
+
assertNoPendingDeletion(task);
|
|
956
|
+
assertNoPendingTerminalIntent(task);
|
|
957
|
+
if (task.state !== 'backlog' || task.room_id)
|
|
958
|
+
throw new TaskStateError(`task ${id} has started; its room layout is fixed (state '${task.state}')`);
|
|
959
|
+
const current = task.layout?.name ?? null;
|
|
960
|
+
// A replay of the same choice is idempotent (and refreshes the pinned source hash).
|
|
961
|
+
if (current !== expected && current !== (next?.name ?? null))
|
|
962
|
+
throw new TaskStateError(`task ${id} room layout changed to '${current ?? 'one room'}'; reload before changing it`);
|
|
963
|
+
const releasedSnapshot = next ? task.execution_plan?.snapshot.launch_snapshot_hash : undefined;
|
|
964
|
+
if (next) {
|
|
965
|
+
task.layout = { name: next.name, run_id: `task-${task.task_id}`, definition_hash: next.definition_hash };
|
|
966
|
+
delete task.execution_plan;
|
|
967
|
+
delete task.template;
|
|
968
|
+
delete task.no_room;
|
|
969
|
+
}
|
|
970
|
+
else
|
|
971
|
+
delete task.layout;
|
|
972
|
+
task.room_plan_changed_at = new Date().toISOString();
|
|
973
|
+
writeTask(task);
|
|
974
|
+
return { task: readTask(id), ...(releasedSnapshot ? { releasedSnapshot } : {}) };
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
export function updateTaskBrief(id, brief, expectedBrief) {
|
|
978
|
+
assertCanonicalTaskId(id);
|
|
979
|
+
return withTaskLock(id, () => {
|
|
980
|
+
const task = readTask(id);
|
|
981
|
+
assertNoPendingDeletion(task);
|
|
982
|
+
assertNoPendingTerminalIntent(task);
|
|
983
|
+
if ((task.brief ?? '') !== expectedBrief && task.brief !== brief)
|
|
984
|
+
throw new TaskStateError('Description changed elsewhere. Reload the task before saving.');
|
|
985
|
+
task.brief = brief;
|
|
986
|
+
writeTask(task);
|
|
987
|
+
return task;
|
|
988
|
+
});
|
|
989
|
+
}
|
|
@@ -62,6 +62,8 @@ export type TaskDeletionMemberPhase = 'pending' | 'stop_requested' | 'liveness_a
|
|
|
62
62
|
export interface TaskDeletionMemberCursor {
|
|
63
63
|
name: string;
|
|
64
64
|
identity_cid: string;
|
|
65
|
+
action_id?: string;
|
|
66
|
+
task_supervised?: boolean;
|
|
65
67
|
phase: TaskDeletionMemberPhase;
|
|
66
68
|
launch_id?: string;
|
|
67
69
|
archive_path?: string;
|
|
@@ -73,6 +75,13 @@ export interface TaskDeletionMemberCursor {
|
|
|
73
75
|
* evidence. While pending, the task is hidden from normal operation and every
|
|
74
76
|
* lifecycle mutation or room publication is rejected.
|
|
75
77
|
*/
|
|
78
|
+
export interface ArchivedMemberAbsence {
|
|
79
|
+
name: string;
|
|
80
|
+
launch_id: string;
|
|
81
|
+
action_id: string;
|
|
82
|
+
archive_path: string;
|
|
83
|
+
checked_at: string;
|
|
84
|
+
}
|
|
76
85
|
export interface TaskDeletionIntent {
|
|
77
86
|
status: 'pending';
|
|
78
87
|
accepted_at: string;
|
|
@@ -80,13 +89,34 @@ export interface TaskDeletionIntent {
|
|
|
80
89
|
room_id?: string;
|
|
81
90
|
/** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
|
|
82
91
|
members: TaskDeletionMemberCursor[];
|
|
92
|
+
archived_absences?: ArchivedMemberAbsence[];
|
|
93
|
+
absent_members?: string[];
|
|
94
|
+
/** Archive proofs were verified before owned workspace cleanup may consume them. */
|
|
95
|
+
workspace_cleanup_started_at?: string;
|
|
83
96
|
error?: string;
|
|
84
97
|
error_at?: string;
|
|
85
98
|
recovery_hint?: string;
|
|
86
99
|
first_failure?: string;
|
|
87
100
|
first_recovery_hint?: string;
|
|
88
101
|
}
|
|
102
|
+
export interface TaskLayoutLink {
|
|
103
|
+
name: string;
|
|
104
|
+
/** Deterministic `task-<task_id>` Room Layout run ID. */
|
|
105
|
+
run_id: string;
|
|
106
|
+
/** sha256 of the canonical source layout definition the run must snapshot. */
|
|
107
|
+
definition_hash: string;
|
|
108
|
+
}
|
|
109
|
+
export interface TaskNotificationEvent {
|
|
110
|
+
eventId: string;
|
|
111
|
+
title: string;
|
|
112
|
+
body: string;
|
|
113
|
+
url: string;
|
|
114
|
+
taskId: string;
|
|
115
|
+
}
|
|
89
116
|
export interface TaskRecord {
|
|
117
|
+
/** Pending lifecycle events, committed atomically with their task change. */
|
|
118
|
+
notification_events?: TaskNotificationEvent[];
|
|
119
|
+
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
90
120
|
task_id: string;
|
|
91
121
|
/** Stable organizational list identifier. Missing legacy values mean `default`. */
|
|
92
122
|
list_id: string;
|
|
@@ -107,6 +137,12 @@ export interface TaskRecord {
|
|
|
107
137
|
plan_hash: string;
|
|
108
138
|
};
|
|
109
139
|
no_room?: boolean;
|
|
140
|
+
/** Multi-room task: rooms and participants come from a Room Layout run. */
|
|
141
|
+
layout?: TaskLayoutLink;
|
|
142
|
+
/** Set when the room plan (layout or one-room template) was changed after creation, before start. */
|
|
143
|
+
room_plan_changed_at?: string;
|
|
144
|
+
/** Fingerprint of the room plan requested at creation; idempotent retries compare against it after later plan edits. */
|
|
145
|
+
creation_plan?: string;
|
|
110
146
|
room_id?: string;
|
|
111
147
|
room_identity_cid?: string;
|
|
112
148
|
member_roles: TaskMemberRole[];
|
|
@@ -145,6 +181,7 @@ export interface RoomRoleBriefingDefinition {
|
|
|
145
181
|
last_error?: string;
|
|
146
182
|
}
|
|
147
183
|
export interface RoomMemberLaunchState {
|
|
184
|
+
task_supervised?: boolean;
|
|
148
185
|
state: 'pending' | 'intent' | 'launched' | 'stopped' | 'failed';
|
|
149
186
|
attempt: number;
|
|
150
187
|
action_id?: string;
|
|
@@ -201,6 +238,7 @@ export type RoomHistoryEvidence = {
|
|
|
201
238
|
};
|
|
202
239
|
export type MemberRetirementPhase = 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
|
|
203
240
|
export interface MemberRetirement {
|
|
241
|
+
absence_verified?: boolean;
|
|
204
242
|
phase: MemberRetirementPhase;
|
|
205
243
|
launch_id: string;
|
|
206
244
|
updated_at: string;
|
|
@@ -227,12 +265,14 @@ export interface RoomMemberSeat {
|
|
|
227
265
|
retirement?: MemberRetirement;
|
|
228
266
|
}
|
|
229
267
|
export interface RoomOrchestrationRecord {
|
|
268
|
+
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
230
269
|
room_id: string;
|
|
231
270
|
room_identity_cid?: string;
|
|
232
271
|
room_name: string;
|
|
233
272
|
goal?: string;
|
|
234
273
|
task_id?: string;
|
|
235
274
|
template_snapshot?: TemplateSnapshot;
|
|
275
|
+
additional_member_templates?: TemplateSnapshot[];
|
|
236
276
|
/** Resolved once at launch; legacy absence means non-anonymous. */
|
|
237
277
|
room_policy?: RoomLaunchPolicy;
|
|
238
278
|
saga: SagaCursor;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type OwnedWorkspace } from './workspace.js';
|
|
2
|
+
/** Runtime control state stays in Fleet's live roster and recovery store. Its
|
|
3
|
+
* durable launch descriptor, not a name prefix or caller-supplied path, proves
|
|
4
|
+
* artifact ownership. Audit receipts/journals intentionally outlive deletion.
|
|
5
|
+
*/
|
|
6
|
+
export declare function collectWorkspaceArchives(workspace: OwnedWorkspace): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { stateRoot } from '../paths.js';
|
|
5
|
+
import { assertSafeAncestors, auditWorkspaceGit, validateWorkspace, ensureWorkspace, WorkspaceError } from './workspace.js';
|
|
6
|
+
/** Runtime control state stays in Fleet's live roster and recovery store. Its
|
|
7
|
+
* durable launch descriptor, not a name prefix or caller-supplied path, proves
|
|
8
|
+
* artifact ownership. Audit receipts/journals intentionally outlive deletion.
|
|
9
|
+
*/
|
|
10
|
+
export function collectWorkspaceArchives(workspace) {
|
|
11
|
+
validateWorkspace(workspace);
|
|
12
|
+
const root = join(stateRoot(), 'recovery', 'temporary');
|
|
13
|
+
assertSafeAncestors(root);
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
if (e.code === 'ENOENT')
|
|
20
|
+
return;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
const owned = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
26
|
+
continue;
|
|
27
|
+
const path = join(root, entry.name);
|
|
28
|
+
const roleFile = join(path, 'role.yaml');
|
|
29
|
+
let role;
|
|
30
|
+
try {
|
|
31
|
+
const s = lstatSync(roleFile);
|
|
32
|
+
if (!s.isFile() || s.isSymbolicLink())
|
|
33
|
+
continue;
|
|
34
|
+
role = parse(readFileSync(roleFile, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
} // no ownership proof: never adopt a foreign archive
|
|
39
|
+
const recorded = role?.roomMemberStartup?.workspace;
|
|
40
|
+
if (recorded?.owner !== workspace.owner || recorded.id !== workspace.id)
|
|
41
|
+
continue;
|
|
42
|
+
if (JSON.stringify(recorded) !== JSON.stringify(workspace))
|
|
43
|
+
throw new WorkspaceError('workspace archive ownership mismatch');
|
|
44
|
+
assertSafeAncestors(path);
|
|
45
|
+
const termination = join(path, 'termination.jsonl');
|
|
46
|
+
if (!lstatSync(termination).isFile() || lstatSync(termination).isSymbolicLink())
|
|
47
|
+
throw new WorkspaceError('unsafe workspace archive termination evidence');
|
|
48
|
+
const supervisorPath = join(path, '.temp-supervisor.json');
|
|
49
|
+
let supervisor;
|
|
50
|
+
try {
|
|
51
|
+
if (!lstatSync(supervisorPath).isFile() || lstatSync(supervisorPath).isSymbolicLink())
|
|
52
|
+
throw new WorkspaceError('unsafe workspace archive supervisor evidence');
|
|
53
|
+
supervisor = JSON.parse(readFileSync(supervisorPath, 'utf8'));
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (e.code !== 'ENOENT')
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
if (supervisor && (supervisor.role !== role.name || !supervisor.launchId))
|
|
60
|
+
throw new WorkspaceError('workspace archive launch ownership mismatch');
|
|
61
|
+
const terminated = readFileSync(termination, 'utf8').trim().split('\n').some(line => {
|
|
62
|
+
const value = JSON.parse(line);
|
|
63
|
+
return value.version === 1 && value.role === role.name
|
|
64
|
+
&& value.launchId === supervisor?.launchId
|
|
65
|
+
&& ['retired', 'reclaimed', 'failed'].includes(value.outcome);
|
|
66
|
+
});
|
|
67
|
+
if (!terminated || entry.name.endsWith('.retiring'))
|
|
68
|
+
throw new WorkspaceError('workspace archive retirement is incomplete');
|
|
69
|
+
auditWorkspaceGit(path, { forDeletion: true });
|
|
70
|
+
owned.push(path);
|
|
71
|
+
}
|
|
72
|
+
// Preflight the entire set before mutation; task/room record remains until all succeed.
|
|
73
|
+
if (!owned.length)
|
|
74
|
+
return;
|
|
75
|
+
ensureWorkspace(workspace);
|
|
76
|
+
const destination = join(workspace.path, '.fleet-retired-agents');
|
|
77
|
+
assertSafeAncestors(destination);
|
|
78
|
+
mkdirSync(destination, { recursive: true, mode: 0o700 });
|
|
79
|
+
for (const path of owned) {
|
|
80
|
+
const target = join(destination, basename(path));
|
|
81
|
+
try {
|
|
82
|
+
lstatSync(target);
|
|
83
|
+
throw new WorkspaceError('archive collection destination exists');
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
if (e.code !== 'ENOENT')
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
// Atomic transfer before workspace deletion: a crash never loses ownership
|
|
90
|
+
// evidence halfway through recursively removing a global recovery archive.
|
|
91
|
+
renameSync(path, target);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Persisted before allocation. Never infer ownership from an Agent's cwd. */
|
|
2
|
+
export interface OwnedWorkspace {
|
|
3
|
+
version: 1;
|
|
4
|
+
owner: 'task' | 'room';
|
|
5
|
+
id: string;
|
|
6
|
+
path: string;
|
|
7
|
+
token: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class WorkspaceError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export declare function assertSafeAncestors(path: string): void;
|
|
12
|
+
export declare function planWorkspace(owner: OwnedWorkspace['owner'], id: string): OwnedWorkspace;
|
|
13
|
+
export declare function validateWorkspace(w: OwnedWorkspace, owner?: "task" | "room", id?: string): void;
|
|
14
|
+
/** Read-only recovery proof; never allocate a replacement for a missing workspace. */
|
|
15
|
+
export declare function assertWorkspacePresent(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
|
|
16
|
+
/** Caller must first prove the exact workspace marker or retired archive
|
|
17
|
+
* provenance, and hold its lifecycle lock with all managed writers retired.
|
|
18
|
+
* Directories need owner rwx for recursive removal; ordinary files do not.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prepareOwnedDeletionTree(path: string): void;
|
|
21
|
+
/** Atomic directory publication; retry uses the durable token, never adopts another folder. */
|
|
22
|
+
export declare function ensureWorkspace(w: OwnedWorkspace): string;
|
|
23
|
+
/** Audit both directions of Git worktree registration, without executing repository config/hooks.
|
|
24
|
+
* All registrations and common dirs must be inside this workspace. Removing the complete
|
|
25
|
+
* workspace then removes their registries atomically with the owned repositories; never prune.
|
|
26
|
+
*/
|
|
27
|
+
export declare function auditWorkspaceGit(path: string, options?: {
|
|
28
|
+
forDeletion?: boolean;
|
|
29
|
+
}): void;
|
|
30
|
+
/** Called only after all managed writers retire, under the owner's lifecycle lock.
|
|
31
|
+
* The tombstone is derived from the durable unguessable token. A crash during rm
|
|
32
|
+
* resumes that exact tombstone even if rm already removed its marker.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assertWorkspaceDeletable(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
|
|
35
|
+
export declare function deleteWorkspace(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
|