@ours.network/fleet 1.2.0-nightly.6 → 1.2.0-nightly.61
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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { lstatSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { parse, stringify } from 'yaml';
|
|
5
|
+
import { attachOursClient } from '@ours.network/sdk/client';
|
|
6
|
+
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
7
|
+
import { binderKey } from '../agent-ours/state.js';
|
|
8
|
+
import { readClientProfile } from '../client-profile.js';
|
|
9
|
+
import { readProvenance } from '../creation.js';
|
|
10
|
+
import { agentDir, stateRoot } from '../paths.js';
|
|
11
|
+
import { readTempSupervisor, tempSupervisorLiveness, TEMP_SUPERVISOR_FILE } from '../temp-lifecycle.js';
|
|
12
|
+
import { getTask } from './task-state.js';
|
|
13
|
+
import { getRoomRecord, updateMemberStartup } from './room-state.js';
|
|
14
|
+
import { assertMemberNotPermanent } from './member-ownership.js';
|
|
15
|
+
function bounded(path) {
|
|
16
|
+
const stat = lstatSync(path);
|
|
17
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
|
|
18
|
+
throw Error('LEGACY_TASK_MEMBER_UNSAFE_PROOF');
|
|
19
|
+
return readFileSync(path, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
async function verifyIdentity(name, cid, env) {
|
|
22
|
+
const profile = readClientProfile(env);
|
|
23
|
+
const client = await attachOursClient({ endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
|
|
24
|
+
credentialPath: profile.credentialPath, sessionMode: 'external', env: {}, leaseToken: `task-migration-${randomUUID()}` });
|
|
25
|
+
try {
|
|
26
|
+
const identities = await client.listIdentities();
|
|
27
|
+
const found = identities.find(value => value.name === name);
|
|
28
|
+
if (!found || !('cid' in found) || found.cid.toLowerCase() !== cid.toLowerCase())
|
|
29
|
+
throw Error('LEGACY_TASK_MEMBER_IDENTITY_MISMATCH');
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
try {
|
|
33
|
+
await client.releaseLease();
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await client.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Caller holds task and room operation locks. No service is started here.
|
|
41
|
+
* Running legacy supervisors remain untouched; stopped exact state receives
|
|
42
|
+
* only ownership metadata, preserving launch, identity and conversation.
|
|
43
|
+
*/
|
|
44
|
+
export async function adoptLegacyTaskMember(owner, name, deps = {}) {
|
|
45
|
+
return withFileLock(join(stateRoot(), 'locks', 'temp-supervisors', encodeURIComponent(name)), async () => {
|
|
46
|
+
assertMemberNotPermanent(name);
|
|
47
|
+
const dir = agentDir(name, true), task = getTask(owner.taskId), room = getRoomRecord(owner.roomId);
|
|
48
|
+
const seat = room?.member_seats.find(value => value.role_name === name);
|
|
49
|
+
const supervisor = readTempSupervisor(dir);
|
|
50
|
+
const rolePath = join(dir, 'role.yaml'), roleBytes = bounded(rolePath);
|
|
51
|
+
const role = parse(roleBytes);
|
|
52
|
+
const startup = role?.roomMemberStartup, provenance = readProvenance(dir);
|
|
53
|
+
if (task.deletion || task.terminal_intent || !['provisioning', 'active', 'review'].includes(task.state)
|
|
54
|
+
|| !room || room.close || !['provisioning', 'active'].includes(room.state) || room.task_id !== owner.taskId
|
|
55
|
+
|| task.room_id !== owner.roomId || room.room_identity_cid !== owner.roomIdentityCid
|
|
56
|
+
|| task.room_identity_cid !== owner.roomIdentityCid || !seat || seat.retirement
|
|
57
|
+
|| !supervisor || supervisor.role !== name || seat.launch?.launch_id !== supervisor.launchId
|
|
58
|
+
|| seat.launch.action_id !== owner.creationActionId || provenance?.creationActionId !== owner.creationActionId
|
|
59
|
+
|| provenance.role !== name || role.name !== name || role.identity !== name
|
|
60
|
+
|| startup?.identity_name !== name || startup.room_id !== owner.roomId
|
|
61
|
+
|| startup.room_identity_cid !== owner.roomIdentityCid || startup.invite_id !== seat.invite_id
|
|
62
|
+
|| startup.role !== seat.cowork_role || startup.invite !== ''
|
|
63
|
+
|| (startup.task_id !== undefined && startup.task_id !== owner.taskId)
|
|
64
|
+
|| (room.workspace && (role.cwd !== room.workspace.path || JSON.stringify(startup.workspace) !== JSON.stringify(room.workspace)))
|
|
65
|
+
|| bounded(join(dir, '.identity')).trim() !== name)
|
|
66
|
+
throw Error('LEGACY_TASK_MEMBER_OWNERSHIP_MISMATCH');
|
|
67
|
+
if (supervisor.taskOwner) {
|
|
68
|
+
if (Object.entries(owner).some(([key, value]) => supervisor.taskOwner[key] !== value)
|
|
69
|
+
|| startup.task_id !== owner.taskId)
|
|
70
|
+
throw Error('LEGACY_TASK_MEMBER_OWNER_MISMATCH');
|
|
71
|
+
if (supervisor.kind !== 'detached' && process.env.OURS_FLEET_SUPERVISOR !== 'none')
|
|
72
|
+
updateMemberStartup(owner.roomId, name, { launch: { ...seat.launch, task_supervised: true } });
|
|
73
|
+
return 'already-durable';
|
|
74
|
+
}
|
|
75
|
+
const live = await (deps.liveness ?? tempSupervisorLiveness)(dir);
|
|
76
|
+
if (live === 'running')
|
|
77
|
+
return 'running-legacy';
|
|
78
|
+
if (live !== 'stopped')
|
|
79
|
+
throw Error('LEGACY_TASK_MEMBER_LIVENESS_UNKNOWN');
|
|
80
|
+
const env = { ...process.env, ...role.env };
|
|
81
|
+
const profile = readClientProfile(env);
|
|
82
|
+
const privateDir = join(stateRoot(), 'private-ours', binderKey(profile.expectedInstanceId, name));
|
|
83
|
+
const state = JSON.parse(bounded(join(privateDir, 'state.json')));
|
|
84
|
+
const instance = JSON.parse(bounded(join(privateDir, 'instance.json')));
|
|
85
|
+
const externalOwner = JSON.parse(bounded(join(privateDir, 'owner.json')));
|
|
86
|
+
const launch = JSON.parse(bounded(join(stateRoot(), 'private-ours', 'launches', binderKey('temporary', name) + '.json')));
|
|
87
|
+
if (!seat.identity_cid || state.name !== name || state.daemon !== profile.expectedInstanceId
|
|
88
|
+
|| state.lifetime !== 'temporary' || state.action !== owner.creationActionId
|
|
89
|
+
|| state.cid?.toLowerCase() !== seat.identity_cid.toLowerCase()
|
|
90
|
+
|| !['READY', 'SERVING', 'RECOVERING'].includes(state.phase)
|
|
91
|
+
|| instance.role !== name || instance.temporary !== true || instance.instance !== state.instance
|
|
92
|
+
|| externalOwner.instance !== state.instance || typeof externalOwner.token !== 'string' || !externalOwner.token
|
|
93
|
+
|| state.room?.id !== owner.roomId || state.room.cid !== owner.roomIdentityCid
|
|
94
|
+
|| state.room.agentCid.toLowerCase() !== seat.identity_cid.toLowerCase() || state.room.action !== seat.invite_id
|
|
95
|
+
|| launch.role !== name || launch.identity !== name || launch.action !== owner.creationActionId)
|
|
96
|
+
throw Error('LEGACY_TASK_MEMBER_RUNTIME_NOT_RESUMABLE');
|
|
97
|
+
const sessionPath = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
|
|
98
|
+
if (!bounded(sessionPath).trim())
|
|
99
|
+
throw Error('LEGACY_TASK_MEMBER_CONVERSATION_MISSING');
|
|
100
|
+
if (deps.verifyIdentity)
|
|
101
|
+
await deps.verifyIdentity(name, seat.identity_cid);
|
|
102
|
+
else
|
|
103
|
+
await verifyIdentity(name, seat.identity_cid, env);
|
|
104
|
+
assertMemberNotPermanent(name);
|
|
105
|
+
if (bounded(rolePath) !== roleBytes || JSON.stringify(readTempSupervisor(dir)) !== JSON.stringify(supervisor))
|
|
106
|
+
throw Error('LEGACY_TASK_MEMBER_STATE_CHANGED');
|
|
107
|
+
// Crash after this write is healed by retrying the same stopped adoption.
|
|
108
|
+
startup.task_id = owner.taskId;
|
|
109
|
+
replaceFileAtomically(rolePath, stringify(role));
|
|
110
|
+
if (supervisor.kind !== 'detached' && process.env.OURS_FLEET_SUPERVISOR !== 'none')
|
|
111
|
+
updateMemberStartup(owner.roomId, name, { launch: { ...seat.launch, task_supervised: true } });
|
|
112
|
+
deps.beforeOwnerCommit?.();
|
|
113
|
+
replaceFileAtomically(join(dir, TEMP_SUPERVISOR_FILE), JSON.stringify({ ...supervisor, taskOwner: owner }, null, 2) + '\n');
|
|
114
|
+
return 'adopted';
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readRoomReadiness } from '../agent-ours/service.js';
|
|
2
|
+
import { controlRequest } from '../session/control.js';
|
|
3
|
+
import { readTempSupervisor, tempSupervisorLiveness } from '../temp-lifecycle.js';
|
|
4
|
+
import type { CoworkAdapter } from './cowork-adapter.js';
|
|
5
|
+
import type { RoomOrchestrationRecord, TaskRecord } from './types.js';
|
|
6
|
+
export type TaskReadinessIssue = {
|
|
7
|
+
state: 'degraded' | 'unknown';
|
|
8
|
+
reason: string;
|
|
9
|
+
member?: string;
|
|
10
|
+
};
|
|
11
|
+
export interface LiveReadinessDeps {
|
|
12
|
+
supervisor?: typeof readTempSupervisor;
|
|
13
|
+
liveness?: typeof tempSupervisorLiveness;
|
|
14
|
+
readiness?: typeof readRoomReadiness;
|
|
15
|
+
control?: typeof controlRequest;
|
|
16
|
+
}
|
|
17
|
+
/** Read-only observation. A failed probe never grants authority to replace a seat. */
|
|
18
|
+
export declare function taskLiveReadiness(task: TaskRecord, room: RoomOrchestrationRecord | undefined, cowork: Pick<CoworkAdapter, 'getRoom'>, deps?: LiveReadinessDeps): Promise<TaskReadinessIssue | undefined>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { realExec } from '../exec.js';
|
|
2
|
+
import { agentDir } from '../paths.js';
|
|
3
|
+
import { readRoomReadiness } from '../agent-ours/service.js';
|
|
4
|
+
import { controlRequest } from '../session/control.js';
|
|
5
|
+
import { readTempSupervisor, tempSupervisorLiveness } from '../temp-lifecycle.js';
|
|
6
|
+
/** Read-only observation. A failed probe never grants authority to replace a seat. */
|
|
7
|
+
export async function taskLiveReadiness(task, room, cowork, deps = {}) {
|
|
8
|
+
const degraded = (reason, member) => ({ state: 'degraded', reason, member });
|
|
9
|
+
const unknown = (reason, member) => ({ state: 'unknown', reason, member });
|
|
10
|
+
if (!room || task.state !== 'active' || task.room_id !== room.room_id || room.state !== 'active' || room.close || task.terminal_intent
|
|
11
|
+
|| room.task_id !== task.task_id || !room.room_identity_cid
|
|
12
|
+
|| task.room_identity_cid !== room.room_identity_cid)
|
|
13
|
+
return degraded('room_record_mismatch');
|
|
14
|
+
const baseExpected = room.template_snapshot?.members.reduce((n, member) => n + member.count, 0);
|
|
15
|
+
const expected = baseExpected === undefined ? undefined : baseExpected + (room.additional_member_templates ?? []).reduce((n, t) => n + t.members.reduce((m, s) => m + s.count, 0), 0);
|
|
16
|
+
if (expected === undefined || room.member_seats.length !== expected
|
|
17
|
+
|| new Set(room.member_seats.map(seat => seat.role_name)).size !== expected
|
|
18
|
+
|| new Set(room.member_seats.map(seat => seat.identity_cid)).size !== expected)
|
|
19
|
+
return degraded('member_roster_mismatch');
|
|
20
|
+
let remote;
|
|
21
|
+
try {
|
|
22
|
+
remote = await cowork.getRoom(room.room_id);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return unknown('room_probe_unavailable');
|
|
26
|
+
}
|
|
27
|
+
if (!remote || remote.room_id !== room.room_id || remote.identity_cid !== room.room_identity_cid
|
|
28
|
+
|| remote.state !== 'active')
|
|
29
|
+
return degraded('room_identity_or_state_mismatch');
|
|
30
|
+
if (room.owner_seat_cid && remote.seats.filter(seat => seat.identity_cid.toLowerCase() === room.owner_seat_cid.toLowerCase() && seat.seat_state === 'active').length !== 1)
|
|
31
|
+
return degraded('owner_seat_missing');
|
|
32
|
+
for (const seat of room.member_seats) {
|
|
33
|
+
const member = seat.role_name;
|
|
34
|
+
if (!/^[A-Za-z0-9_-]+$/.test(member) || seat.seat_state !== 'active' || !seat.identity_cid || !seat.invite_id
|
|
35
|
+
|| seat.launch?.state !== 'launched' || !seat.launch.launch_id || seat.retirement)
|
|
36
|
+
return degraded('member_record_incomplete', member);
|
|
37
|
+
const matches = remote.seats.filter(item => item.display_name === member && item.seat_state !== 'removed');
|
|
38
|
+
if (matches.length !== 1 || matches[0].identity_cid !== seat.identity_cid
|
|
39
|
+
|| matches[0].invite_id !== seat.invite_id || matches[0].role !== seat.cowork_role
|
|
40
|
+
|| matches[0].seat_state !== 'active')
|
|
41
|
+
return degraded('member_seat_mismatch', member);
|
|
42
|
+
const dir = agentDir(member, true);
|
|
43
|
+
try {
|
|
44
|
+
const supervisor = (deps.supervisor ?? readTempSupervisor)(dir);
|
|
45
|
+
if (!supervisor)
|
|
46
|
+
return unknown('supervisor_record_missing', member);
|
|
47
|
+
if (supervisor.role !== member || supervisor.launchId !== seat.launch.launch_id)
|
|
48
|
+
return degraded('supervisor_launch_mismatch', member);
|
|
49
|
+
const live = await (deps.liveness ?? tempSupervisorLiveness)(dir, {
|
|
50
|
+
exec: (cmd, args, opts) => realExec(cmd, args, { ...opts, timeout: 2_000 }),
|
|
51
|
+
});
|
|
52
|
+
if (live === 'stopped')
|
|
53
|
+
return degraded('supervisor_stopped', member);
|
|
54
|
+
if (live !== 'running')
|
|
55
|
+
return unknown('supervisor_liveness_unknown', member);
|
|
56
|
+
const ready = (deps.readiness ?? readRoomReadiness)(room.room_identity_cid, member);
|
|
57
|
+
if (!ready || ready.room !== room.room_id || ready.invite !== seat.invite_id || ready.cid !== seat.identity_cid)
|
|
58
|
+
return degraded('member_readiness_mismatch', member);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return unknown('supervisor_probe_unavailable', member);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const response = await (deps.control ?? controlRequest)(dir, { command: 'status', controller: false }, 2_000);
|
|
65
|
+
if (!response.ok)
|
|
66
|
+
return unknown('control_unavailable', member);
|
|
67
|
+
const status = response.result;
|
|
68
|
+
if (status?.alive === false || status?.readiness === 'failed')
|
|
69
|
+
return degraded('agent_not_ready', member);
|
|
70
|
+
if (status?.alive !== true || !['idle', 'running', 'awaiting_permission'].includes(status.readiness ?? ''))
|
|
71
|
+
return unknown('agent_readiness_unknown', member);
|
|
72
|
+
const after = (deps.supervisor ?? readTempSupervisor)(dir);
|
|
73
|
+
if (!after || after.role !== member || after.launchId !== seat.launch.launch_id)
|
|
74
|
+
return degraded('supervisor_launch_changed', member);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return unknown('control_unavailable', member);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const memberCids = new Set(room.member_seats.map(seat => seat.identity_cid));
|
|
81
|
+
const memberRoles = new Set(room.member_seats.map(seat => seat.cowork_role));
|
|
82
|
+
if (remote.seats.some(seat => seat.seat_state === 'active' && memberRoles.has(seat.role)
|
|
83
|
+
&& !memberCids.has(seat.identity_cid) && seat.identity_cid.toLowerCase() !== room.owner_seat_cid?.toLowerCase()))
|
|
84
|
+
return degraded('untracked_member_seats');
|
|
85
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** A room seat is never authority to retire a standalone persistent agent.
|
|
2
|
+
* Check both role-name collisions and a permanent role using the seat's
|
|
3
|
+
* identity name. Missing legacy task state must not bypass this fence.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertMemberNotPermanent(name: string): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { agentDir, agentsRoot } from '../paths.js';
|
|
4
|
+
/** A room seat is never authority to retire a standalone persistent agent.
|
|
5
|
+
* Check both role-name collisions and a permanent role using the seat's
|
|
6
|
+
* identity name. Missing legacy task state must not bypass this fence.
|
|
7
|
+
*/
|
|
8
|
+
export function assertMemberNotPermanent(name) {
|
|
9
|
+
if (!/^[a-zA-Z0-9_-]{1,120}$/.test(name))
|
|
10
|
+
throw new Error('Invalid retirement member name');
|
|
11
|
+
if (existsSync(agentDir(name)))
|
|
12
|
+
throw new Error(`room member '${name}' conflicts with permanent agent state; refusing retirement`);
|
|
13
|
+
let entries;
|
|
14
|
+
try {
|
|
15
|
+
entries = readdirSync(agentsRoot(), { withFileTypes: true });
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
24
|
+
continue;
|
|
25
|
+
const path = join(agentsRoot(), entry.name, '.identity');
|
|
26
|
+
let stat;
|
|
27
|
+
try {
|
|
28
|
+
stat = lstatSync(path);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code === 'ENOENT')
|
|
32
|
+
continue;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024)
|
|
36
|
+
throw new Error('Permanent identity ownership proof is unsafe; refusing member retirement');
|
|
37
|
+
if (readFileSync(path, 'utf8').trim() === name)
|
|
38
|
+
throw new Error(`room member '${name}' identity belongs to permanent agent '${entry.name}'; refusing retirement`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -6,6 +6,7 @@ export interface RoomTaskMember {
|
|
|
6
6
|
}
|
|
7
7
|
/** Build the complete task text Fleet places directly in a temporary member's briefing. */
|
|
8
8
|
export declare function buildRoomMemberTask(input: {
|
|
9
|
+
workspace?: string;
|
|
9
10
|
taskId?: string;
|
|
10
11
|
roomId: string;
|
|
11
12
|
roomIdentityCid: string;
|
|
@@ -7,6 +7,8 @@ export function buildRoomMemberTask(input) {
|
|
|
7
7
|
`Fleet Task ${input.taskId ?? '(standalone)'} — ${member.cowork_role} in room ${input.roomId}`,
|
|
8
8
|
'',
|
|
9
9
|
];
|
|
10
|
+
if (input.workspace)
|
|
11
|
+
lines.push(`Task-owned workspace: ${input.workspace}`, 'Start and keep all execution artifacts, dependency checkouts, and Git worktrees inside this workspace.', 'Clone repositories inside this workspace before adding worktrees; never attach to an external Git common directory.', 'Use distinct per-agent branches/directories for concurrent edits. Configured cwd is source context, not artifact ownership.', 'Completion, cancellation and room retirement retain artifacts. Only explicit owner deletion removes them.', 'Do not delete the workspace or its ownership marker yourself.', '');
|
|
10
12
|
if (input.goal)
|
|
11
13
|
lines.push(`Goal: ${input.goal}`);
|
|
12
14
|
if (input.brief)
|
|
@@ -3,6 +3,7 @@ import type { RoomOrchestrationRecord, TemplateSnapshot } from './types.js';
|
|
|
3
3
|
import { type FleetConfig } from '../config.js';
|
|
4
4
|
export declare function getBinPath(): string;
|
|
5
5
|
export interface ProvisionMembersInput {
|
|
6
|
+
configPath?: string;
|
|
6
7
|
cfg: FleetConfig;
|
|
7
8
|
cowork: CoworkAdapter;
|
|
8
9
|
roomId: string;
|
|
@@ -12,6 +13,8 @@ export interface ProvisionMembersInput {
|
|
|
12
13
|
brief?: string;
|
|
13
14
|
goal?: string;
|
|
14
15
|
startupWait?: Partial<StartupWaitPolicy>;
|
|
16
|
+
/** Append only the supplied new slots to an already active room. */
|
|
17
|
+
append?: boolean;
|
|
15
18
|
}
|
|
16
19
|
export interface StartupWaitPolicy {
|
|
17
20
|
timeoutMs: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readRoomReadiness } from '../agent-ours/service.js';
|
|
2
|
+
import { ensureWorkspace, validateWorkspace } from './workspace.js';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
5
|
import { join } from 'node:path';
|
|
@@ -6,7 +7,8 @@ import { parse } from 'yaml';
|
|
|
6
7
|
import { advanceSaga, setSagaError, updateMemberSeats, updateMemberStartup, activateRoom, getRoomRecord, } from './room-state.js';
|
|
7
8
|
import { activateTask, updateTaskMembers, blockTask, unblockTask, getTask, } from './task-state.js';
|
|
8
9
|
import { storedRoomLaunchPolicy } from './types.js';
|
|
9
|
-
import { spawnTemp } from '../spawn.js';
|
|
10
|
+
import { spawnTemp, resumeTaskSupervisor } from '../spawn.js';
|
|
11
|
+
import { adoptLegacyTaskMember } from './legacy-task-member.js';
|
|
10
12
|
import { effectivePermissionMode } from '../permissions.js';
|
|
11
13
|
import { selectionOrigin, summarizeResolvedLaunch, } from '../lifecycle-summary.js';
|
|
12
14
|
import { canonicalJson } from '../canonical-json.js';
|
|
@@ -14,7 +16,7 @@ import { readLaunchSnapshot, redactLaunchDefinition } from './launch-snapshot.js
|
|
|
14
16
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from '../fleet-proxy.js';
|
|
15
17
|
import { controlRequest } from '../session/control.js';
|
|
16
18
|
import { SessionControlError } from '../session/types.js';
|
|
17
|
-
import { closeManagedRoom, roomCloseLockPath } from './close.js';
|
|
19
|
+
import { closeManagedRoom, roomCloseLockPath, CLOSE_LOCK_STALE_MS } from './close.js';
|
|
18
20
|
import { withFileLock } from '../atomic-file.js';
|
|
19
21
|
import { TASK_OPERATION_LOCK_STALE_MS, taskOperationLockPath } from './terminal.js';
|
|
20
22
|
import { TaskStateError, taskDeletionState } from './task-state.js';
|
|
@@ -116,6 +118,7 @@ function settingsFor(member, cfg, sealed) {
|
|
|
116
118
|
}
|
|
117
119
|
function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCid, anonymous) {
|
|
118
120
|
return buildRoomMemberTask({
|
|
121
|
+
workspace: getRoomRecord(input.roomId)?.workspace?.path,
|
|
119
122
|
taskId: input.taskId,
|
|
120
123
|
roomId: input.roomId,
|
|
121
124
|
roomIdentityCid,
|
|
@@ -129,7 +132,7 @@ function roomTask(input, member, settings, members, roomIdentityCid, ownerSeatCi
|
|
|
129
132
|
cowork_role: member.coworkRole,
|
|
130
133
|
persona: settings.persona,
|
|
131
134
|
},
|
|
132
|
-
roster: members.map(candidate => ({
|
|
135
|
+
roster: [...(input.append ? (getRoomRecord(input.roomId)?.member_seats ?? []).filter(seat => !members.some(m => m.name === seat.role_name)).map(seat => ({ name: seat.role_name, coworkRole: seat.cowork_role })) : []), ...members].map(candidate => ({
|
|
133
136
|
role_name: candidate.name,
|
|
134
137
|
cowork_role: candidate.coworkRole,
|
|
135
138
|
})),
|
|
@@ -142,6 +145,10 @@ function launchMatches(dir, member, actionId, taskSha, roomId, roomIdentityCid,
|
|
|
142
145
|
try {
|
|
143
146
|
const role = parse(readFileSync(`${dir}/role.yaml`, 'utf8'));
|
|
144
147
|
const startup = role.roomMemberStartup;
|
|
148
|
+
const workspace = getRoomRecord(roomId)?.workspace;
|
|
149
|
+
if (workspace && (role.cwd !== workspace.path
|
|
150
|
+
|| JSON.stringify(startup?.workspace) !== JSON.stringify(workspace)))
|
|
151
|
+
return false;
|
|
145
152
|
return role.identity === member.name
|
|
146
153
|
&& startup?.room_id === roomId
|
|
147
154
|
&& startup.room_identity_cid === roomIdentityCid
|
|
@@ -201,6 +208,22 @@ async function retainRunningLaunch(input) {
|
|
|
201
208
|
} });
|
|
202
209
|
return true;
|
|
203
210
|
}
|
|
211
|
+
if (supervisor.taskOwner) {
|
|
212
|
+
await resumeTaskSupervisor(member.name, provision.binPath);
|
|
213
|
+
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
214
|
+
...retainedLaunch, state: 'launched', launch_id: supervisor.launchId,
|
|
215
|
+
presentation: retainedLaunch.presentation ?? presentationFromStatePath(dir, settings, member.coworkRole),
|
|
216
|
+
updated_at: new Date().toISOString(),
|
|
217
|
+
} });
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (provision.taskId) {
|
|
221
|
+
const adopted = await adoptLegacyTaskMember({ taskId: provision.taskId, roomId: provision.roomId,
|
|
222
|
+
roomIdentityCid, creationActionId: retainedLaunch.action_id }, member.name);
|
|
223
|
+
if (adopted !== 'running-legacy')
|
|
224
|
+
await resumeTaskSupervisor(member.name, provision.binPath);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
204
227
|
await secureStoppedTempArchive(member.name, supervisor.launchId);
|
|
205
228
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
206
229
|
...retainedLaunch, state: 'stopped', launch_id: supervisor.launchId,
|
|
@@ -209,6 +232,8 @@ async function retainRunningLaunch(input) {
|
|
|
209
232
|
return false;
|
|
210
233
|
}
|
|
211
234
|
if (seat.launch?.state === 'launched' && !existsSync(dir)) {
|
|
235
|
+
if (provision.taskId)
|
|
236
|
+
throw Error(`retained task member ${member.name} is missing; explicit reconciliation required`);
|
|
212
237
|
if (!seat.launch.launch_id || !seat.launch.action_id)
|
|
213
238
|
throw new Error(`missing durable launch identity for disappeared ${member.name}`);
|
|
214
239
|
const archive = await secureStoppedTempArchive(member.name, seat.launch.launch_id);
|
|
@@ -234,35 +259,16 @@ async function retainRunningLaunch(input) {
|
|
|
234
259
|
}
|
|
235
260
|
return false;
|
|
236
261
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Member-launch publication window: for a task-bound room, the durable launch
|
|
239
|
-
* intent, spawn, and launch record publish under the common task-operation
|
|
240
|
-
* lock so an accepted deletion linearizes strictly before (bounded abort — no
|
|
241
|
-
* spawn happens) or strictly after (the seat's launch record is visible to
|
|
242
|
-
* deletion retirement). The lock is held per member only, never across
|
|
243
|
-
* seat-wait loops.
|
|
244
|
-
*/
|
|
245
262
|
async function launchMember(input) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
assertProvisioningOpen(input.provision);
|
|
249
|
-
return launchMemberUnlocked(input);
|
|
250
|
-
}, {}, TASK_OPERATION_LOCK_STALE_MS);
|
|
251
|
-
if (!taskId)
|
|
252
|
-
return launch();
|
|
253
|
-
return withFileLock(taskOperationLockPath(taskId), () => {
|
|
254
|
-
if (taskDeletionState(taskId) !== 'none')
|
|
255
|
-
throw new Error(`task ${taskId} is pending deletion; aborting member launch for ${input.member.name}`);
|
|
256
|
-
return launch();
|
|
257
|
-
}, {}, TASK_OPERATION_LOCK_STALE_MS);
|
|
258
|
-
}
|
|
259
|
-
async function launchMemberUnlocked(input) {
|
|
263
|
+
// provisionMembers holds the task and room lifecycle locks through every launch.
|
|
264
|
+
assertProvisioningOpen(input.provision);
|
|
260
265
|
const { provision, member, settings, startup } = input;
|
|
261
266
|
const seat = getRoomRecord(provision.roomId).member_seats
|
|
262
267
|
.find(candidate => candidate.role_name === member.name);
|
|
263
268
|
const actionId = randomUUID();
|
|
264
269
|
let effectiveActionId = actionId;
|
|
265
270
|
const attempt = (seat.launch?.attempt ?? 0) + 1;
|
|
271
|
+
const taskSupervised = Boolean(provision.taskId) && process.env.OURS_FLEET_SUPERVISOR !== 'none';
|
|
266
272
|
const taskSha = sha256Text(startup.task);
|
|
267
273
|
const effectiveAgentDefinition = structuredClone(settings.definition);
|
|
268
274
|
const { projection: agentDefinition, fingerprint: agentFingerprint } = launchDefinition(effectiveAgentDefinition);
|
|
@@ -270,6 +276,7 @@ async function launchMemberUnlocked(input) {
|
|
|
270
276
|
? process.env[FLEET_PROXY_CALLER_ENV] : undefined;
|
|
271
277
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
272
278
|
state: 'intent', attempt, action_id: actionId, mission_sha256: taskSha,
|
|
279
|
+
...(taskSupervised ? { task_supervised: true } : {}),
|
|
273
280
|
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
274
281
|
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
275
282
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
@@ -278,6 +285,7 @@ async function launchMemberUnlocked(input) {
|
|
|
278
285
|
try {
|
|
279
286
|
const launched = await spawnRoomMember({
|
|
280
287
|
name: member.name,
|
|
288
|
+
configPath: provision.configPath,
|
|
281
289
|
temp: true,
|
|
282
290
|
identity: member.name,
|
|
283
291
|
agentDefinition: settings.definition,
|
|
@@ -293,6 +301,7 @@ async function launchMemberUnlocked(input) {
|
|
|
293
301
|
if (launched.creationActionId !== actionId) {
|
|
294
302
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
295
303
|
state: 'intent', attempt, action_id: launched.creationActionId,
|
|
304
|
+
...(taskSupervised ? { task_supervised: true } : {}),
|
|
296
305
|
mission_sha256: taskSha, agent_definition: agentDefinition,
|
|
297
306
|
agent_fingerprint: agentFingerprint,
|
|
298
307
|
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
@@ -310,6 +319,7 @@ async function launchMemberUnlocked(input) {
|
|
|
310
319
|
: presentationFromStatePath(launchedDir, settings, member.coworkRole);
|
|
311
320
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
312
321
|
state: 'launched', attempt, action_id: launched.creationActionId, mission_sha256: taskSha,
|
|
322
|
+
...(taskSupervised ? { task_supervised: true } : {}),
|
|
313
323
|
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
314
324
|
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
315
325
|
presentation,
|
|
@@ -320,6 +330,7 @@ async function launchMemberUnlocked(input) {
|
|
|
320
330
|
catch (error) {
|
|
321
331
|
updateMemberStartup(provision.roomId, member.name, { launch: {
|
|
322
332
|
state: 'failed', attempt, action_id: effectiveActionId, mission_sha256: taskSha,
|
|
333
|
+
...(taskSupervised ? { task_supervised: true } : {}),
|
|
323
334
|
agent_definition: agentDefinition, agent_fingerprint: agentFingerprint,
|
|
324
335
|
agent_template: settings.template, agent_template_hash: settings.templateHash,
|
|
325
336
|
...(proxyCaller ? { caller_role: proxyCaller } : {}),
|
|
@@ -349,6 +360,8 @@ function reconcileMemberSeats(roomId, members, observed, ownerSeatCid) {
|
|
|
349
360
|
&& seat.seat_state === 'active');
|
|
350
361
|
const seats = current.member_seats.map(seat => {
|
|
351
362
|
const member = members.find(candidate => candidate.name === seat.role_name);
|
|
363
|
+
if (!member)
|
|
364
|
+
return seat;
|
|
352
365
|
const readiness = readRoomReadiness(current.room_identity_cid, member.name);
|
|
353
366
|
if (!readiness || readiness.room !== roomId || readiness.invite !== seat.invite_id) {
|
|
354
367
|
complete = false;
|
|
@@ -383,9 +396,33 @@ function assertProvisioningOpen(input) {
|
|
|
383
396
|
throw new Error(`room ${input.roomId} is ${room.state}; refusing to provision members`);
|
|
384
397
|
}
|
|
385
398
|
export async function provisionMembers(input) {
|
|
399
|
+
// Same lock order as terminal/deletion settlement. Serialize concurrent retries
|
|
400
|
+
// and standalone retirement as well as member publication. Seat waits are bounded.
|
|
401
|
+
const run = () => withFileLock(roomCloseLockPath(input.roomId), () => {
|
|
402
|
+
assertProvisioningOpen(input);
|
|
403
|
+
if (input.append) {
|
|
404
|
+
const task = input.taskId ? getTask(input.taskId) : undefined;
|
|
405
|
+
const room = getRoomRecord(input.roomId);
|
|
406
|
+
if (!task || !['active', 'review'].includes(task.state) || room?.state !== 'active'
|
|
407
|
+
|| room.task_id !== task.task_id || room.room_identity_cid !== task.room_identity_cid)
|
|
408
|
+
throw new Error('Adding an agent requires the exact active task room');
|
|
409
|
+
const names = expandMembers(input.template, shortId(task.task_id)).map(m => m.name);
|
|
410
|
+
if (room.member_seats.some(seat => names.includes(seat.role_name)))
|
|
411
|
+
throw new Error('Member slot is already recorded; inspect its existing launch');
|
|
412
|
+
if (room.member_seats.some(seat => seat.seat_state !== 'active'))
|
|
413
|
+
throw new Error('Existing member admission must finish before adding an agent');
|
|
414
|
+
}
|
|
415
|
+
if (!getRoomRecord(input.roomId))
|
|
416
|
+
throw new Error(`room ${input.roomId} is missing; refusing provisioning`);
|
|
417
|
+
return provisionMembersUnlocked(input);
|
|
418
|
+
}, {}, CLOSE_LOCK_STALE_MS);
|
|
419
|
+
return input.taskId
|
|
420
|
+
? withFileLock(taskOperationLockPath(input.taskId), run, {}, TASK_OPERATION_LOCK_STALE_MS)
|
|
421
|
+
: run();
|
|
422
|
+
}
|
|
423
|
+
async function provisionMembersUnlocked(input) {
|
|
386
424
|
const { cfg, cowork, roomId, taskId, template } = input;
|
|
387
|
-
|
|
388
|
-
// Deletion-epoch pre-check; each member launch re-checks under the lock.
|
|
425
|
+
// The common task-operation lock protects this epoch through all member launches.
|
|
389
426
|
if (taskId && taskDeletionState(taskId) !== 'none')
|
|
390
427
|
throw new Error(`task ${taskId} is pending deletion; refusing to provision members`);
|
|
391
428
|
const prefix = taskId ? shortId(taskId) : `room-${shortId(roomId)}`;
|
|
@@ -397,6 +434,12 @@ export async function provisionMembers(input) {
|
|
|
397
434
|
const existing = getRoomRecord(roomId);
|
|
398
435
|
if (!existing?.room_identity_cid)
|
|
399
436
|
throw new Error(`room ${roomId} has no pinned room identity CID`);
|
|
437
|
+
if (existing.workspace) {
|
|
438
|
+
validateWorkspace(existing.workspace, taskId ? 'task' : 'room', taskId ?? roomId);
|
|
439
|
+
ensureWorkspace(existing.workspace);
|
|
440
|
+
for (const setting of settings.values())
|
|
441
|
+
setting.definition.cwd = existing.workspace.path;
|
|
442
|
+
}
|
|
400
443
|
const roomIdentityCid = existing.room_identity_cid;
|
|
401
444
|
const ownerSeatCid = existing.owner_seat_cid ?? null;
|
|
402
445
|
const roomPolicy = storedRoomLaunchPolicy(existing.room_policy);
|
|
@@ -404,22 +447,23 @@ export async function provisionMembers(input) {
|
|
|
404
447
|
const resuming = members.length > 0
|
|
405
448
|
&& members.every(member => persistedNames.has(member.name));
|
|
406
449
|
if (!resuming) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
450
|
+
if (!input.append)
|
|
451
|
+
advanceSaga(roomId, 'create_members', 3);
|
|
452
|
+
updateMemberSeats(roomId, [...(input.append ? existing.member_seats : []), ...members.map(member => {
|
|
453
|
+
const memberSettings = settings.get(member.name);
|
|
454
|
+
const evidence = launchDefinition(memberSettings.definition);
|
|
455
|
+
return ({
|
|
456
|
+
role_name: member.name,
|
|
457
|
+
slot: member.slot,
|
|
458
|
+
cowork_role: member.coworkRole,
|
|
459
|
+
seat_state: 'pending',
|
|
460
|
+
launch: { state: 'pending', attempt: 0,
|
|
461
|
+
agent_definition: evidence.projection, agent_fingerprint: evidence.fingerprint,
|
|
462
|
+
agent_template: memberSettings.template,
|
|
463
|
+
agent_template_hash: memberSettings.templateHash,
|
|
464
|
+
updated_at: new Date().toISOString() },
|
|
465
|
+
});
|
|
466
|
+
})], input.append ? template : undefined);
|
|
423
467
|
}
|
|
424
468
|
else {
|
|
425
469
|
for (const member of members) {
|
|
@@ -437,7 +481,8 @@ export async function provisionMembers(input) {
|
|
|
437
481
|
now: input.startupWait?.now ?? Date.now,
|
|
438
482
|
sleep: input.startupWait?.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms))),
|
|
439
483
|
};
|
|
440
|
-
|
|
484
|
+
if (!input.append)
|
|
485
|
+
advanceSaga(roomId, 'join_role_groups', 4);
|
|
441
486
|
try {
|
|
442
487
|
const initialRoom = await cowork.recoverRoom(roomId);
|
|
443
488
|
assertCoworkRoomPolicy(initialRoom, roomPolicy.anonymous);
|
|
@@ -482,6 +527,8 @@ export async function provisionMembers(input) {
|
|
|
482
527
|
member,
|
|
483
528
|
settings: settings.get(member.name),
|
|
484
529
|
startup: {
|
|
530
|
+
...(taskId ? { task_id: taskId } : {}),
|
|
531
|
+
workspace: existing.workspace,
|
|
485
532
|
room_id: roomId,
|
|
486
533
|
room_identity_cid: roomIdentityCid,
|
|
487
534
|
identity_name: member.name,
|
|
@@ -521,7 +568,8 @@ export async function provisionMembers(input) {
|
|
|
521
568
|
}
|
|
522
569
|
throw error;
|
|
523
570
|
}
|
|
524
|
-
|
|
571
|
+
if (!input.append)
|
|
572
|
+
advanceSaga(roomId, 'wait_seats', 5);
|
|
525
573
|
const deadline = policy.now() + policy.timeoutMs;
|
|
526
574
|
let delay = policy.initialDelayMs;
|
|
527
575
|
for (;;) {
|
|
@@ -531,10 +579,12 @@ export async function provisionMembers(input) {
|
|
|
531
579
|
if (reconciled.complete && remote.state === 'active')
|
|
532
580
|
break;
|
|
533
581
|
if (policy.now() >= deadline) {
|
|
534
|
-
|
|
582
|
+
if (!input.append)
|
|
583
|
+
advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
|
|
535
584
|
return getRoomRecord(roomId);
|
|
536
585
|
}
|
|
537
|
-
|
|
586
|
+
if (!input.append)
|
|
587
|
+
advanceSaga(roomId, 'wait_seats', 5, 'waiting_seats');
|
|
538
588
|
await policy.sleep(delay);
|
|
539
589
|
delay = Math.min(policy.maxDelayMs, Math.max(delay + 1, delay * 2));
|
|
540
590
|
}
|
|
@@ -559,9 +609,11 @@ export async function provisionMembers(input) {
|
|
|
559
609
|
};
|
|
560
610
|
});
|
|
561
611
|
updateTaskMembers(taskId, taskMembers);
|
|
562
|
-
if (getTask(taskId).blocked)
|
|
612
|
+
if (!input.append && getTask(taskId).blocked)
|
|
563
613
|
unblockTask(taskId);
|
|
564
614
|
}
|
|
615
|
+
if (input.append)
|
|
616
|
+
return getRoomRecord(roomId);
|
|
565
617
|
advanceSaga(roomId, 'activate', 6);
|
|
566
618
|
const record = activateRoom(roomId);
|
|
567
619
|
if (taskId)
|
|
@@ -592,7 +644,7 @@ export async function reconcileExistingTaskMembers(input) {
|
|
|
592
644
|
|| remote.state !== 'active')
|
|
593
645
|
throw new Error('Cowork room is not the exact active room');
|
|
594
646
|
assertCoworkRoomPolicy(remote, storedRoomLaunchPolicy(current.room_policy).anonymous);
|
|
595
|
-
if (current.owner_seat_cid && !remote.seats.some(seat => seat.identity_cid === current.owner_seat_cid && seat.seat_state === 'active'))
|
|
647
|
+
if (current.owner_seat_cid && !remote.seats.some(seat => seat.identity_cid.toLowerCase() === current.owner_seat_cid.toLowerCase() && seat.seat_state === 'active'))
|
|
596
648
|
throw new Error('expected Owner seat is not active');
|
|
597
649
|
const seats = [];
|
|
598
650
|
for (const seat of current.member_seats) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { tempSupervisorLiveness } from '../temp-lifecycle.js';
|
|
2
|
+
import { resumeTaskSupervisor } from '../spawn.js';
|
|
3
|
+
import type { CoworkAdapter } from './cowork-adapter.js';
|
|
4
|
+
export type MemberRecovery = {
|
|
5
|
+
name: string;
|
|
6
|
+
status: 'running' | 'resumed' | 'migration_pending' | 'recovery_reset';
|
|
7
|
+
};
|
|
8
|
+
export interface TaskMemberRecoveryDeps {
|
|
9
|
+
liveness?: typeof tempSupervisorLiveness;
|
|
10
|
+
resume?: typeof resumeTaskSupervisor;
|
|
11
|
+
adopt?(taskId: string, roomId: string, name: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Explicit supported recovery; never creates an identity, seat, invite, or session ID. */
|
|
14
|
+
export declare function recoverTaskMembers(input: {
|
|
15
|
+
taskId: string;
|
|
16
|
+
binPath: string;
|
|
17
|
+
cowork?: Pick<CoworkAdapter, 'getRoom'>;
|
|
18
|
+
deps?: TaskMemberRecoveryDeps;
|
|
19
|
+
}): Promise<MemberRecovery[]>;
|