@ours.network/fleet 1.2.0-nightly.8 → 1.2.0-nightly.80
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 +5 -0
- package/dist/account-origin.js +31 -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 +294 -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 +43 -0
- package/dist/application/resource-observer.js +323 -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 +63 -17
- package/dist/application/session-control.js +221 -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 +31 -0
- package/dist/notifications/fleet-producer.js +351 -0
- package/dist/notifications/outbox.d.ts +59 -0
- package/dist/notifications/outbox.js +199 -0
- package/dist/notifications/target-cleanup.d.ts +16 -0
- package/dist/notifications/target-cleanup.js +75 -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 +5 -0
- package/dist/paths.js +5 -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 +4 -0
- package/dist/rooms-tasks/archived-absence.js +11 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +224 -36
- 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 +63 -10
- 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 +43 -5
- package/dist/rooms-tasks/task-state.js +244 -22
- package/dist/rooms-tasks/types.d.ts +35 -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 +219 -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 +213 -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 +130 -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 +26 -0
- package/dist/web/conversation-stream.js +239 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- package/dist/web/fleet-config-service.d.ts +7 -1
- package/dist/web/fleet-config-service.js +61 -16
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +248 -0
- package/dist/web/resource-views.d.ts +135 -0
- package/dist/web/resource-views.js +486 -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 +821 -68
- 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 +605 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +238 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +216 -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,88 @@ 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
|
+
target: { kind: 'task', taskId: 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
|
+
function typedTaskNotification(value, id) {
|
|
115
|
+
if (!value || typeof value !== 'object' || Object.keys(value).some(key => !['eventId', 'title', 'body', 'target', 'taskId'].includes(key)))
|
|
116
|
+
return false;
|
|
117
|
+
const target = value.target;
|
|
118
|
+
return typeof value.eventId === 'string' && value.eventId.startsWith(`task:${id}:`) && value.eventId.length <= 512
|
|
119
|
+
&& value.taskId === id && !!target && typeof target === 'object' && !Array.isArray(target)
|
|
120
|
+
&& Object.keys(target).every(key => ['kind', 'taskId'].includes(key)) && target.taskId === id
|
|
121
|
+
&& (target.kind === 'task' || (target.kind === 'task-deleted' && value.eventId === `task:${id}:deleted`))
|
|
122
|
+
&& typeof value.title === 'string' && !!value.title.trim() && value.title.length <= 160
|
|
123
|
+
&& typeof value.body === 'string' && !!value.body.trim() && value.body.length <= 512;
|
|
124
|
+
}
|
|
125
|
+
/** Atomic snapshots contain the events, including intermediate states between polls. */
|
|
126
|
+
export function pendingTaskNotifications(warn = () => { }) {
|
|
127
|
+
const result = [];
|
|
128
|
+
for (const dir of [tasksDir(), retiredNotificationsDir()]) {
|
|
129
|
+
if (!existsSync(dir))
|
|
130
|
+
continue;
|
|
131
|
+
for (const file of readdirSync(dir).filter(file => TASK_ID_PATTERN.test(file.slice(0, -5)) && file.endsWith('.json'))) {
|
|
132
|
+
try {
|
|
133
|
+
const path = join(dir, file), stat = lstatSync(path);
|
|
134
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
135
|
+
throw new Error('invalid notification source');
|
|
136
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
137
|
+
const id = file.slice(0, -5);
|
|
138
|
+
// A prepared deletion copy has no authority while the original still exists.
|
|
139
|
+
if (dir === retiredNotificationsDir() && existsSync(taskPath(id)))
|
|
140
|
+
continue;
|
|
141
|
+
if (value.notification_events === undefined)
|
|
142
|
+
continue;
|
|
143
|
+
if (!Array.isArray(value.notification_events))
|
|
144
|
+
throw new Error('invalid notification events');
|
|
145
|
+
// Only typed task events are delivered. Unsupported entries are never sent or converted,
|
|
146
|
+
// and they do not block later lifecycle events.
|
|
147
|
+
const events = value.notification_events.filter(event => typedTaskNotification(event, id));
|
|
148
|
+
if (events.length)
|
|
149
|
+
result.push({ taskId: id, events });
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (!isNotFoundError(error))
|
|
153
|
+
warn('Task notification record unavailable; other task deliveries continue');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
|
|
160
|
+
export function acknowledgeTaskNotification(id, eventId) {
|
|
161
|
+
assertCanonicalTaskId(id);
|
|
162
|
+
withTaskLock(id, () => {
|
|
163
|
+
for (const path of [taskPath(id), join(retiredNotificationsDir(), `${id}.json`)]) {
|
|
164
|
+
if (!existsSync(path))
|
|
165
|
+
continue;
|
|
166
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
167
|
+
if (!value.notification_events?.some(event => typedTaskNotification(event, id) && event.eventId === eventId))
|
|
168
|
+
continue;
|
|
169
|
+
value.notification_events = value.notification_events.filter(event => !typedTaskNotification(event, id) || event.eventId !== eventId);
|
|
170
|
+
replaceFileAtomically(path, JSON.stringify(value, null, 2) + '\n');
|
|
171
|
+
if (path.startsWith(retiredNotificationsDir() + '/') && !value.notification_events.length)
|
|
172
|
+
unlinkSync(path);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
92
175
|
}
|
|
93
176
|
function presentTask(record) {
|
|
94
177
|
const listId = record.list_id ?? DEFAULT_TASK_LIST_ID;
|
|
@@ -125,21 +208,32 @@ export function assertNoPendingDeletion(task) {
|
|
|
125
208
|
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
209
|
}
|
|
127
210
|
}
|
|
211
|
+
function creationPlan(plan, layout) {
|
|
212
|
+
return JSON.stringify([plan?.plan_hash ?? null, storedRoomLaunchPolicy(plan?.room_policy) ?? null,
|
|
213
|
+
layout ? `${layout.name}@${layout.definition_hash}` : null]);
|
|
214
|
+
}
|
|
128
215
|
export function createTask(input) {
|
|
129
216
|
const key = input.idempotency_key ?? randomUUID();
|
|
130
|
-
const existing =
|
|
217
|
+
const existing = listTasks({ includeDeleting: true }).find(task => task.idempotency_key === key);
|
|
218
|
+
const requestedPlan = creationPlan(input.execution_plan, input.layout);
|
|
131
219
|
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))
|
|
220
|
+
// After a Backlog plan edit the current plan differs from the one created; retries compare the original request.
|
|
221
|
+
if ((existing.creation_plan ?? creationPlan(existing.execution_plan, existing.layout)) !== requestedPlan)
|
|
137
222
|
throw new TaskStateError(`idempotency key '${key}' was already used with a different execution plan`);
|
|
138
|
-
return existing
|
|
223
|
+
return withTaskLock(existing.task_id, () => {
|
|
224
|
+
const fresh = readTask(existing.task_id);
|
|
225
|
+
assertNoPendingDeletion(fresh);
|
|
226
|
+
if (fresh.workspace)
|
|
227
|
+
ensureWorkspace(fresh.workspace);
|
|
228
|
+
return fresh;
|
|
229
|
+
});
|
|
139
230
|
}
|
|
231
|
+
if (input.layout && (input.execution_plan || input.no_room || input.room_id))
|
|
232
|
+
throw new TaskStateError('a layout task cannot also use a room template, no-room mode or a room');
|
|
140
233
|
const state = input.start === false ? 'backlog' : 'provisioning';
|
|
234
|
+
const taskId = generateTaskId();
|
|
141
235
|
const record = {
|
|
142
|
-
task_id:
|
|
236
|
+
task_id: taskId,
|
|
143
237
|
list_id: input.listId ?? DEFAULT_TASK_LIST_ID,
|
|
144
238
|
title: input.title,
|
|
145
239
|
brief: input.brief,
|
|
@@ -148,14 +242,18 @@ export function createTask(input) {
|
|
|
148
242
|
template: input.template,
|
|
149
243
|
execution_plan: input.execution_plan,
|
|
150
244
|
no_room: input.no_room || undefined,
|
|
245
|
+
...(input.layout ? { layout: { name: input.layout.name, run_id: `task-${taskId}`, definition_hash: input.layout.definition_hash } } : {}),
|
|
151
246
|
room_id: input.room_id,
|
|
152
247
|
member_roles: [],
|
|
153
248
|
origin: input.origin,
|
|
154
249
|
idempotency_key: key,
|
|
250
|
+
creation_plan: requestedPlan,
|
|
155
251
|
created_at: new Date().toISOString(),
|
|
156
252
|
started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
|
|
157
253
|
};
|
|
254
|
+
record.workspace = planWorkspace('task', record.task_id);
|
|
158
255
|
writeTask(record);
|
|
256
|
+
ensureWorkspace(record.workspace);
|
|
159
257
|
return readTask(record.task_id);
|
|
160
258
|
}
|
|
161
259
|
export function getTask(id) { return withTaskLock(id, () => readTask(id)); }
|
|
@@ -195,7 +293,10 @@ export function listTasks(filter) {
|
|
|
195
293
|
if ((!states || states.includes(t.state)) && (!filter?.listId || t.list_id === filter.listId))
|
|
196
294
|
tasks.push(t);
|
|
197
295
|
}
|
|
198
|
-
catch {
|
|
296
|
+
catch (error) {
|
|
297
|
+
if (filter?.strict)
|
|
298
|
+
throw error; /* legacy CLI inventory tolerates corrupt files */
|
|
299
|
+
}
|
|
199
300
|
}
|
|
200
301
|
const order = new Map(readTaskLists().map((item, index) => [item.list_id, index]));
|
|
201
302
|
return tasks.sort((a, b) => (order.get(a.list_id) - order.get(b.list_id))
|
|
@@ -526,14 +627,10 @@ export function ensureTaskDeletionReceipt(id) {
|
|
|
526
627
|
writeDeletionReceiptForIntent(stored);
|
|
527
628
|
});
|
|
528
629
|
}
|
|
529
|
-
/**
|
|
630
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
530
631
|
export function completeTaskDeletionReceipt(id) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return;
|
|
534
|
-
receipt.settled_at = new Date().toISOString();
|
|
535
|
-
receipt.result = 'deleted';
|
|
536
|
-
replaceFileAtomically(deletionReceiptPath(id), JSON.stringify(receipt, null, 2) + '\n');
|
|
632
|
+
assertCanonicalTaskId(id);
|
|
633
|
+
rmSync(deletionReceiptPath(id), { force: true });
|
|
537
634
|
}
|
|
538
635
|
/**
|
|
539
636
|
* Persist the first-wins durable deletion intent. Accepts every lifecycle
|
|
@@ -552,8 +649,10 @@ export function beginTaskDeletionIntent(id, actor) {
|
|
|
552
649
|
stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
553
650
|
}
|
|
554
651
|
catch (error) {
|
|
555
|
-
if (isNotFoundError(error))
|
|
652
|
+
if (isNotFoundError(error)) {
|
|
653
|
+
completeTaskDeletionReceipt(id);
|
|
556
654
|
return { status: 'already_absent' };
|
|
655
|
+
}
|
|
557
656
|
throw error;
|
|
558
657
|
}
|
|
559
658
|
if (stored.deletion?.status === 'pending') {
|
|
@@ -650,6 +749,8 @@ function findCursorForSeat(id, stored, seat) {
|
|
|
650
749
|
if (cursor && seat.identity_cid
|
|
651
750
|
&& cursor.identity_cid.toLowerCase() !== seat.identity_cid.toLowerCase())
|
|
652
751
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' identity CID mismatch between cursor and room seat`);
|
|
752
|
+
if (cursor?.action_id && seat.launch?.action_id && cursor.action_id !== seat.launch.action_id)
|
|
753
|
+
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' creation ownership proof changed`);
|
|
653
754
|
return cursor;
|
|
654
755
|
}
|
|
655
756
|
/**
|
|
@@ -670,11 +771,20 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
|
|
|
670
771
|
for (const seat of seats) {
|
|
671
772
|
if (!seat.identity_cid)
|
|
672
773
|
continue;
|
|
673
|
-
|
|
774
|
+
const cursor = findCursorForSeat(id, stored, seat);
|
|
775
|
+
if (!cursor) {
|
|
674
776
|
stored.deletion.members.push({
|
|
675
777
|
name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now,
|
|
778
|
+
...(seat.launch?.action_id ? { action_id: seat.launch.action_id } : {}),
|
|
779
|
+
...(seat.launch?.task_supervised ? { task_supervised: true } : {}),
|
|
676
780
|
});
|
|
677
781
|
}
|
|
782
|
+
else {
|
|
783
|
+
if (seat.launch?.action_id)
|
|
784
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
785
|
+
if (seat.launch?.task_supervised)
|
|
786
|
+
cursor.task_supervised = true;
|
|
787
|
+
}
|
|
678
788
|
}
|
|
679
789
|
writeTask(stored);
|
|
680
790
|
return presentTaskLenient(stored);
|
|
@@ -703,12 +813,17 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
703
813
|
const evidence = seat.retirement;
|
|
704
814
|
if (evidence?.phase !== 'identity_absent')
|
|
705
815
|
throw new TaskStateError(`task ${id} deletion cannot checkpoint member '${seat.role_name}' before completed retirement`);
|
|
706
|
-
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
816
|
+
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
817
|
+
|| evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
|
|
707
818
|
if (!neverLaunched && evidence.archive_path === undefined)
|
|
708
819
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
|
|
709
820
|
if (!seat.identity_cid) {
|
|
710
|
-
if (neverLaunched)
|
|
711
|
-
|
|
821
|
+
if (neverLaunched) {
|
|
822
|
+
stored.deletion.absent_members ??= [];
|
|
823
|
+
if (!stored.deletion.absent_members.includes(seat.role_name))
|
|
824
|
+
stored.deletion.absent_members.push(seat.role_name);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
712
827
|
const proof = proofs.find(value => value.name === seat.role_name);
|
|
713
828
|
if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
|
|
714
829
|
&& proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
|
|
@@ -728,6 +843,10 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
728
843
|
cursor = { name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now };
|
|
729
844
|
stored.deletion.members.push(cursor);
|
|
730
845
|
}
|
|
846
|
+
if (seat.launch?.action_id)
|
|
847
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
848
|
+
if (seat.launch?.task_supervised)
|
|
849
|
+
cursor.task_supervised = true;
|
|
731
850
|
if (DELETION_MEMBER_PHASE_ORDER[evidence.phase] < DELETION_MEMBER_PHASE_ORDER[cursor.phase])
|
|
732
851
|
continue;
|
|
733
852
|
if (cursor.launch_id !== undefined && cursor.launch_id !== evidence.launch_id)
|
|
@@ -746,6 +865,19 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
746
865
|
return presentTaskLenient(stored);
|
|
747
866
|
});
|
|
748
867
|
}
|
|
868
|
+
/** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
|
|
869
|
+
* From here retries still verify live absence, but owned archives may be consumed.
|
|
870
|
+
*/
|
|
871
|
+
export function beginTaskWorkspaceCleanup(id) {
|
|
872
|
+
withTaskLock(id, () => {
|
|
873
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
874
|
+
if (stored.deletion?.status !== 'pending'
|
|
875
|
+
|| stored.deletion.members.some(member => member.phase !== 'identity_absent'))
|
|
876
|
+
throw new TaskStateError('Task is not ready for workspace cleanup');
|
|
877
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
878
|
+
writeTask(stored);
|
|
879
|
+
});
|
|
880
|
+
}
|
|
749
881
|
/**
|
|
750
882
|
* Physically remove a deletion-pending task record. Settlement-only: callers
|
|
751
883
|
* must have completed member retirement and room cleanup first. Missing
|
|
@@ -765,6 +897,12 @@ export function unlinkDeletedTask(id) {
|
|
|
765
897
|
}
|
|
766
898
|
if (stored.deletion?.status !== 'pending')
|
|
767
899
|
throw new TaskStateError(`task ${id} has no pending deletion`);
|
|
900
|
+
// Preserve committed deliveries outside the record before deletion. A crash
|
|
901
|
+
// before unlink may replay two copies; both carry identical service dedupe IDs.
|
|
902
|
+
const removed = { eventId: `task:${id}:deleted`, taskId: id,
|
|
903
|
+
title: `Task deleted: ${stored.title}`.slice(0, 160), body: `Task ${id}: deleting → deleted.`,
|
|
904
|
+
target: { kind: 'task-deleted', taskId: id } };
|
|
905
|
+
replaceFileAtomically(join(retiredNotificationsDir(), `${id}.json`), JSON.stringify({ notification_events: [...(stored.notification_events ?? []), removed] }) + '\n');
|
|
768
906
|
try {
|
|
769
907
|
unlinkSync(taskPath(id));
|
|
770
908
|
}
|
|
@@ -776,3 +914,87 @@ export function unlinkDeletedTask(id) {
|
|
|
776
914
|
return true;
|
|
777
915
|
});
|
|
778
916
|
}
|
|
917
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
918
|
+
export function settleAbsentTaskDeletionMember(id, name) {
|
|
919
|
+
withTaskLock(id, () => {
|
|
920
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
921
|
+
if (stored.deletion?.status !== 'pending')
|
|
922
|
+
throw new TaskStateError('Task is not deleting');
|
|
923
|
+
const member = stored.deletion.members.find(value => value.name === name);
|
|
924
|
+
if (!member)
|
|
925
|
+
throw new TaskStateError('Unknown deletion member');
|
|
926
|
+
member.phase = 'identity_absent';
|
|
927
|
+
member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
|
|
928
|
+
member.updated_at = new Date().toISOString();
|
|
929
|
+
writeTask(stored);
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
933
|
+
export function detachDeletedRoom(taskId, roomId) {
|
|
934
|
+
withTaskLock(taskId, () => {
|
|
935
|
+
if (!existsSync(taskPath(taskId)))
|
|
936
|
+
return;
|
|
937
|
+
const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
|
|
938
|
+
if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
|
|
939
|
+
return;
|
|
940
|
+
delete stored.room_id;
|
|
941
|
+
delete stored.room_identity_cid;
|
|
942
|
+
stored.member_roles = [];
|
|
943
|
+
writeTask(stored);
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
947
|
+
export function beginTaskArchiveCleanup(id) {
|
|
948
|
+
withTaskLock(id, () => {
|
|
949
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
950
|
+
if (stored.deletion?.status !== 'pending')
|
|
951
|
+
throw new TaskStateError('Task is not deleting');
|
|
952
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
953
|
+
writeTask(stored);
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
/** Metadata-only edit; compare under the task lock to prevent lost updates. */
|
|
957
|
+
/**
|
|
958
|
+
* Choose, change or clear a Backlog task's Room Layout before it starts.
|
|
959
|
+
* `expected` is the layout name the caller saw (null = none); a different
|
|
960
|
+
* current choice is a stale edit. Choosing a layout drops the sealed room
|
|
961
|
+
* template plan; the caller releases the returned launch snapshot hash.
|
|
962
|
+
*/
|
|
963
|
+
export function updateTaskLayout(id, expected, next) {
|
|
964
|
+
return withTaskLock(id, () => {
|
|
965
|
+
const task = readTask(id);
|
|
966
|
+
assertNoPendingDeletion(task);
|
|
967
|
+
assertNoPendingTerminalIntent(task);
|
|
968
|
+
if (task.state !== 'backlog' || task.room_id)
|
|
969
|
+
throw new TaskStateError(`task ${id} has started; its room layout is fixed (state '${task.state}')`);
|
|
970
|
+
const current = task.layout?.name ?? null;
|
|
971
|
+
// A replay of the same choice is idempotent (and refreshes the pinned source hash).
|
|
972
|
+
if (current !== expected && current !== (next?.name ?? null))
|
|
973
|
+
throw new TaskStateError(`task ${id} room layout changed to '${current ?? 'one room'}'; reload before changing it`);
|
|
974
|
+
const releasedSnapshot = next ? task.execution_plan?.snapshot.launch_snapshot_hash : undefined;
|
|
975
|
+
if (next) {
|
|
976
|
+
task.layout = { name: next.name, run_id: `task-${task.task_id}`, definition_hash: next.definition_hash };
|
|
977
|
+
delete task.execution_plan;
|
|
978
|
+
delete task.template;
|
|
979
|
+
delete task.no_room;
|
|
980
|
+
}
|
|
981
|
+
else
|
|
982
|
+
delete task.layout;
|
|
983
|
+
task.room_plan_changed_at = new Date().toISOString();
|
|
984
|
+
writeTask(task);
|
|
985
|
+
return { task: readTask(id), ...(releasedSnapshot ? { releasedSnapshot } : {}) };
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
export function updateTaskBrief(id, brief, expectedBrief) {
|
|
989
|
+
assertCanonicalTaskId(id);
|
|
990
|
+
return withTaskLock(id, () => {
|
|
991
|
+
const task = readTask(id);
|
|
992
|
+
assertNoPendingDeletion(task);
|
|
993
|
+
assertNoPendingTerminalIntent(task);
|
|
994
|
+
if ((task.brief ?? '') !== expectedBrief && task.brief !== brief)
|
|
995
|
+
throw new TaskStateError('Description changed elsewhere. Reload the task before saving.');
|
|
996
|
+
task.brief = brief;
|
|
997
|
+
writeTask(task);
|
|
998
|
+
return task;
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
@@ -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;
|
|
@@ -88,13 +90,36 @@ export interface TaskDeletionIntent {
|
|
|
88
90
|
/** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
|
|
89
91
|
members: TaskDeletionMemberCursor[];
|
|
90
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;
|
|
91
96
|
error?: string;
|
|
92
97
|
error_at?: string;
|
|
93
98
|
recovery_hint?: string;
|
|
94
99
|
first_failure?: string;
|
|
95
100
|
first_recovery_hint?: string;
|
|
96
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
|
+
target: {
|
|
114
|
+
kind: 'task' | 'task-deleted';
|
|
115
|
+
taskId: string;
|
|
116
|
+
};
|
|
117
|
+
taskId: string;
|
|
118
|
+
}
|
|
97
119
|
export interface TaskRecord {
|
|
120
|
+
/** Pending lifecycle events, committed atomically with their task change. */
|
|
121
|
+
notification_events?: TaskNotificationEvent[];
|
|
122
|
+
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
98
123
|
task_id: string;
|
|
99
124
|
/** Stable organizational list identifier. Missing legacy values mean `default`. */
|
|
100
125
|
list_id: string;
|
|
@@ -115,6 +140,12 @@ export interface TaskRecord {
|
|
|
115
140
|
plan_hash: string;
|
|
116
141
|
};
|
|
117
142
|
no_room?: boolean;
|
|
143
|
+
/** Multi-room task: rooms and participants come from a Room Layout run. */
|
|
144
|
+
layout?: TaskLayoutLink;
|
|
145
|
+
/** Set when the room plan (layout or one-room template) was changed after creation, before start. */
|
|
146
|
+
room_plan_changed_at?: string;
|
|
147
|
+
/** Fingerprint of the room plan requested at creation; idempotent retries compare against it after later plan edits. */
|
|
148
|
+
creation_plan?: string;
|
|
118
149
|
room_id?: string;
|
|
119
150
|
room_identity_cid?: string;
|
|
120
151
|
member_roles: TaskMemberRole[];
|
|
@@ -153,6 +184,7 @@ export interface RoomRoleBriefingDefinition {
|
|
|
153
184
|
last_error?: string;
|
|
154
185
|
}
|
|
155
186
|
export interface RoomMemberLaunchState {
|
|
187
|
+
task_supervised?: boolean;
|
|
156
188
|
state: 'pending' | 'intent' | 'launched' | 'stopped' | 'failed';
|
|
157
189
|
attempt: number;
|
|
158
190
|
action_id?: string;
|
|
@@ -209,6 +241,7 @@ export type RoomHistoryEvidence = {
|
|
|
209
241
|
};
|
|
210
242
|
export type MemberRetirementPhase = 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
|
|
211
243
|
export interface MemberRetirement {
|
|
244
|
+
absence_verified?: boolean;
|
|
212
245
|
phase: MemberRetirementPhase;
|
|
213
246
|
launch_id: string;
|
|
214
247
|
updated_at: string;
|
|
@@ -235,12 +268,14 @@ export interface RoomMemberSeat {
|
|
|
235
268
|
retirement?: MemberRetirement;
|
|
236
269
|
}
|
|
237
270
|
export interface RoomOrchestrationRecord {
|
|
271
|
+
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
238
272
|
room_id: string;
|
|
239
273
|
room_identity_cid?: string;
|
|
240
274
|
room_name: string;
|
|
241
275
|
goal?: string;
|
|
242
276
|
task_id?: string;
|
|
243
277
|
template_snapshot?: TemplateSnapshot;
|
|
278
|
+
additional_member_templates?: TemplateSnapshot[];
|
|
244
279
|
/** Resolved once at launch; legacy absence means non-anonymous. */
|
|
245
280
|
room_policy?: RoomLaunchPolicy;
|
|
246
281
|
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;
|