@ours.network/fleet 1.2.0-nightly.8 → 1.2.0-nightly.81
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 +280 -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 +22 -0
- package/dist/agent-ours/mcp-endpoint.js +101 -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 +27 -1
- package/dist/agent-ours/service.js +107 -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 +120 -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 +402 -38
- 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 +227 -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 +13 -0
- package/dist/fleet-command-audit.js +31 -9
- 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 +55 -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 +182 -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 +66 -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-review.d.ts +113 -0
- package/dist/rooms-tasks/task-review.js +561 -0
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -5
- package/dist/rooms-tasks/task-state.js +358 -50
- package/dist/rooms-tasks/task-tools.d.ts +35 -0
- package/dist/rooms-tasks/task-tools.js +134 -0
- package/dist/rooms-tasks/types.d.ts +78 -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 +494 -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 +893 -71
- 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-progress-review.md +143 -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 +25 -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 +6 -3
- 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 +21 -13
- package/presets/fleet/roles/Critic.yaml +24 -0
- package/presets/fleet/roles/Developer.yaml +32 -0
- package/presets/fleet/roles/Engineer.yaml +72 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +17 -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,15 @@
|
|
|
1
1
|
import type { ArchivedMemberAbsence } from './types.js';
|
|
2
|
-
import type { TaskRecord, TaskState, TaskOrigin, TaskTemplateRef, TaskOutcome, TaskMemberRole, TaskTerminalIntent, TaskDeletionActor, TaskDeletionMemberPhase } from './types.js';
|
|
2
|
+
import type { TaskRecord, TaskState, TaskOrigin, TaskTemplateRef, TaskOutcome, TaskMemberRole, TaskTerminalIntent, TaskDeletionActor, TaskDeletionMemberPhase, TaskNotificationEvent } from './types.js';
|
|
3
3
|
export declare const tasksDir: () => string;
|
|
4
4
|
export declare class TaskStateError extends Error {
|
|
5
5
|
}
|
|
6
|
+
/** Atomic snapshots contain the events, including intermediate states between polls. */
|
|
7
|
+
export declare function pendingTaskNotifications(warn?: (line: string) => void): Array<{
|
|
8
|
+
taskId: string;
|
|
9
|
+
events: TaskNotificationEvent[];
|
|
10
|
+
}>;
|
|
11
|
+
/** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
|
|
12
|
+
export declare function acknowledgeTaskNotification(id: string, eventId: string): void;
|
|
6
13
|
/**
|
|
7
14
|
* Accepted deletion is the per-task epoch guard: once pending, every lifecycle
|
|
8
15
|
* mutation, terminal settlement, and room publication must fail boundedly.
|
|
@@ -18,6 +25,10 @@ export interface CreateTaskInput {
|
|
|
18
25
|
idempotency_key?: string;
|
|
19
26
|
start?: boolean;
|
|
20
27
|
no_room?: boolean;
|
|
28
|
+
layout?: {
|
|
29
|
+
name: string;
|
|
30
|
+
definition_hash: string;
|
|
31
|
+
};
|
|
21
32
|
room_id?: string;
|
|
22
33
|
listId?: string;
|
|
23
34
|
}
|
|
@@ -28,6 +39,7 @@ export declare function listTasks(filter?: {
|
|
|
28
39
|
state?: TaskState | TaskState[];
|
|
29
40
|
listId?: string;
|
|
30
41
|
includeDeleting?: boolean;
|
|
42
|
+
strict?: boolean;
|
|
31
43
|
}): TaskRecord[];
|
|
32
44
|
export declare function moveTaskToList(id: string, listId: string): TaskRecord;
|
|
33
45
|
export declare function findByIdempotencyKey(key: string): TaskRecord | undefined;
|
|
@@ -62,9 +74,8 @@ export declare function getDeletingTask(id: string): TaskRecord;
|
|
|
62
74
|
export declare function taskDeletionState(id: string): 'none' | 'pending' | 'absent';
|
|
63
75
|
/**
|
|
64
76
|
* Durable, surface-independent audit evidence for a permanent deletion. The
|
|
65
|
-
* receipt
|
|
66
|
-
*
|
|
67
|
-
* room content.
|
|
77
|
+
* receipt supports in-flight recovery only: written with acceptance intent,
|
|
78
|
+
* then erased before settlement is reported. It may contain a bounded title.
|
|
68
79
|
*/
|
|
69
80
|
export interface TaskDeletionReceipt {
|
|
70
81
|
schema_version: 1;
|
|
@@ -88,7 +99,7 @@ export declare function readTaskDeletionReceipt(id: string): TaskDeletionReceipt
|
|
|
88
99
|
* settlement rather than deleting resources without audit evidence.
|
|
89
100
|
*/
|
|
90
101
|
export declare function ensureTaskDeletionReceipt(id: string): void;
|
|
91
|
-
/**
|
|
102
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
92
103
|
export declare function completeTaskDeletionReceipt(id: string): void;
|
|
93
104
|
export type TaskDeletionAcceptance = {
|
|
94
105
|
status: 'accepted' | 'pending';
|
|
@@ -127,8 +138,10 @@ interface SeatEvidence {
|
|
|
127
138
|
launch?: {
|
|
128
139
|
launch_id?: string;
|
|
129
140
|
action_id?: string;
|
|
141
|
+
task_supervised?: boolean;
|
|
130
142
|
};
|
|
131
143
|
retirement?: {
|
|
144
|
+
absence_verified?: boolean;
|
|
132
145
|
phase: TaskDeletionMemberPhase;
|
|
133
146
|
launch_id: string;
|
|
134
147
|
archive_path?: string;
|
|
@@ -156,10 +169,43 @@ export declare function upsertTaskDeletionMembersFromSeats(id: string, seats: Re
|
|
|
156
169
|
* never-launched proof or a freshly verified archived-absence checkpoint.
|
|
157
170
|
*/
|
|
158
171
|
export declare function importTaskDeletionRetirementEvidence(id: string, seats: ReadonlyArray<SeatEvidence>, proofs?: ReadonlyArray<ArchivedMemberAbsence>): TaskRecord;
|
|
172
|
+
/** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
|
|
173
|
+
* From here retries still verify live absence, but owned archives may be consumed.
|
|
174
|
+
*/
|
|
175
|
+
export declare function beginTaskWorkspaceCleanup(id: string): void;
|
|
159
176
|
/**
|
|
160
177
|
* Physically remove a deletion-pending task record. Settlement-only: callers
|
|
161
178
|
* must have completed member retirement and room cleanup first. Missing
|
|
162
179
|
* records are the idempotent already-settled outcome.
|
|
163
180
|
*/
|
|
164
181
|
export declare function unlinkDeletedTask(id: string): boolean;
|
|
182
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
183
|
+
export declare function settleAbsentTaskDeletionMember(id: string, name: string): void;
|
|
184
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
185
|
+
export declare function detachDeletedRoom(taskId: string, roomId: string): void;
|
|
186
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
187
|
+
export declare function beginTaskArchiveCleanup(id: string): void;
|
|
188
|
+
/** Metadata-only edit; compare under the task lock to prevent lost updates. */
|
|
189
|
+
/**
|
|
190
|
+
* Choose, change or clear a Backlog task's Room Layout before it starts.
|
|
191
|
+
* `expected` is the layout name the caller saw (null = none); a different
|
|
192
|
+
* current choice is a stale edit. Choosing a layout drops the sealed room
|
|
193
|
+
* template plan; the caller releases the returned launch snapshot hash.
|
|
194
|
+
*/
|
|
195
|
+
export declare function updateTaskLayout(id: string, expected: string | null, next: {
|
|
196
|
+
name: string;
|
|
197
|
+
definition_hash: string;
|
|
198
|
+
} | null): {
|
|
199
|
+
task: TaskRecord;
|
|
200
|
+
releasedSnapshot?: string;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Progress and To review edits: one read-modify-write under the task lock, so
|
|
204
|
+
* concurrent writers never lose each other's changes. Lifecycle state is untouched.
|
|
205
|
+
*/
|
|
206
|
+
export declare function updateTaskContribution<T>(id: string, change: (task: TaskRecord) => {
|
|
207
|
+
value: T;
|
|
208
|
+
changed: boolean;
|
|
209
|
+
}): T;
|
|
210
|
+
export declare function updateTaskBrief(id: string, brief: string, expectedBrief: string): TaskRecord;
|
|
165
211
|
export {};
|
|
@@ -1,64 +1,136 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { publishFleetResourceCommit } from '../application/resource-observer.js';
|
|
2
|
+
import { existsSync, linkSync, 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
|
-
import { replaceFileAtomically } from '../atomic-file.js';
|
|
5
|
+
import { replaceFileAtomically, withSynchronousFileLock } from '../atomic-file.js';
|
|
6
|
+
import { incarnationEnded, processIncarnation } from '../process-incarnation.js';
|
|
5
7
|
import { stateRoot } from '../paths.js';
|
|
8
|
+
import { planWorkspace, ensureWorkspace } from './workspace.js';
|
|
6
9
|
import { storedRoomLaunchPolicy, TASK_TERMINAL_STATES, TASK_CANCELLABLE_STATES } from './types.js';
|
|
7
10
|
import { DEFAULT_TASK_LIST_ID, readTaskLists } from './task-lists.js';
|
|
8
11
|
export const tasksDir = () => join(stateRoot(), 'tasks');
|
|
9
12
|
function taskPath(id) { return join(tasksDir(), `${id}.json`); }
|
|
10
13
|
function taskLockPath(id) { return join(tasksDir(), '.locks', id); }
|
|
11
14
|
const localTaskLocks = new Set();
|
|
15
|
+
/**
|
|
16
|
+
* Remove a lock atomically: rename it to a unique name first, so no other
|
|
17
|
+
* process ever observes a half-removed lock.
|
|
18
|
+
*/
|
|
19
|
+
function retireLock(path) {
|
|
20
|
+
const retired = `${path}.retired.${process.pid}.${randomUUID()}`;
|
|
21
|
+
try {
|
|
22
|
+
renameSync(path, retired);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code === 'ENOENT')
|
|
26
|
+
return;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
rmSync(retired, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
/** A legacy lock directory without an owner file younger than this may still be releasing. */
|
|
32
|
+
const LEGACY_OWNERLESS_LOCK_GRACE_MS = 5_000;
|
|
33
|
+
/**
|
|
34
|
+
* Hold a task's cross-process lock while `fn` runs.
|
|
35
|
+
*
|
|
36
|
+
* The lock is a file holding its owner record, published complete by hard-linking
|
|
37
|
+
* a written temporary file: link never replaces an existing lock, and there is no
|
|
38
|
+
* moment at which a live claimer has an ownerless lock that could look abandoned.
|
|
39
|
+
* A lock is reclaimed only when its owner process has provably ended; reclaimers
|
|
40
|
+
* take turns and remove only the lock they observed. Directory locks are left by
|
|
41
|
+
* earlier versions; they are honored, and an ownerless one is reclaimed once it is
|
|
42
|
+
* old enough, since this version never creates one.
|
|
43
|
+
*/
|
|
12
44
|
function withTaskLock(id, fn) {
|
|
13
45
|
if (localTaskLocks.has(id))
|
|
14
46
|
return fn();
|
|
15
47
|
const path = taskLockPath(id);
|
|
16
|
-
const ownerPath = join(path, 'owner.json');
|
|
17
48
|
const token = randomUUID();
|
|
18
49
|
mkdirSync(join(tasksDir(), '.locks'), { recursive: true });
|
|
19
|
-
|
|
20
|
-
const claimPath = `${path}.claim.${process.pid}.${randomUUID()}`;
|
|
50
|
+
const readOwner = () => {
|
|
21
51
|
try {
|
|
22
|
-
|
|
23
|
-
writeFileSync(join(claimPath, 'owner.json'), JSON.stringify({ token, pid: process.pid }));
|
|
24
|
-
renameSync(claimPath, path);
|
|
25
|
-
break;
|
|
52
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
26
53
|
}
|
|
27
54
|
catch (error) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
55
|
+
if (error.code !== 'EISDIR')
|
|
56
|
+
return undefined;
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(readFileSync(join(path, 'owner.json'), 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Who holds the lock now, and whether it can be proved abandoned. */
|
|
66
|
+
const holder = () => {
|
|
67
|
+
let stat;
|
|
68
|
+
try {
|
|
69
|
+
stat = lstatSync(path);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const owner = readOwner();
|
|
75
|
+
if (owner && typeof owner.pid === 'number') {
|
|
31
76
|
let alive = false;
|
|
32
77
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
process.kill(owner.pid, 0);
|
|
79
|
+
alive = true;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
alive = error.code !== 'ESRCH';
|
|
83
|
+
}
|
|
84
|
+
return { token: owner.token, stale: !alive || incarnationEnded(owner.pid, owner) };
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
token: undefined,
|
|
88
|
+
stale: stat.isDirectory() && Date.now() - stat.mtimeMs > LEGACY_OWNERLESS_LOCK_GRACE_MS,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
const claimPath = `${path}.claim.${process.pid}.${randomUUID()}`;
|
|
92
|
+
writeFileSync(claimPath, JSON.stringify({ token, pid: process.pid, ...processIncarnation(process.pid) }));
|
|
93
|
+
try {
|
|
94
|
+
for (;;) {
|
|
95
|
+
try {
|
|
96
|
+
linkSync(claimPath, path);
|
|
97
|
+
break;
|
|
41
98
|
}
|
|
42
|
-
catch {
|
|
43
|
-
|
|
44
|
-
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (error.code !== 'EEXIST')
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
const observed = holder();
|
|
104
|
+
if (observed?.stale) {
|
|
105
|
+
try {
|
|
106
|
+
withSynchronousFileLock(`${path}.reclaim`, () => {
|
|
107
|
+
const current = holder();
|
|
108
|
+
if (current?.stale && current.token === observed.token)
|
|
109
|
+
retireLock(path);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// A contender stalled inside the reclaim section: keep waiting like any other poll.
|
|
114
|
+
if (!(error instanceof Error) || !/^Timed out acquiring synchronous/.test(error.message))
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
45
117
|
continue;
|
|
46
118
|
}
|
|
47
119
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
48
120
|
}
|
|
49
121
|
}
|
|
122
|
+
finally {
|
|
123
|
+
rmSync(claimPath, { force: true });
|
|
124
|
+
}
|
|
50
125
|
localTaskLocks.add(id);
|
|
51
126
|
try {
|
|
52
127
|
return fn();
|
|
53
128
|
}
|
|
54
129
|
finally {
|
|
55
130
|
localTaskLocks.delete(id);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
rmSync(path, { recursive: true, force: true });
|
|
60
|
-
}
|
|
61
|
-
catch { /* ownership cannot be proved */ }
|
|
131
|
+
// Only this live owner removes its lock; reclaimers never take a live owner's lock.
|
|
132
|
+
if (readOwner()?.token === token)
|
|
133
|
+
retireLock(path);
|
|
62
134
|
}
|
|
63
135
|
}
|
|
64
136
|
function generateTaskId() {
|
|
@@ -88,7 +160,88 @@ function writeTask(record) {
|
|
|
88
160
|
mkdirSync(tasksDir(), { recursive: true });
|
|
89
161
|
const persisted = { ...record };
|
|
90
162
|
delete persisted.list_name;
|
|
163
|
+
const path = taskPath(record.task_id);
|
|
164
|
+
const previous = existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : undefined;
|
|
165
|
+
const status = (task) => task.deletion?.status === 'pending' ? 'deleting' : task.blocked ? `${task.state} (blocked)` : task.state;
|
|
166
|
+
// Read pending events under the same task lock as every mutation. Metadata edits
|
|
167
|
+
// and retries preserve the queue; acknowledgement cannot be resurrected by stale input.
|
|
168
|
+
const pending = [...(previous?.notification_events ?? [])];
|
|
169
|
+
if (!previous || status(previous) !== status(record)) {
|
|
170
|
+
const next = status(record), before = previous ? status(previous) : undefined;
|
|
171
|
+
pending.push({ eventId: `task:${record.task_id}:${randomUUID()}`, taskId: record.task_id,
|
|
172
|
+
title: `Task ${!previous ? 'created' : 'updated'}: ${record.title}`.slice(0, 160),
|
|
173
|
+
body: `Task ${record.task_id}: ${before ? `${before} → ` : 'created in '}${next}.`,
|
|
174
|
+
target: { kind: 'task', taskId: record.task_id } });
|
|
175
|
+
}
|
|
176
|
+
if (pending.length)
|
|
177
|
+
persisted.notification_events = pending;
|
|
178
|
+
else
|
|
179
|
+
delete persisted.notification_events;
|
|
91
180
|
replaceFileAtomically(taskPath(record.task_id), JSON.stringify(persisted, null, 2) + '\n');
|
|
181
|
+
publishFleetResourceCommit(taskPath(record.task_id));
|
|
182
|
+
}
|
|
183
|
+
const retiredNotificationsDir = () => join(tasksDir(), '.notification-retired');
|
|
184
|
+
function typedTaskNotification(value, id) {
|
|
185
|
+
if (!value || typeof value !== 'object' || Object.keys(value).some(key => !['eventId', 'title', 'body', 'target', 'taskId'].includes(key)))
|
|
186
|
+
return false;
|
|
187
|
+
const target = value.target;
|
|
188
|
+
return typeof value.eventId === 'string' && value.eventId.startsWith(`task:${id}:`) && value.eventId.length <= 512
|
|
189
|
+
&& value.taskId === id && !!target && typeof target === 'object' && !Array.isArray(target)
|
|
190
|
+
&& Object.keys(target).every(key => ['kind', 'taskId'].includes(key)) && target.taskId === id
|
|
191
|
+
&& (target.kind === 'task' || (target.kind === 'task-deleted' && value.eventId === `task:${id}:deleted`))
|
|
192
|
+
&& typeof value.title === 'string' && !!value.title.trim() && value.title.length <= 160
|
|
193
|
+
&& typeof value.body === 'string' && !!value.body.trim() && value.body.length <= 512;
|
|
194
|
+
}
|
|
195
|
+
/** Atomic snapshots contain the events, including intermediate states between polls. */
|
|
196
|
+
export function pendingTaskNotifications(warn = () => { }) {
|
|
197
|
+
const result = [];
|
|
198
|
+
for (const dir of [tasksDir(), retiredNotificationsDir()]) {
|
|
199
|
+
if (!existsSync(dir))
|
|
200
|
+
continue;
|
|
201
|
+
for (const file of readdirSync(dir).filter(file => TASK_ID_PATTERN.test(file.slice(0, -5)) && file.endsWith('.json'))) {
|
|
202
|
+
try {
|
|
203
|
+
const path = join(dir, file), stat = lstatSync(path);
|
|
204
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
205
|
+
throw new Error('invalid notification source');
|
|
206
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
207
|
+
const id = file.slice(0, -5);
|
|
208
|
+
// A prepared deletion copy has no authority while the original still exists.
|
|
209
|
+
if (dir === retiredNotificationsDir() && existsSync(taskPath(id)))
|
|
210
|
+
continue;
|
|
211
|
+
if (value.notification_events === undefined)
|
|
212
|
+
continue;
|
|
213
|
+
if (!Array.isArray(value.notification_events))
|
|
214
|
+
throw new Error('invalid notification events');
|
|
215
|
+
// Only typed task events are delivered. Unsupported entries are never sent or converted,
|
|
216
|
+
// and they do not block later lifecycle events.
|
|
217
|
+
const events = value.notification_events.filter(event => typedTaskNotification(event, id));
|
|
218
|
+
if (events.length)
|
|
219
|
+
result.push({ taskId: id, events });
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (!isNotFoundError(error))
|
|
223
|
+
warn('Task notification record unavailable; other task deliveries continue');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
/** Service event-id dedupe covers acceptance followed by a crash before this acknowledgement. */
|
|
230
|
+
export function acknowledgeTaskNotification(id, eventId) {
|
|
231
|
+
assertCanonicalTaskId(id);
|
|
232
|
+
withTaskLock(id, () => {
|
|
233
|
+
for (const path of [taskPath(id), join(retiredNotificationsDir(), `${id}.json`)]) {
|
|
234
|
+
if (!existsSync(path))
|
|
235
|
+
continue;
|
|
236
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
237
|
+
if (!value.notification_events?.some(event => typedTaskNotification(event, id) && event.eventId === eventId))
|
|
238
|
+
continue;
|
|
239
|
+
value.notification_events = value.notification_events.filter(event => !typedTaskNotification(event, id) || event.eventId !== eventId);
|
|
240
|
+
replaceFileAtomically(path, JSON.stringify(value, null, 2) + '\n');
|
|
241
|
+
if (path.startsWith(retiredNotificationsDir() + '/') && !value.notification_events.length)
|
|
242
|
+
unlinkSync(path);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
92
245
|
}
|
|
93
246
|
function presentTask(record) {
|
|
94
247
|
const listId = record.list_id ?? DEFAULT_TASK_LIST_ID;
|
|
@@ -125,21 +278,32 @@ export function assertNoPendingDeletion(task) {
|
|
|
125
278
|
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
279
|
}
|
|
127
280
|
}
|
|
281
|
+
function creationPlan(plan, layout) {
|
|
282
|
+
return JSON.stringify([plan?.plan_hash ?? null, storedRoomLaunchPolicy(plan?.room_policy) ?? null,
|
|
283
|
+
layout ? `${layout.name}@${layout.definition_hash}` : null]);
|
|
284
|
+
}
|
|
128
285
|
export function createTask(input) {
|
|
129
286
|
const key = input.idempotency_key ?? randomUUID();
|
|
130
|
-
const existing =
|
|
287
|
+
const existing = listTasks({ includeDeleting: true }).find(task => task.idempotency_key === key);
|
|
288
|
+
const requestedPlan = creationPlan(input.execution_plan, input.layout);
|
|
131
289
|
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))
|
|
290
|
+
// After a Backlog plan edit the current plan differs from the one created; retries compare the original request.
|
|
291
|
+
if ((existing.creation_plan ?? creationPlan(existing.execution_plan, existing.layout)) !== requestedPlan)
|
|
137
292
|
throw new TaskStateError(`idempotency key '${key}' was already used with a different execution plan`);
|
|
138
|
-
return existing
|
|
293
|
+
return withTaskLock(existing.task_id, () => {
|
|
294
|
+
const fresh = readTask(existing.task_id);
|
|
295
|
+
assertNoPendingDeletion(fresh);
|
|
296
|
+
if (fresh.workspace)
|
|
297
|
+
ensureWorkspace(fresh.workspace);
|
|
298
|
+
return fresh;
|
|
299
|
+
});
|
|
139
300
|
}
|
|
301
|
+
if (input.layout && (input.execution_plan || input.no_room || input.room_id))
|
|
302
|
+
throw new TaskStateError('a layout task cannot also use a room template, no-room mode or a room');
|
|
140
303
|
const state = input.start === false ? 'backlog' : 'provisioning';
|
|
304
|
+
const taskId = generateTaskId();
|
|
141
305
|
const record = {
|
|
142
|
-
task_id:
|
|
306
|
+
task_id: taskId,
|
|
143
307
|
list_id: input.listId ?? DEFAULT_TASK_LIST_ID,
|
|
144
308
|
title: input.title,
|
|
145
309
|
brief: input.brief,
|
|
@@ -148,14 +312,18 @@ export function createTask(input) {
|
|
|
148
312
|
template: input.template,
|
|
149
313
|
execution_plan: input.execution_plan,
|
|
150
314
|
no_room: input.no_room || undefined,
|
|
315
|
+
...(input.layout ? { layout: { name: input.layout.name, run_id: `task-${taskId}`, definition_hash: input.layout.definition_hash } } : {}),
|
|
151
316
|
room_id: input.room_id,
|
|
152
317
|
member_roles: [],
|
|
153
318
|
origin: input.origin,
|
|
154
319
|
idempotency_key: key,
|
|
320
|
+
creation_plan: requestedPlan,
|
|
155
321
|
created_at: new Date().toISOString(),
|
|
156
322
|
started_at: state === 'provisioning' ? new Date().toISOString() : undefined,
|
|
157
323
|
};
|
|
324
|
+
record.workspace = planWorkspace('task', record.task_id);
|
|
158
325
|
writeTask(record);
|
|
326
|
+
ensureWorkspace(record.workspace);
|
|
159
327
|
return readTask(record.task_id);
|
|
160
328
|
}
|
|
161
329
|
export function getTask(id) { return withTaskLock(id, () => readTask(id)); }
|
|
@@ -195,7 +363,10 @@ export function listTasks(filter) {
|
|
|
195
363
|
if ((!states || states.includes(t.state)) && (!filter?.listId || t.list_id === filter.listId))
|
|
196
364
|
tasks.push(t);
|
|
197
365
|
}
|
|
198
|
-
catch {
|
|
366
|
+
catch (error) {
|
|
367
|
+
if (filter?.strict)
|
|
368
|
+
throw error; /* legacy CLI inventory tolerates corrupt files */
|
|
369
|
+
}
|
|
199
370
|
}
|
|
200
371
|
const order = new Map(readTaskLists().map((item, index) => [item.list_id, index]));
|
|
201
372
|
return tasks.sort((a, b) => (order.get(a.list_id) - order.get(b.list_id))
|
|
@@ -526,14 +697,10 @@ export function ensureTaskDeletionReceipt(id) {
|
|
|
526
697
|
writeDeletionReceiptForIntent(stored);
|
|
527
698
|
});
|
|
528
699
|
}
|
|
529
|
-
/**
|
|
700
|
+
/** Erase the in-flight receipt; also heals a crash after task unlink. */
|
|
530
701
|
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');
|
|
702
|
+
assertCanonicalTaskId(id);
|
|
703
|
+
rmSync(deletionReceiptPath(id), { force: true });
|
|
537
704
|
}
|
|
538
705
|
/**
|
|
539
706
|
* Persist the first-wins durable deletion intent. Accepts every lifecycle
|
|
@@ -552,8 +719,10 @@ export function beginTaskDeletionIntent(id, actor) {
|
|
|
552
719
|
stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
553
720
|
}
|
|
554
721
|
catch (error) {
|
|
555
|
-
if (isNotFoundError(error))
|
|
722
|
+
if (isNotFoundError(error)) {
|
|
723
|
+
completeTaskDeletionReceipt(id);
|
|
556
724
|
return { status: 'already_absent' };
|
|
725
|
+
}
|
|
557
726
|
throw error;
|
|
558
727
|
}
|
|
559
728
|
if (stored.deletion?.status === 'pending') {
|
|
@@ -650,6 +819,8 @@ function findCursorForSeat(id, stored, seat) {
|
|
|
650
819
|
if (cursor && seat.identity_cid
|
|
651
820
|
&& cursor.identity_cid.toLowerCase() !== seat.identity_cid.toLowerCase())
|
|
652
821
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' identity CID mismatch between cursor and room seat`);
|
|
822
|
+
if (cursor?.action_id && seat.launch?.action_id && cursor.action_id !== seat.launch.action_id)
|
|
823
|
+
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' creation ownership proof changed`);
|
|
653
824
|
return cursor;
|
|
654
825
|
}
|
|
655
826
|
/**
|
|
@@ -670,11 +841,20 @@ export function upsertTaskDeletionMembersFromSeats(id, seats) {
|
|
|
670
841
|
for (const seat of seats) {
|
|
671
842
|
if (!seat.identity_cid)
|
|
672
843
|
continue;
|
|
673
|
-
|
|
844
|
+
const cursor = findCursorForSeat(id, stored, seat);
|
|
845
|
+
if (!cursor) {
|
|
674
846
|
stored.deletion.members.push({
|
|
675
847
|
name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now,
|
|
848
|
+
...(seat.launch?.action_id ? { action_id: seat.launch.action_id } : {}),
|
|
849
|
+
...(seat.launch?.task_supervised ? { task_supervised: true } : {}),
|
|
676
850
|
});
|
|
677
851
|
}
|
|
852
|
+
else {
|
|
853
|
+
if (seat.launch?.action_id)
|
|
854
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
855
|
+
if (seat.launch?.task_supervised)
|
|
856
|
+
cursor.task_supervised = true;
|
|
857
|
+
}
|
|
678
858
|
}
|
|
679
859
|
writeTask(stored);
|
|
680
860
|
return presentTaskLenient(stored);
|
|
@@ -703,12 +883,17 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
703
883
|
const evidence = seat.retirement;
|
|
704
884
|
if (evidence?.phase !== 'identity_absent')
|
|
705
885
|
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
|
|
886
|
+
const neverLaunched = evidence.launch_id === DELETION_MEMBER_NEVER_LAUNCHED
|
|
887
|
+
|| evidence.launch_id === DELETION_MEMBER_ABSENT_VERIFIED || evidence.absence_verified === true;
|
|
707
888
|
if (!neverLaunched && evidence.archive_path === undefined)
|
|
708
889
|
throw new TaskStateError(`task ${id} deletion member '${seat.role_name}' retirement evidence lacks its archive`);
|
|
709
890
|
if (!seat.identity_cid) {
|
|
710
|
-
if (neverLaunched)
|
|
711
|
-
|
|
891
|
+
if (neverLaunched) {
|
|
892
|
+
stored.deletion.absent_members ??= [];
|
|
893
|
+
if (!stored.deletion.absent_members.includes(seat.role_name))
|
|
894
|
+
stored.deletion.absent_members.push(seat.role_name);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
712
897
|
const proof = proofs.find(value => value.name === seat.role_name);
|
|
713
898
|
if (proof && proof.launch_id === evidence.launch_id && proof.archive_path === evidence.archive_path
|
|
714
899
|
&& proof.launch_id === seat.launch?.launch_id && proof.action_id === seat.launch?.action_id) {
|
|
@@ -728,6 +913,10 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
728
913
|
cursor = { name: seat.role_name, identity_cid: seat.identity_cid, phase: 'pending', updated_at: now };
|
|
729
914
|
stored.deletion.members.push(cursor);
|
|
730
915
|
}
|
|
916
|
+
if (seat.launch?.action_id)
|
|
917
|
+
cursor.action_id ??= seat.launch.action_id;
|
|
918
|
+
if (seat.launch?.task_supervised)
|
|
919
|
+
cursor.task_supervised = true;
|
|
731
920
|
if (DELETION_MEMBER_PHASE_ORDER[evidence.phase] < DELETION_MEMBER_PHASE_ORDER[cursor.phase])
|
|
732
921
|
continue;
|
|
733
922
|
if (cursor.launch_id !== undefined && cursor.launch_id !== evidence.launch_id)
|
|
@@ -746,6 +935,19 @@ export function importTaskDeletionRetirementEvidence(id, seats, proofs = []) {
|
|
|
746
935
|
return presentTaskLenient(stored);
|
|
747
936
|
});
|
|
748
937
|
}
|
|
938
|
+
/** Settlement-only checkpoint, under the task-operation lock after fresh archive verification.
|
|
939
|
+
* From here retries still verify live absence, but owned archives may be consumed.
|
|
940
|
+
*/
|
|
941
|
+
export function beginTaskWorkspaceCleanup(id) {
|
|
942
|
+
withTaskLock(id, () => {
|
|
943
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
944
|
+
if (stored.deletion?.status !== 'pending'
|
|
945
|
+
|| stored.deletion.members.some(member => member.phase !== 'identity_absent'))
|
|
946
|
+
throw new TaskStateError('Task is not ready for workspace cleanup');
|
|
947
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
948
|
+
writeTask(stored);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
749
951
|
/**
|
|
750
952
|
* Physically remove a deletion-pending task record. Settlement-only: callers
|
|
751
953
|
* must have completed member retirement and room cleanup first. Missing
|
|
@@ -765,6 +967,12 @@ export function unlinkDeletedTask(id) {
|
|
|
765
967
|
}
|
|
766
968
|
if (stored.deletion?.status !== 'pending')
|
|
767
969
|
throw new TaskStateError(`task ${id} has no pending deletion`);
|
|
970
|
+
// Preserve committed deliveries outside the record before deletion. A crash
|
|
971
|
+
// before unlink may replay two copies; both carry identical service dedupe IDs.
|
|
972
|
+
const removed = { eventId: `task:${id}:deleted`, taskId: id,
|
|
973
|
+
title: `Task deleted: ${stored.title}`.slice(0, 160), body: `Task ${id}: deleting → deleted.`,
|
|
974
|
+
target: { kind: 'task-deleted', taskId: id } };
|
|
975
|
+
replaceFileAtomically(join(retiredNotificationsDir(), `${id}.json`), JSON.stringify({ notification_events: [...(stored.notification_events ?? []), removed] }) + '\n');
|
|
768
976
|
try {
|
|
769
977
|
unlinkSync(taskPath(id));
|
|
770
978
|
}
|
|
@@ -776,3 +984,103 @@ export function unlinkDeletedTask(id) {
|
|
|
776
984
|
return true;
|
|
777
985
|
});
|
|
778
986
|
}
|
|
987
|
+
/** Called only after current temp and exact identity absence have been checked. */
|
|
988
|
+
export function settleAbsentTaskDeletionMember(id, name) {
|
|
989
|
+
withTaskLock(id, () => {
|
|
990
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
991
|
+
if (stored.deletion?.status !== 'pending')
|
|
992
|
+
throw new TaskStateError('Task is not deleting');
|
|
993
|
+
const member = stored.deletion.members.find(value => value.name === name);
|
|
994
|
+
if (!member)
|
|
995
|
+
throw new TaskStateError('Unknown deletion member');
|
|
996
|
+
member.phase = 'identity_absent';
|
|
997
|
+
member.launch_id ??= DELETION_MEMBER_ABSENT_VERIFIED;
|
|
998
|
+
member.updated_at = new Date().toISOString();
|
|
999
|
+
writeTask(stored);
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
/** Room-only erasure keeps the parent Task but removes its dead room/member links. */
|
|
1003
|
+
export function detachDeletedRoom(taskId, roomId) {
|
|
1004
|
+
withTaskLock(taskId, () => {
|
|
1005
|
+
if (!existsSync(taskPath(taskId)))
|
|
1006
|
+
return;
|
|
1007
|
+
const stored = JSON.parse(readFileSync(taskPath(taskId), 'utf8'));
|
|
1008
|
+
if (stored.room_id !== roomId || stored.deletion?.status === 'pending')
|
|
1009
|
+
return;
|
|
1010
|
+
delete stored.room_id;
|
|
1011
|
+
delete stored.room_identity_cid;
|
|
1012
|
+
stored.member_roles = [];
|
|
1013
|
+
writeTask(stored);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
/** A completed room may consume its archives while other task members still retire. */
|
|
1017
|
+
export function beginTaskArchiveCleanup(id) {
|
|
1018
|
+
withTaskLock(id, () => {
|
|
1019
|
+
const stored = JSON.parse(readFileSync(taskPath(id), 'utf8'));
|
|
1020
|
+
if (stored.deletion?.status !== 'pending')
|
|
1021
|
+
throw new TaskStateError('Task is not deleting');
|
|
1022
|
+
stored.deletion.workspace_cleanup_started_at ??= new Date().toISOString();
|
|
1023
|
+
writeTask(stored);
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
/** Metadata-only edit; compare under the task lock to prevent lost updates. */
|
|
1027
|
+
/**
|
|
1028
|
+
* Choose, change or clear a Backlog task's Room Layout before it starts.
|
|
1029
|
+
* `expected` is the layout name the caller saw (null = none); a different
|
|
1030
|
+
* current choice is a stale edit. Choosing a layout drops the sealed room
|
|
1031
|
+
* template plan; the caller releases the returned launch snapshot hash.
|
|
1032
|
+
*/
|
|
1033
|
+
export function updateTaskLayout(id, expected, next) {
|
|
1034
|
+
return withTaskLock(id, () => {
|
|
1035
|
+
const task = readTask(id);
|
|
1036
|
+
assertNoPendingDeletion(task);
|
|
1037
|
+
assertNoPendingTerminalIntent(task);
|
|
1038
|
+
if (task.state !== 'backlog' || task.room_id)
|
|
1039
|
+
throw new TaskStateError(`task ${id} has started; its room layout is fixed (state '${task.state}')`);
|
|
1040
|
+
const current = task.layout?.name ?? null;
|
|
1041
|
+
// A replay of the same choice is idempotent (and refreshes the pinned source hash).
|
|
1042
|
+
if (current !== expected && current !== (next?.name ?? null))
|
|
1043
|
+
throw new TaskStateError(`task ${id} room layout changed to '${current ?? 'one room'}'; reload before changing it`);
|
|
1044
|
+
const releasedSnapshot = next ? task.execution_plan?.snapshot.launch_snapshot_hash : undefined;
|
|
1045
|
+
if (next) {
|
|
1046
|
+
task.layout = { name: next.name, run_id: `task-${task.task_id}`, definition_hash: next.definition_hash };
|
|
1047
|
+
delete task.execution_plan;
|
|
1048
|
+
delete task.template;
|
|
1049
|
+
delete task.no_room;
|
|
1050
|
+
}
|
|
1051
|
+
else
|
|
1052
|
+
delete task.layout;
|
|
1053
|
+
task.room_plan_changed_at = new Date().toISOString();
|
|
1054
|
+
writeTask(task);
|
|
1055
|
+
return { task: readTask(id), ...(releasedSnapshot ? { releasedSnapshot } : {}) };
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Progress and To review edits: one read-modify-write under the task lock, so
|
|
1060
|
+
* concurrent writers never lose each other's changes. Lifecycle state is untouched.
|
|
1061
|
+
*/
|
|
1062
|
+
export function updateTaskContribution(id, change) {
|
|
1063
|
+
assertCanonicalTaskId(id);
|
|
1064
|
+
return withTaskLock(id, () => {
|
|
1065
|
+
const task = readTask(id);
|
|
1066
|
+
assertNoPendingDeletion(task);
|
|
1067
|
+
assertNoPendingTerminalIntent(task);
|
|
1068
|
+
const { value, changed } = change(task);
|
|
1069
|
+
if (changed)
|
|
1070
|
+
writeTask(task);
|
|
1071
|
+
return value;
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
export function updateTaskBrief(id, brief, expectedBrief) {
|
|
1075
|
+
assertCanonicalTaskId(id);
|
|
1076
|
+
return withTaskLock(id, () => {
|
|
1077
|
+
const task = readTask(id);
|
|
1078
|
+
assertNoPendingDeletion(task);
|
|
1079
|
+
assertNoPendingTerminalIntent(task);
|
|
1080
|
+
if ((task.brief ?? '') !== expectedBrief && task.brief !== brief)
|
|
1081
|
+
throw new TaskStateError('Description changed elsewhere. Reload the task before saving.');
|
|
1082
|
+
task.brief = brief;
|
|
1083
|
+
writeTask(task);
|
|
1084
|
+
return task;
|
|
1085
|
+
});
|
|
1086
|
+
}
|