@ours.network/fleet 1.2.0-nightly.6 → 1.2.0-nightly.60
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 +287 -65
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +35 -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 +4 -0
- package/dist/agent-ours/mcp-endpoint.js +5 -0
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +21 -1
- package/dist/agent-ours/service.js +94 -14
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +27 -0
- package/dist/application/conversation-history.js +212 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +9 -1
- package/dist/application/role-repository.js +19 -2
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +30 -0
- package/dist/application/session-control.js +72 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +115 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +259 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +285 -32
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/briefing.d.ts +3 -1
- package/dist/briefing.js +70 -30
- package/dist/build-info.json +7 -4
- package/dist/capabilities.d.ts +6 -1
- package/dist/capabilities.js +8 -0
- package/dist/cli.js +407 -37
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +123 -19
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +55 -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 +26 -9
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +20 -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 +23 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +8 -34
- package/dist/monitor.js +89 -152
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +8 -0
- package/dist/notifications/target-cleanup.js +41 -0
- package/dist/notifications/task-producer.d.ts +18 -0
- package/dist/notifications/task-producer.js +67 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +25 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +6 -2
- package/dist/rooms-tasks/close.js +246 -37
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +2 -4
- package/dist/rooms-tasks/cowork-adapter.js +7 -121
- package/dist/rooms-tasks/cowork-http.js +29 -1
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +84 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +285 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -8
- package/dist/rooms-tasks/task-state.js +245 -25
- package/dist/rooms-tasks/types.d.ts +40 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +25 -3
- package/dist/runner.js +291 -49
- package/dist/session/acp.d.ts +53 -1
- package/dist/session/acp.js +216 -40
- package/dist/session/arbiter.d.ts +11 -0
- package/dist/session/arbiter.js +43 -2
- package/dist/session/codex-app-server.d.ts +32 -0
- package/dist/session/codex-app-server.js +137 -23
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +68 -3
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-store.d.ts +1 -1
- package/dist/session/conversation-store.js +7 -11
- package/dist/session/conversation-types.d.ts +28 -3
- package/dist/session/prompt-batch.d.ts +63 -0
- package/dist/session/prompt-batch.js +86 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +14 -0
- 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 +4 -0
- package/dist/startup-prompt.js +36 -0
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -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 +75 -0
- package/dist/subscriptions/login.js +182 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/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 +113 -65
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +63 -2
- package/dist/web/auth.js +191 -10
- 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/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +238 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +654 -55
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +82 -0
- package/dist/workspace-enrollment.js +566 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/acp-files.md +146 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -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 +20 -19
- 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 +1 -1
- 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 +34 -11
- package/presets/fleet/roles/Engineer.yaml +40 -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,9 +1,12 @@
|
|
|
1
|
+
import { TaskLayouts, assertLayoutOpenable, writeTaskBrief } from './task-layouts.js';
|
|
2
|
+
import { FleetError } from './errors.js';
|
|
1
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { taskLiveReadiness } from '../rooms-tasks/live-readiness.js';
|
|
2
5
|
import { ConfigError, loadConfig } from '../config.js';
|
|
3
6
|
import { CoworkProtocolError, CoworkUnavailableError, createCoworkAdapter, } from '../rooms-tasks/cowork-adapter.js';
|
|
4
7
|
import { getBinPath, provisionMembers } from '../rooms-tasks/provision.js';
|
|
5
8
|
import { activateRoom, advanceSaga, createRoomRecord, getRoomRecord, listRoomRecords, setOwnerSeat, setSagaError, } from '../rooms-tasks/room-state.js';
|
|
6
|
-
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, } from '../rooms-tasks/task-state.js';
|
|
9
|
+
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, updateTaskBrief, updateTaskLayout, } from '../rooms-tasks/task-state.js';
|
|
7
10
|
import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
|
|
8
11
|
import { hashTemplate, listTemplates, resolveTemplate, sealTemplateSnapshot, snapshotTemplate } from '../rooms-tasks/templates.js';
|
|
9
12
|
import { acquireLaunchSnapshotLock, releaseLaunchSnapshot } from '../rooms-tasks/launch-snapshot.js';
|
|
@@ -71,7 +74,127 @@ export class TaskRoomApplicationService {
|
|
|
71
74
|
this.configurationPath = configurationPath;
|
|
72
75
|
this.deps = deps;
|
|
73
76
|
}
|
|
77
|
+
async addMember(taskId, request) {
|
|
78
|
+
const { requestMemberAddition } = await import('../rooms-tasks/add-member.js');
|
|
79
|
+
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
80
|
+
return requestMemberAddition({ taskId, request, cfg, configPath: this.configurationPath,
|
|
81
|
+
cowork: (this.deps.cowork ?? createCoworkAdapter)(cfg), binPath: (this.deps.binPath ?? getBinPath)(), provision: this.deps.provisionMembers });
|
|
82
|
+
}
|
|
83
|
+
async memberAddition(taskId, requestId) { const { memberAddition } = await import('../rooms-tasks/add-member.js'); return memberAddition(taskId, requestId); }
|
|
84
|
+
layouts() {
|
|
85
|
+
return this.deps.taskLayouts ?? new TaskLayouts(this.configurationPath, { binPath: this.deps.binPath });
|
|
86
|
+
}
|
|
87
|
+
async createLayoutTask(request) {
|
|
88
|
+
if (request.template || request.noRoom || request.members || request.anonymous !== undefined)
|
|
89
|
+
throw new FleetError('invalid_request', 'a layout task cannot be combined with a room template, no-room mode, member overrides or anonymity');
|
|
90
|
+
let layout;
|
|
91
|
+
try {
|
|
92
|
+
layout = this.layouts().resolve(request.layout);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw new FleetError('invalid_request', error.message, { details: { layout: request.layout } });
|
|
96
|
+
}
|
|
97
|
+
const brief = request.briefFile ? readFileSync(request.briefFile, 'utf8') : request.brief;
|
|
98
|
+
const task = await withTaskListsLock(() => persistTask({
|
|
99
|
+
title: request.title, brief, brief_file: request.briefFile, layout,
|
|
100
|
+
origin: request.origin, idempotency_key: request.idempotencyKey,
|
|
101
|
+
start: !request.backlog, listId: resolveTaskList(request.list ?? 'default').list_id,
|
|
102
|
+
}));
|
|
103
|
+
recordFleetAuditPresentation({ kind: 'task', operation: 'create', id: task.task_id,
|
|
104
|
+
title: task.title, previousState: 'none', newState: task.state, revision: task.created_at,
|
|
105
|
+
list: task.list_name ?? 'default', roomId: undefined, template: `layout:${layout.name}`, agents: [] });
|
|
106
|
+
return task.state === 'provisioning' ? (await this.ensureLayoutWork(task)).task : task;
|
|
107
|
+
}
|
|
108
|
+
/** Layout tasks never provision the legacy single room; their run is a local snapshot. */
|
|
109
|
+
async ensureLayoutWork(task) {
|
|
110
|
+
if (TASK_TERMINAL_STATES.includes(task.state))
|
|
111
|
+
throw new TaskRoomApplicationError('task_terminal', 'task terminal', { task: task.task_id, state: task.state });
|
|
112
|
+
if (task.state === 'active' || task.state === 'review')
|
|
113
|
+
return { task, status: 'already_active' };
|
|
114
|
+
if (task.state === 'backlog') {
|
|
115
|
+
// Verify the pinned source before leaving Backlog, so a stale choice stays editable on the task page.
|
|
116
|
+
if (!this.layouts().runExists(task.layout) && this.layouts().resolve(task.layout.name).definition_hash !== task.layout.definition_hash)
|
|
117
|
+
throw new FleetError('conflict', `room layout ${task.layout.name} changed after it was chosen for this task; choose it again on the task page to use the edited version`);
|
|
118
|
+
task = transitionTask(task.task_id);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await this.layouts().ensureRun(task);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const reason = `layout run could not be created: ${error.message}`;
|
|
125
|
+
persistBlockTask(task.task_id, reason);
|
|
126
|
+
throw new FleetError('conflict', reason, { details: { task: task.task_id } });
|
|
127
|
+
}
|
|
128
|
+
return { task: activateTask(task.task_id), status: 'ready' };
|
|
129
|
+
}
|
|
130
|
+
/** Compact room list for task listings; never fails the listing on an unreadable run. */
|
|
131
|
+
withLayoutRooms(task) {
|
|
132
|
+
if (!task.layout)
|
|
133
|
+
return task;
|
|
134
|
+
try {
|
|
135
|
+
const view = this.layouts().view(task);
|
|
136
|
+
return { ...task, layout_rooms: (view?.rooms ?? []).map(({ key, state, room_id, identity_cid }) => ({ key, state, ...(room_id ? { room_id } : {}), ...(identity_cid ? { identity_cid } : {}) })) };
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return { ...task, layout_rooms: [] };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Backlog only: choose, change or clear the task's Room Layout before it starts. */
|
|
143
|
+
async setTaskLayout(input) {
|
|
144
|
+
let next = null;
|
|
145
|
+
if (input.layout !== null) {
|
|
146
|
+
try {
|
|
147
|
+
next = this.layouts().resolve(input.layout);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw new FleetError('invalid_request', error.message, { details: { layout: input.layout } });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const current = readTask(input.taskId);
|
|
154
|
+
if (current.layout && this.layouts().runExists(current.layout))
|
|
155
|
+
throw new FleetError('conflict', 'this task already has a layout run; its room layout is fixed');
|
|
156
|
+
let result;
|
|
157
|
+
try {
|
|
158
|
+
result = updateTaskLayout(input.taskId, input.expectedLayout, next);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error instanceof TaskStateError && !/task not found/.test(error.message))
|
|
162
|
+
throw new FleetError('conflict', error.message);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
// Only after the task stopped referencing it; release re-checks every retained reference under its lock.
|
|
166
|
+
if (result.releasedSnapshot)
|
|
167
|
+
releaseLaunchSnapshot(result.releasedSnapshot);
|
|
168
|
+
return result.task;
|
|
169
|
+
}
|
|
170
|
+
taskLayout(taskId) {
|
|
171
|
+
const task = readTask(taskId);
|
|
172
|
+
if (!task.layout)
|
|
173
|
+
throw new FleetError('resource_not_found', 'task has no room layout');
|
|
174
|
+
return this.layouts().view(task);
|
|
175
|
+
}
|
|
176
|
+
async launchTaskLayoutOperation(input) {
|
|
177
|
+
const task = readTask(input.taskId);
|
|
178
|
+
if (!task.layout)
|
|
179
|
+
throw new FleetError('resource_not_found', 'task has no room layout');
|
|
180
|
+
if (input.operation === 'open')
|
|
181
|
+
assertLayoutOpenable(task);
|
|
182
|
+
else if (task.deletion?.status === 'pending' || task.terminal_intent)
|
|
183
|
+
throw new FleetError('conflict', 'task is closing; its layout rooms are cleaned up by the terminal operation');
|
|
184
|
+
return this.layouts().launch(task, input.operation, input.room);
|
|
185
|
+
}
|
|
186
|
+
/** Worker entry for detached layout operations. */
|
|
187
|
+
async runTaskLayoutOperation(input) {
|
|
188
|
+
const task = readTask(input.taskId);
|
|
189
|
+
// Preconditions are enforced inside the worker before side effects; the worker is directly callable.
|
|
190
|
+
if (input.operation === 'open')
|
|
191
|
+
assertLayoutOpenable(task);
|
|
192
|
+
await this.layouts().run(task, input.operation, input.room, input.operationId);
|
|
193
|
+
return this.layouts().view(task);
|
|
194
|
+
}
|
|
74
195
|
async createTask(request) {
|
|
196
|
+
if (request.layout !== undefined)
|
|
197
|
+
return this.createLayoutTask(request);
|
|
75
198
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
76
199
|
let template = this.createTemplate(cfg, request.template, request.noRoom);
|
|
77
200
|
if (request.noRoom && request.anonymous !== undefined)
|
|
@@ -219,38 +342,61 @@ export class TaskRoomApplicationService {
|
|
|
219
342
|
return { deleted: source, moved: assigned.length, destination };
|
|
220
343
|
});
|
|
221
344
|
}
|
|
345
|
+
editTaskDescription(input) {
|
|
346
|
+
const task = updateTaskBrief(input.taskId, input.brief, input.expectedBrief);
|
|
347
|
+
// Layout rooms reference the brief file, so rooms opened later read the edited brief.
|
|
348
|
+
if (task.layout)
|
|
349
|
+
writeTaskBrief(task);
|
|
350
|
+
return task;
|
|
351
|
+
}
|
|
222
352
|
getTask(taskId) {
|
|
223
|
-
const task = readTask(taskId);
|
|
353
|
+
const task = this.withLayoutRooms(readTask(taskId));
|
|
224
354
|
return { task, orchestration: task.room_id ? getRoomRecord(task.room_id) : undefined };
|
|
225
355
|
}
|
|
356
|
+
/** Durable provisioning progress only; use active task start/work for a live readiness check. */
|
|
226
357
|
taskProvisioningOutcome(taskId) {
|
|
227
358
|
const task = readTask(taskId);
|
|
359
|
+
if (task.layout) {
|
|
360
|
+
const failed = task.state === 'failed';
|
|
361
|
+
const ready = ['active', 'review', 'done'].includes(task.state);
|
|
362
|
+
const blocker = task.outcome?.summary ?? task.blocked?.reason;
|
|
363
|
+
return {
|
|
364
|
+
kind: failed ? 'failed' : ready ? 'ready' : 'in_progress', task, room: undefined,
|
|
365
|
+
launch: { template: `layout:${task.layout.name}`, anonymous: false, owner_attached: false },
|
|
366
|
+
members: { expected: 0, active: 0, launched: 0 },
|
|
367
|
+
...(blocker ? { blocker } : {}),
|
|
368
|
+
...(!ready && !failed ? { next_action: `Correct the blocker, then run ours-fleet task start ${task.task_id}.` } : {}),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
228
371
|
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
229
|
-
const expected = room?.template_snapshot?.members.reduce((sum, member) => sum + member.count, 0)
|
|
230
|
-
?? task.execution_plan?.snapshot.members.reduce((sum, member) => sum + member.count, 0) ?? 0
|
|
372
|
+
const expected = (room?.template_snapshot?.members.reduce((sum, member) => sum + member.count, 0)
|
|
373
|
+
?? task.execution_plan?.snapshot.members.reduce((sum, member) => sum + member.count, 0) ?? 0)
|
|
374
|
+
+ (room?.additional_member_templates ?? []).reduce((n, t) => n + t.members.reduce((m, s) => m + s.count, 0), 0);
|
|
231
375
|
const active = room?.member_seats.filter(seat => seat.seat_state === 'active').length ?? 0;
|
|
232
376
|
const launched = room?.member_seats.filter(seat => seat.launch?.state === 'launched').length ?? 0;
|
|
233
377
|
const failed = task.state === 'failed' || room?.saga.phase === 'failed';
|
|
234
378
|
const ready = task.state === 'active' && room?.state === 'active'
|
|
235
379
|
&& active === expected && launched === expected;
|
|
236
380
|
const blocker = task.outcome?.summary ?? task.blocked?.reason ?? room?.saga.error;
|
|
237
|
-
const nextAction =
|
|
238
|
-
? `
|
|
239
|
-
:
|
|
240
|
-
? `Complete
|
|
241
|
-
: room?.
|
|
242
|
-
? `
|
|
243
|
-
: room?.provisioning_detail === '
|
|
244
|
-
? `
|
|
245
|
-
: room?.provisioning_detail === '
|
|
246
|
-
? `
|
|
247
|
-
: room?.provisioning_detail === '
|
|
248
|
-
? `
|
|
249
|
-
: room?.provisioning_detail === '
|
|
250
|
-
? `
|
|
251
|
-
:
|
|
252
|
-
? `
|
|
253
|
-
:
|
|
381
|
+
const nextAction = blocker === 'FLEET_SERVICE_NOT_INSTALLED: run ours-fleet init'
|
|
382
|
+
? `Run ours-fleet init, then retry ours-fleet task start ${task.task_id}.`
|
|
383
|
+
: task.terminal_intent
|
|
384
|
+
? `Complete the accepted ${task.terminal_intent.kind} operation; do not restart provisioning.`
|
|
385
|
+
: room?.state === 'closing' || room?.state === 'closed'
|
|
386
|
+
? `Complete room cleanup; do not restart provisioning.`
|
|
387
|
+
: room?.provisioning_detail === 'member_failed'
|
|
388
|
+
? `Inspect the failed launch, then run ours-fleet task start ${task.task_id}.`
|
|
389
|
+
: room?.provisioning_detail === 'waiting_owner_authorization'
|
|
390
|
+
? `Ensure ours-cowork 1.3.0 or newer is running and available, then run ours-fleet task start ${task.task_id}.`
|
|
391
|
+
: room?.provisioning_detail === 'waiting_owner_invite'
|
|
392
|
+
? `Rotate rooms.owner.public_invite, then run ours-fleet task start ${task.task_id}.`
|
|
393
|
+
: room?.provisioning_detail === 'owner_cid_mismatch'
|
|
394
|
+
? `Verify rooms.owner.expected_cid, rotate the Owner invite, then run ours-fleet task start ${task.task_id}.`
|
|
395
|
+
: room?.provisioning_detail === 'waiting_cowork'
|
|
396
|
+
? `Restore ours-cowork, then run ours-fleet task start ${task.task_id}.`
|
|
397
|
+
: failed
|
|
398
|
+
? `Correct the blocker, then run ours-fleet task start ${task.task_id}.`
|
|
399
|
+
: undefined;
|
|
254
400
|
return {
|
|
255
401
|
kind: failed ? 'failed' : ready ? 'ready' : 'in_progress', task, room,
|
|
256
402
|
launch: {
|
|
@@ -271,6 +417,10 @@ export class TaskRoomApplicationService {
|
|
|
271
417
|
const deadline = now() + (input.waitMs ?? 60_000);
|
|
272
418
|
for (;;) {
|
|
273
419
|
const outcome = this.taskProvisioningOutcome(input.taskId);
|
|
420
|
+
if (outcome.kind === 'ready') {
|
|
421
|
+
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
422
|
+
await this.assertTaskLiveReadiness(outcome.task, outcome.room, cfg);
|
|
423
|
+
}
|
|
274
424
|
if (outcome.kind !== 'in_progress' || now() >= deadline) {
|
|
275
425
|
recordTaskProvisioningOutcome(outcome);
|
|
276
426
|
return outcome;
|
|
@@ -310,6 +460,15 @@ export class TaskRoomApplicationService {
|
|
|
310
460
|
}
|
|
311
461
|
/** Worker entry: converge an accepted deletion; Cowork is resolved lazily. */
|
|
312
462
|
async settleTaskDeletion(input) {
|
|
463
|
+
// Layout resources close first; a failure stays a retryable pending deletion.
|
|
464
|
+
const deleting = (() => { try {
|
|
465
|
+
return getDeletingTask(input.taskId);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return undefined;
|
|
469
|
+
} })();
|
|
470
|
+
if (deleting?.layout)
|
|
471
|
+
await this.layouts().closeAndForget(deleting);
|
|
313
472
|
const result = await settleTaskDeletion({
|
|
314
473
|
taskId: input.taskId,
|
|
315
474
|
cowork: () => {
|
|
@@ -338,7 +497,7 @@ export class TaskRoomApplicationService {
|
|
|
338
497
|
*/
|
|
339
498
|
async launchTaskDeletionWorker(input) {
|
|
340
499
|
const launch = this.deps.launchDeletionWorker
|
|
341
|
-
?? ((taskId) => launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, this.configurationPath));
|
|
500
|
+
?? ((taskId) => launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, this.configurationPath, this.deps.binPath?.()));
|
|
342
501
|
const errorAtBefore = (() => {
|
|
343
502
|
try {
|
|
344
503
|
return getDeletingTask(input.taskId).deletion?.error_at;
|
|
@@ -391,6 +550,20 @@ export class TaskRoomApplicationService {
|
|
|
391
550
|
}
|
|
392
551
|
return this.acceptTerminal(input.taskId, 'done', roomId, input.outcome);
|
|
393
552
|
}
|
|
553
|
+
/** Terminal layout tasks archive their rooms and retire only agents their run created. */
|
|
554
|
+
async withLayoutCleanup(plan) {
|
|
555
|
+
// The task is already terminal; a worker launch failure is recorded on the run for retry.
|
|
556
|
+
if (plan.task.layout && this.layouts().runExists(plan.task.layout))
|
|
557
|
+
await this.layouts().launch(plan.task, 'close').catch(() => { });
|
|
558
|
+
return plan;
|
|
559
|
+
}
|
|
560
|
+
/** Retry terminal layout cleanup for known resources (e.g. after a failed close). */
|
|
561
|
+
async retryTaskLayoutCleanup(input) {
|
|
562
|
+
const task = readTask(input.taskId);
|
|
563
|
+
if (!task.layout || !TASK_TERMINAL_STATES.includes(task.state))
|
|
564
|
+
throw new TaskStateError('layout cleanup applies to finished or cancelled layout tasks');
|
|
565
|
+
return this.layouts().launch(task, 'close');
|
|
566
|
+
}
|
|
394
567
|
async cancelTask(input) {
|
|
395
568
|
const task = readTask(input.taskId);
|
|
396
569
|
if (!TASK_CANCELLABLE_STATES.includes(task.state))
|
|
@@ -399,6 +572,23 @@ export class TaskRoomApplicationService {
|
|
|
399
572
|
(this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
400
573
|
return this.acceptTerminal(input.taskId, 'cancelled', task.room_id);
|
|
401
574
|
}
|
|
575
|
+
/** Continue accepted operations outside the HTTP connection's lifetime. */
|
|
576
|
+
async launchTaskSettlement(taskId) {
|
|
577
|
+
try {
|
|
578
|
+
await launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, this.configurationPath, this.deps.binPath?.());
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
await recordTaskTerminalIntentError(taskId, error instanceof Error ? error.message : String(error), 'The settlement worker could not start. Inspect the task before retrying.');
|
|
582
|
+
}
|
|
583
|
+
return readTask(taskId);
|
|
584
|
+
}
|
|
585
|
+
async launchTaskProvisioning(taskId) {
|
|
586
|
+
const outcome = this.taskProvisioningOutcome(taskId);
|
|
587
|
+
if (outcome.kind === 'in_progress' && !outcome.next_action) {
|
|
588
|
+
await launchFleetWorker(['task', '_provision', taskId], `task-provision-${taskId}`, this.configurationPath, this.deps.binPath?.());
|
|
589
|
+
}
|
|
590
|
+
return outcome;
|
|
591
|
+
}
|
|
402
592
|
async settleTask(input) {
|
|
403
593
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
404
594
|
if (!cfg.rooms)
|
|
@@ -494,7 +684,7 @@ export class TaskRoomApplicationService {
|
|
|
494
684
|
throw new ConfigError('rooms: configuration is required before creating or querying rooms');
|
|
495
685
|
const cowork = this.deps.cowork ? this.deps.cowork(cfg) : createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
|
|
496
686
|
await (this.deps.provisionMembers ?? provisionMembers)({
|
|
497
|
-
cfg, cowork, roomId: room.room_id, taskId: task.task_id, template,
|
|
687
|
+
configPath: this.configurationPath, cfg, cowork, roomId: room.room_id, taskId: task.task_id, template,
|
|
498
688
|
binPath: (this.deps.binPath ?? getBinPath)(), brief: task.brief, goal: task.title,
|
|
499
689
|
});
|
|
500
690
|
task = readTask(task.task_id);
|
|
@@ -557,7 +747,8 @@ export class TaskRoomApplicationService {
|
|
|
557
747
|
}
|
|
558
748
|
async acceptTerminal(taskId, kind, roomId, outcome) {
|
|
559
749
|
const task = await acceptTaskTerminalIntent({ taskId, kind, roomId, outcome });
|
|
560
|
-
|
|
750
|
+
// Every terminal path (complete, finish, cancel) closes a layout task's rooms.
|
|
751
|
+
return this.withLayoutCleanup({ task, settlementRequired: task.terminal_intent?.status === 'pending' && !!roomId });
|
|
561
752
|
}
|
|
562
753
|
listTemplates() {
|
|
563
754
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
@@ -632,7 +823,7 @@ export class TaskRoomApplicationService {
|
|
|
632
823
|
createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
|
|
633
824
|
const room = getRoomRecord(input.roomId);
|
|
634
825
|
if (!room) {
|
|
635
|
-
|
|
826
|
+
return { room: undefined, settlementRequired: true };
|
|
636
827
|
}
|
|
637
828
|
return { room: await acceptManagedRoomClose(input.roomId), settlementRequired: true };
|
|
638
829
|
}
|
|
@@ -657,9 +848,42 @@ export class TaskRoomApplicationService {
|
|
|
657
848
|
(this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
658
849
|
return this.acceptTerminal(task.task_id, 'done', task.room_id, input.outcome);
|
|
659
850
|
}
|
|
851
|
+
/** Point-in-time health, separate from durable provisioning and lifecycle. */
|
|
852
|
+
async observeTaskReadiness(taskId) {
|
|
853
|
+
const task = readTask(taskId);
|
|
854
|
+
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
855
|
+
try {
|
|
856
|
+
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
857
|
+
return await this.observeLiveReadiness(task, room, cfg) ?? { state: 'ready' };
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
return { state: 'unknown', reason: 'readiness_unavailable' };
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async observeLiveReadiness(task, room, cfg) {
|
|
864
|
+
const cowork = this.deps.cowork ? this.deps.cowork(cfg, { timeoutMs: 2_000 })
|
|
865
|
+
: createCoworkAdapter({ configPath: cfg.rooms?.cowork?.config, timeoutMs: 2_000 });
|
|
866
|
+
const issue = await taskLiveReadiness(task, room, cowork, this.deps.liveReadiness);
|
|
867
|
+
if (issue)
|
|
868
|
+
return issue;
|
|
869
|
+
if (JSON.stringify(readTask(task.task_id)) !== JSON.stringify(task)
|
|
870
|
+
|| (room && JSON.stringify(getRoomRecord(room.room_id)) !== JSON.stringify(room)))
|
|
871
|
+
return { state: 'unknown', reason: 'records_changed' };
|
|
872
|
+
}
|
|
873
|
+
async assertTaskLiveReadiness(task, room, cfg) {
|
|
874
|
+
const issue = await this.observeLiveReadiness(task, room, cfg);
|
|
875
|
+
if (issue)
|
|
876
|
+
throw new TaskRoomApplicationError('task_not_ready', `Task readiness is ${issue.state}: ${issue.reason}. Ask Fleet Coordinator to inspect the room and member status and choose supported recovery; do not blindly relaunch or reuse invites.`, { task: task.task_id, room: task.room_id ?? 'missing', readiness: issue.state,
|
|
877
|
+
reason: issue.reason, ...(issue.member ? { member: issue.member } : {}) });
|
|
878
|
+
}
|
|
660
879
|
async ensureTaskWork(input) {
|
|
661
880
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
662
881
|
let task = readTask(input.taskId);
|
|
882
|
+
if (task.layout) {
|
|
883
|
+
if (input.template || input.members || input.anonymous !== undefined)
|
|
884
|
+
throw new FleetError('invalid_request', 'a layout task cannot take a room template or member overrides');
|
|
885
|
+
return this.ensureLayoutWork(task);
|
|
886
|
+
}
|
|
663
887
|
const recordedRoom = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
664
888
|
const recordedPolicy = storedRoomLaunchPolicy(recordedRoom?.room_policy ?? task.execution_plan?.room_policy);
|
|
665
889
|
if (task.room_id && input.anonymous !== undefined && input.anonymous !== recordedPolicy.anonymous)
|
|
@@ -681,8 +905,10 @@ export class TaskRoomApplicationService {
|
|
|
681
905
|
const pinned = durableTemplate;
|
|
682
906
|
if (!pinned)
|
|
683
907
|
throw new TaskRoomApplicationError('template_mismatch', 'active task has no durable execution plan', { room: task.room_id });
|
|
684
|
-
if (durableRequestMatches)
|
|
908
|
+
if (durableRequestMatches) {
|
|
909
|
+
await this.assertTaskLiveReadiness(task, recordedRoom, cfg);
|
|
685
910
|
return { task, status: 'already_active' };
|
|
911
|
+
}
|
|
686
912
|
const templateName = input.template ?? pinned.name;
|
|
687
913
|
const definition = resolveTemplate(templateName, cfg.roomTemplates ?? {});
|
|
688
914
|
if (!definition)
|
|
@@ -695,6 +921,7 @@ export class TaskRoomApplicationService {
|
|
|
695
921
|
provisioned: `${pinned.name}@${pinned.version}`
|
|
696
922
|
});
|
|
697
923
|
}
|
|
924
|
+
await this.assertTaskLiveReadiness(task, recordedRoom, cfg);
|
|
698
925
|
return { task, status: 'already_active' };
|
|
699
926
|
}
|
|
700
927
|
let room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
@@ -821,7 +1048,7 @@ export class TaskRoomApplicationService {
|
|
|
821
1048
|
if (!cfg.rooms)
|
|
822
1049
|
throw new ConfigError('rooms: configuration is required before creating or querying rooms');
|
|
823
1050
|
const cowork = this.deps.cowork ? this.deps.cowork(cfg) : createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
|
|
824
|
-
await (this.deps.provisionMembers ?? provisionMembers)({ cfg, cowork, roomId: room.room_id,
|
|
1051
|
+
await (this.deps.provisionMembers ?? provisionMembers)({ configPath: this.configurationPath, cfg, cowork, roomId: room.room_id,
|
|
825
1052
|
taskId: task.task_id, template: snapshot, binPath: (this.deps.binPath ?? getBinPath)(),
|
|
826
1053
|
brief: task.brief, goal: task.title });
|
|
827
1054
|
task = readTask(task.task_id);
|
|
@@ -833,7 +1060,11 @@ export class TaskRoomApplicationService {
|
|
|
833
1060
|
}
|
|
834
1061
|
}
|
|
835
1062
|
const resultingRoom = getRoomRecord(task.room_id);
|
|
836
|
-
|
|
1063
|
+
if (task.state === 'active' && resultingRoom?.state === 'active') {
|
|
1064
|
+
await this.assertTaskLiveReadiness(task, resultingRoom, cfg);
|
|
1065
|
+
return { task, status: 'ready' };
|
|
1066
|
+
}
|
|
1067
|
+
return { task, status: 'in_progress' };
|
|
837
1068
|
}
|
|
838
1069
|
createTemplate(cfg, requested, noRoom) {
|
|
839
1070
|
if (noRoom)
|
|
@@ -872,6 +1103,7 @@ export class TaskRoomApplicationService {
|
|
|
872
1103
|
const unlockSnapshot = template ? acquireLaunchSnapshotLock() : undefined;
|
|
873
1104
|
let launchTemplate;
|
|
874
1105
|
let room;
|
|
1106
|
+
let reusedRoom = false;
|
|
875
1107
|
try {
|
|
876
1108
|
launchTemplate = template ? (template.launch_snapshot_hash ? template
|
|
877
1109
|
: sealTemplateSnapshot(template, cfg.agentTemplates ?? {}, launchDefinitions)) : undefined;
|
|
@@ -885,6 +1117,25 @@ export class TaskRoomApplicationService {
|
|
|
885
1117
|
const fresh = readTask(task.task_id);
|
|
886
1118
|
if (fresh.deletion?.status === 'pending')
|
|
887
1119
|
throw new TaskRoomApplicationError('task_deleting', `task ${task.task_id} is pending deletion`, { task: task.task_id });
|
|
1120
|
+
// A concurrent caller or a restart after local Room publication must
|
|
1121
|
+
// reuse the task's one durable room, never create a second workspace/room.
|
|
1122
|
+
const candidates = listRoomRecords().filter(record => record.task_id === task.task_id);
|
|
1123
|
+
if (candidates.length > 1)
|
|
1124
|
+
throw new Error('ambiguous task room ownership');
|
|
1125
|
+
const existing = fresh.room_id ? getRoomRecord(fresh.room_id) : candidates[0];
|
|
1126
|
+
if (fresh.room_id && !existing)
|
|
1127
|
+
throw new Error('recorded task room is missing; refusing duplicate creation');
|
|
1128
|
+
if (existing) {
|
|
1129
|
+
reusedRoom = true;
|
|
1130
|
+
// Heal a crash between durable room publication and task linkage.
|
|
1131
|
+
// Return a resumable phase, leaving owner admission/member launch to
|
|
1132
|
+
// the existing continuation rather than racing the original creator.
|
|
1133
|
+
const resumable = ['persist_intent', 'create_room'].includes(existing.saga.phase)
|
|
1134
|
+
? advanceSaga(existing.room_id, attachOwner ? 'attach_owner' : 'create_members', attachOwner ? 2 : 3)
|
|
1135
|
+
: existing;
|
|
1136
|
+
onCreated(resumable);
|
|
1137
|
+
return resumable;
|
|
1138
|
+
}
|
|
888
1139
|
}
|
|
889
1140
|
const requiredRoles = new Map();
|
|
890
1141
|
if (attachOwner)
|
|
@@ -902,8 +1153,9 @@ export class TaskRoomApplicationService {
|
|
|
902
1153
|
room_id: created.room_id, room_name: roomName, room_identity_cid: created.identity_cid,
|
|
903
1154
|
task_id: task.task_id, template_snapshot: launchTemplate, room_policy: policy,
|
|
904
1155
|
});
|
|
905
|
-
|
|
906
|
-
|
|
1156
|
+
const resumable = advanceSaga(record.room_id, attachOwner ? 'attach_owner' : 'create_members', attachOwner ? 2 : 3);
|
|
1157
|
+
onCreated(resumable);
|
|
1158
|
+
return resumable;
|
|
907
1159
|
};
|
|
908
1160
|
room = task.task_id
|
|
909
1161
|
? await withFileLock(taskOperationLockPath(task.task_id), publishRoom, {}, TASK_OPERATION_LOCK_STALE_MS)
|
|
@@ -916,7 +1168,8 @@ export class TaskRoomApplicationService {
|
|
|
916
1168
|
throw error;
|
|
917
1169
|
}
|
|
918
1170
|
unlockSnapshot?.();
|
|
919
|
-
|
|
1171
|
+
if (reusedRoom)
|
|
1172
|
+
return room;
|
|
920
1173
|
if (attachOwner) {
|
|
921
1174
|
room = advanceSaga(room.room_id, 'attach_owner', 2);
|
|
922
1175
|
await this.setOwnerRoomCommands(cowork, room.room_id, rooms.owner.role);
|
|
@@ -941,7 +1194,7 @@ export class TaskRoomApplicationService {
|
|
|
941
1194
|
if (launchTemplate && (launchTemplate.members.length > 0 || attachOwner))
|
|
942
1195
|
try {
|
|
943
1196
|
room = await (this.deps.provisionMembers ?? provisionMembers)({
|
|
944
|
-
cfg, cowork, roomId: room.room_id, taskId: task.task_id, template: launchTemplate,
|
|
1197
|
+
configPath: this.configurationPath, cfg, cowork, roomId: room.room_id, taskId: task.task_id, template: launchTemplate,
|
|
945
1198
|
binPath: (this.deps.binPath ?? getBinPath)(), brief: task.brief,
|
|
946
1199
|
goal: task.task_id ? task.title : task.goal,
|
|
947
1200
|
});
|
|
@@ -26,6 +26,8 @@ export interface RoleRecord {
|
|
|
26
26
|
id: string;
|
|
27
27
|
lifetime: RoleLifetime;
|
|
28
28
|
configured: boolean;
|
|
29
|
+
/** Read-only display ownership; never authorizes lifecycle operations. */
|
|
30
|
+
taskId?: string;
|
|
29
31
|
config?: ResolvedRoleView;
|
|
30
32
|
stateRef?: {
|
|
31
33
|
lifetime: 'permanent' | 'temporary';
|
package/dist/atomic-file.d.ts
CHANGED
|
@@ -36,3 +36,8 @@ export declare function withFileLock<T>(lockPath: string, fn: () => T | Promise<
|
|
|
36
36
|
* intact.
|
|
37
37
|
*/
|
|
38
38
|
export declare function replaceFileAtomically(path: string, contents: string, mode?: number, deps?: WriteDeps): void;
|
|
39
|
+
/** Synchronous Lamport bakery lock: unique claims are never reused, so stale
|
|
40
|
+
* reclamation cannot remove a successor's lock. Atomic claim replacement keeps
|
|
41
|
+
* choosing/ticket reads complete. Dead claims can be removed by any contender.
|
|
42
|
+
*/
|
|
43
|
+
export declare function withSynchronousFileLock<T>(path: string, work: () => T): T;
|
package/dist/atomic-file.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
|
|
1
|
+
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, writeSync, } from 'node:fs';
|
|
2
2
|
import { basename, dirname, join } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
const DEFAULT_STALE_MS = 10_000;
|
|
@@ -136,3 +136,64 @@ export function replaceFileAtomically(path, contents, mode = 0o600, deps = {}) {
|
|
|
136
136
|
}
|
|
137
137
|
catch { /* platform does not allow fsync on a directory */ }
|
|
138
138
|
}
|
|
139
|
+
/** Synchronous Lamport bakery lock: unique claims are never reused, so stale
|
|
140
|
+
* reclamation cannot remove a successor's lock. Atomic claim replacement keeps
|
|
141
|
+
* choosing/ticket reads complete. Dead claims can be removed by any contender.
|
|
142
|
+
*/
|
|
143
|
+
export function withSynchronousFileLock(path, work) {
|
|
144
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
145
|
+
const token = `${process.pid}-${randomUUID()}.json`;
|
|
146
|
+
const own = join(path, token);
|
|
147
|
+
const deadline = Date.now() + 10_000;
|
|
148
|
+
const claims = () => {
|
|
149
|
+
const result = [];
|
|
150
|
+
for (const name of readdirSync(path).filter(name => /^[0-9]+-[a-f0-9-]{36}\.json$/.test(name))) {
|
|
151
|
+
const file = join(path, name);
|
|
152
|
+
let value;
|
|
153
|
+
try {
|
|
154
|
+
value = JSON.parse(readFileSync(file, 'utf8'));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error.code === 'ENOENT')
|
|
158
|
+
continue;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
if (!Number.isSafeInteger(value.pid) || value.pid <= 0 || !Number.isSafeInteger(value.ticket) || value.ticket < 0)
|
|
162
|
+
throw new Error('Invalid synchronous lock claim');
|
|
163
|
+
if (name !== token) {
|
|
164
|
+
try {
|
|
165
|
+
process.kill(value.pid, 0);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (error.code === 'ESRCH') {
|
|
169
|
+
// This dead contender alone owned this UUID path. Never remove
|
|
170
|
+
// the namespace or a successor's differently named claim.
|
|
171
|
+
rmSync(file, { force: true });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
result.push({ name, value });
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
};
|
|
180
|
+
replaceFileAtomically(own, JSON.stringify({ pid: process.pid, choosing: true, ticket: 0 }));
|
|
181
|
+
try {
|
|
182
|
+
const ticket = Math.max(0, ...claims().map(claim => claim.value.ticket)) + 1;
|
|
183
|
+
if (!Number.isSafeInteger(ticket))
|
|
184
|
+
throw new Error('Synchronous lock ticket overflow');
|
|
185
|
+
replaceFileAtomically(own, JSON.stringify({ pid: process.pid, choosing: false, ticket }));
|
|
186
|
+
for (;;) {
|
|
187
|
+
const preceding = claims().some(claim => claim.name !== token && (claim.value.choosing
|
|
188
|
+
|| claim.value.ticket < ticket || (claim.value.ticket === ticket && claim.name < token)));
|
|
189
|
+
if (!preceding)
|
|
190
|
+
return work();
|
|
191
|
+
if (Date.now() >= deadline)
|
|
192
|
+
throw new Error('Timed out acquiring synchronous journal lock');
|
|
193
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
rmSync(own, { force: true });
|
|
198
|
+
}
|
|
199
|
+
}
|
package/dist/briefing.d.ts
CHANGED
|
@@ -8,13 +8,15 @@ export interface BriefingOpts {
|
|
|
8
8
|
briefingBody?: string;
|
|
9
9
|
/**
|
|
10
10
|
* What spawn established about a persistent role's ours identity.
|
|
11
|
-
*
|
|
11
|
+
* The supervisor provisions temporary identities as well.
|
|
12
12
|
* Defaults to `unverified`, because a briefing generated without that
|
|
13
13
|
* knowledge must not claim one.
|
|
14
14
|
*/
|
|
15
15
|
identityGuarantee?: 'verified' | 'created' | 'unverified';
|
|
16
16
|
/** Temporary spawn whose newly-created identity should share the session lifecycle. */
|
|
17
17
|
temporaryIdentity?: boolean;
|
|
18
|
+
/** Fleet-prepared invocation guidance (src/managed-cli.ts); empty when none applies. */
|
|
19
|
+
managedCli?: string[];
|
|
18
20
|
}
|
|
19
21
|
/** Render a role's briefing.md: narrative (or curated body) + mechanical boot steps. */
|
|
20
22
|
export declare function generateBriefing(role: ResolvedRole, v: BriefingVocab, opts: BriefingOpts): string;
|