@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,14 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { detachDeletedRoom } from './task-state.js';
|
|
2
|
+
import { binderKey } from '../agent-ours/state.js';
|
|
3
|
+
import { assertMemberNotPermanent } from './member-ownership.js';
|
|
4
|
+
import { retireTaskMemberService } from './task-service-retirement.js';
|
|
5
|
+
import { eraseMemberArtifacts } from './erasure.js';
|
|
6
|
+
import { deleteWorkspace, assertWorkspaceDeletable } from './workspace.js';
|
|
7
|
+
import { collectWorkspaceArchives } from './workspace-artifacts.js';
|
|
8
|
+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
2
9
|
import { join } from 'node:path';
|
|
3
10
|
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { parse } from 'yaml';
|
|
12
|
+
import { privateRuntimeRoot, releaseManagedAgent } from '../agent-ours/service.js';
|
|
4
13
|
import { attachOursClient } from '@ours.network/sdk/client';
|
|
5
14
|
import { withFileLock } from '../atomic-file.js';
|
|
6
15
|
import { agentDir, stateRoot } from '../paths.js';
|
|
7
16
|
import { readClientProfile } from '../client-profile.js';
|
|
8
|
-
import { readTempSupervisor, secureStoppedTempArchive, stopTempSupervisor, tempSupervisorLiveness, } from '../temp-lifecycle.js';
|
|
17
|
+
import { readTempSupervisor, secureStoppedTempArchive, stopTempSupervisor, tempSupervisorLiveness, tempArchiveForLaunch, tempArchiveForCreationAction, } from '../temp-lifecycle.js';
|
|
9
18
|
import { CoworkProtocolError } from './cowork-adapter.js';
|
|
10
|
-
import { advanceMemberRetirement, advanceRoomClose, beginRoomClose, closeRoom, deleteRoomRecord, getRoomRecord, listRoomRecords, setRoomCloseError, } from './room-state.js';
|
|
11
|
-
const CLOSE_LOCK_STALE_MS = 5 * 60_000;
|
|
19
|
+
import { advanceMemberRetirement, advanceRoomClose, beginRoomClose, closeRoom, deleteRoomRecord, getRoomRecord, listRoomRecords, setRoomCloseError, recordRetirementMemberCids, } from './room-state.js';
|
|
20
|
+
export const CLOSE_LOCK_STALE_MS = 5 * 60_000;
|
|
12
21
|
const STOP_POLLS = 50;
|
|
13
22
|
const STOP_POLL_MS = 100;
|
|
14
23
|
export function roomCloseLockPath(roomId) {
|
|
@@ -20,6 +29,20 @@ function errorText(error) {
|
|
|
20
29
|
function exactMemberIdentity(seat) {
|
|
21
30
|
const dir = agentDir(seat.role_name, true);
|
|
22
31
|
const identityPath = join(dir, '.identity');
|
|
32
|
+
if (existsSync(dir) && !existsSync(identityPath) && seat.launch?.action_id && seat.launch.launch_id) {
|
|
33
|
+
const supervisor = readTempSupervisor(dir);
|
|
34
|
+
let creation;
|
|
35
|
+
try {
|
|
36
|
+
creation = JSON.parse(readFileSync(join(dir, 'creation.json'), 'utf8'));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== 'ENOENT')
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (supervisor?.role === seat.role_name && supervisor.launchId === seat.launch.launch_id
|
|
43
|
+
&& creation?.role === seat.role_name && creation.creationActionId === seat.launch.action_id)
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
23
46
|
if (!existsSync(dir) || !existsSync(identityPath)) {
|
|
24
47
|
throw new Error(`room member '${seat.role_name}' has no live Fleet temp-state identity proof; refusing retirement`);
|
|
25
48
|
}
|
|
@@ -29,9 +52,11 @@ function exactMemberIdentity(seat) {
|
|
|
29
52
|
}
|
|
30
53
|
}
|
|
31
54
|
export async function inspectMember(seat) {
|
|
55
|
+
assertMemberNotPermanent(seat.role_name);
|
|
32
56
|
exactMemberIdentity(seat);
|
|
33
57
|
const supervisor = readTempSupervisor(agentDir(seat.role_name, true));
|
|
34
|
-
if (!supervisor || supervisor.role !== seat.role_name
|
|
58
|
+
if (!supervisor || supervisor.role !== seat.role_name
|
|
59
|
+
|| (seat.launch?.launch_id && supervisor.launchId !== seat.launch.launch_id)) {
|
|
35
60
|
throw new Error(`room member '${seat.role_name}' has no exact Fleet supervisor ownership proof`);
|
|
36
61
|
}
|
|
37
62
|
return { launchId: supervisor.launchId };
|
|
@@ -57,15 +82,11 @@ export async function waitForLivenessAbsent(role, launchId, lifecycleDeps = {})
|
|
|
57
82
|
async function withIdentityClient(work) {
|
|
58
83
|
const profile = readClientProfile(process.env);
|
|
59
84
|
const leaseToken = `ours-fleet-room-close-${process.pid}-${randomUUID()}`;
|
|
60
|
-
const client = await attachOursClient(
|
|
85
|
+
const client = await attachOursClient({
|
|
61
86
|
endpoint: profile.endpoint,
|
|
62
87
|
expectedInstanceId: profile.expectedInstanceId,
|
|
63
88
|
credentialPath: profile.credentialPath,
|
|
64
89
|
sessionMode: 'external', env: {}, leaseToken,
|
|
65
|
-
} : {
|
|
66
|
-
env: process.env,
|
|
67
|
-
leaseToken,
|
|
68
|
-
clientPid: process.pid,
|
|
69
90
|
});
|
|
70
91
|
try {
|
|
71
92
|
return await work(client);
|
|
@@ -89,18 +110,69 @@ export async function identityCidPresent(cid) {
|
|
|
89
110
|
return rows.some(row => 'cid' in row && row.cid.toLowerCase() === cid.toLowerCase());
|
|
90
111
|
});
|
|
91
112
|
}
|
|
113
|
+
/** A managed temporary identity belongs to its durable external lease even
|
|
114
|
+
* after worker exit. Retire that exact logical owner before generic removal. */
|
|
115
|
+
async function releaseRetainedMemberRuntime(seat) {
|
|
116
|
+
const live = agentDir(seat.role_name, true);
|
|
117
|
+
const source = existsSync(live) ? live
|
|
118
|
+
: (seat.launch?.launch_id ? tempArchiveForLaunch(seat.role_name, seat.launch.launch_id) : undefined);
|
|
119
|
+
if (!source) {
|
|
120
|
+
if (seat.launch?.task_supervised)
|
|
121
|
+
throw Error('MEMBER_RUNTIME_RETIREMENT_SOURCE_MISSING');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const path = join(source, 'role.yaml');
|
|
125
|
+
if (!existsSync(path))
|
|
126
|
+
return; // pre-managed compatibility evidence has no runtime
|
|
127
|
+
const stat = lstatSync(path);
|
|
128
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 256 * 1024)
|
|
129
|
+
throw Error('MEMBER_RUNTIME_UNSAFE_PROOF');
|
|
130
|
+
const role = parse(readFileSync(path, 'utf8'));
|
|
131
|
+
const profile = readClientProfile({ ...process.env, ...role.env });
|
|
132
|
+
const runtime = join(privateRuntimeRoot(), binderKey(profile.expectedInstanceId, seat.role_name));
|
|
133
|
+
const metadata = readTempSupervisor(source);
|
|
134
|
+
if (!existsSync(join(runtime, 'instance.json'))) {
|
|
135
|
+
if (metadata?.kind === 'fleet-managed')
|
|
136
|
+
throw Error('MEMBER_RUNTIME_RETIREMENT_MISSING');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const creationPath = join(source, 'creation.json'), creationStat = lstatSync(creationPath);
|
|
140
|
+
if (!creationStat.isFile() || creationStat.isSymbolicLink() || creationStat.size > 256 * 1024)
|
|
141
|
+
throw Error('MEMBER_RUNTIME_UNSAFE_PROOF');
|
|
142
|
+
const creation = JSON.parse(readFileSync(creationPath, 'utf8'));
|
|
143
|
+
if (role.name !== seat.role_name || role.identity !== seat.role_name || !seat.identity_cid
|
|
144
|
+
|| !seat.launch?.action_id || !seat.launch.launch_id || creation.role !== seat.role_name
|
|
145
|
+
|| creation.creationActionId !== seat.launch.action_id || metadata?.role !== seat.role_name
|
|
146
|
+
|| metadata.launchId !== seat.launch.launch_id
|
|
147
|
+
|| (metadata.taskOwner && metadata.taskOwner.creationActionId !== seat.launch.action_id))
|
|
148
|
+
throw Error('MEMBER_RUNTIME_RETIREMENT_MISMATCH');
|
|
149
|
+
await releaseManagedAgent(role, { cid: seat.identity_cid, action: seat.launch.action_id });
|
|
150
|
+
}
|
|
92
151
|
export async function removeExactMemberIdentity(seat) {
|
|
152
|
+
assertMemberNotPermanent(seat.role_name);
|
|
93
153
|
await withIdentityClient(async (client) => {
|
|
94
|
-
const
|
|
95
|
-
|
|
154
|
+
const rows = await client.listIdentities();
|
|
155
|
+
const before = listedIdentity(rows, seat.role_name);
|
|
156
|
+
if (!before) {
|
|
157
|
+
if (rows.some(row => row.name === seat.role_name || (seat.identity_cid && 'cid' in row
|
|
158
|
+
&& row.cid.toLowerCase() === seat.identity_cid.toLowerCase())))
|
|
159
|
+
throw new Error(`room member '${seat.role_name}' identity absence is not proven`);
|
|
160
|
+
// A terminal release can remove the SDK identity before its response is
|
|
161
|
+
// recorded. Finish the exact retained launch's release journal on retry.
|
|
162
|
+
if (existsSync(agentDir(seat.role_name, true)) || (seat.launch?.launch_id
|
|
163
|
+
&& tempArchiveForLaunch(seat.role_name, seat.launch.launch_id)))
|
|
164
|
+
await releaseRetainedMemberRuntime(seat);
|
|
96
165
|
return;
|
|
166
|
+
}
|
|
97
167
|
if (!seat.identity_cid) {
|
|
98
|
-
throw new Error(`room member '${seat.role_name}' exists without a recorded authenticated CID; refusing removal`);
|
|
168
|
+
throw new Error(`room member '${seat.role_name}' exists without a recorded authenticated CID; identity absence is not proven; refusing removal`);
|
|
99
169
|
}
|
|
100
170
|
if (before.cid?.toLowerCase() !== seat.identity_cid.toLowerCase()) {
|
|
101
|
-
throw new Error(`room member '${seat.role_name}' identity CID mismatch: recorded ${seat.identity_cid}, found ${before.cid ?? 'none'}`);
|
|
171
|
+
throw new Error(`room member '${seat.role_name}' identity absence is not proven: CID mismatch: recorded ${seat.identity_cid}, found ${before.cid ?? 'none'}`);
|
|
102
172
|
}
|
|
173
|
+
await releaseRetainedMemberRuntime(seat);
|
|
103
174
|
try {
|
|
175
|
+
assertMemberNotPermanent(seat.role_name);
|
|
104
176
|
await client.removeIdentity({ name: seat.role_name });
|
|
105
177
|
}
|
|
106
178
|
catch (error) {
|
|
@@ -113,13 +185,14 @@ export async function removeExactMemberIdentity(seat) {
|
|
|
113
185
|
if (after)
|
|
114
186
|
throw error;
|
|
115
187
|
}
|
|
116
|
-
const after =
|
|
117
|
-
if (after
|
|
188
|
+
const after = await client.listIdentities();
|
|
189
|
+
if (after.some(row => row.name === seat.role_name || (seat.identity_cid && 'cid' in row
|
|
190
|
+
&& row.cid.toLowerCase() === seat.identity_cid.toLowerCase()))) {
|
|
118
191
|
throw new Error(`room member '${seat.role_name}' identity still exists after remove_identity`);
|
|
119
192
|
}
|
|
120
193
|
});
|
|
121
194
|
}
|
|
122
|
-
async function assertMemberIdentityAbsent(seat) {
|
|
195
|
+
export async function assertMemberIdentityAbsent(seat) {
|
|
123
196
|
await withIdentityClient(async (client) => {
|
|
124
197
|
const rows = await client.listIdentities();
|
|
125
198
|
if (rows.some(row => row.name === seat.role_name ||
|
|
@@ -128,26 +201,52 @@ async function assertMemberIdentityAbsent(seat) {
|
|
|
128
201
|
}
|
|
129
202
|
});
|
|
130
203
|
}
|
|
204
|
+
async function retireMemberRegistration(room, current, deps) {
|
|
205
|
+
if (room.task_id)
|
|
206
|
+
await (deps.retireTaskService ?? retireTaskMemberService)(current.role_name, {
|
|
207
|
+
taskId: room.task_id, creationActionId: current.launch?.action_id,
|
|
208
|
+
taskSupervised: current.launch?.task_supervised,
|
|
209
|
+
...(current.launch?.launch_id ? { launchId: current.launch.launch_id } : {}),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
131
212
|
async function retireMember(roomId, seat, deps) {
|
|
213
|
+
assertMemberNotPermanent(seat.role_name);
|
|
132
214
|
let room = getRoomRecord(roomId);
|
|
133
215
|
let current = room.member_seats.find(candidate => candidate.role_name === seat.role_name);
|
|
216
|
+
await retireMemberRegistration(room, current, deps);
|
|
134
217
|
let retirement = current.retirement;
|
|
135
218
|
if (retirement?.phase === 'identity_absent') {
|
|
136
|
-
if (
|
|
219
|
+
if (!deps.inspectMember) {
|
|
137
220
|
if (existsSync(agentDir(current.role_name, true)))
|
|
138
|
-
throw new Error(
|
|
221
|
+
throw new Error('Retired member has replacement launch live state');
|
|
139
222
|
await assertMemberIdentityAbsent(current);
|
|
140
223
|
}
|
|
141
224
|
return;
|
|
142
225
|
}
|
|
143
226
|
if (!retirement) {
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
227
|
+
if (!existsSync(agentDir(current.role_name, true)) && current.launch?.launch_id && current.launch.action_id) {
|
|
228
|
+
const archived = tempArchiveForLaunch(current.role_name, current.launch.launch_id);
|
|
229
|
+
const created = tempArchiveForCreationAction(current.role_name, current.launch.action_id);
|
|
230
|
+
if (archived && (!created || created.path !== archived || created.launchId !== current.launch.launch_id))
|
|
231
|
+
throw new Error('Archived member creation ownership mismatch');
|
|
232
|
+
if (archived && created?.path === archived && created.launchId === current.launch.launch_id) {
|
|
233
|
+
if (readFileSync(join(archived, '.identity'), 'utf8').trim() !== current.role_name)
|
|
234
|
+
throw new Error(`room member '${current.role_name}' archive identity mismatch`);
|
|
235
|
+
if (await tempSupervisorLiveness(archived) !== 'stopped')
|
|
236
|
+
throw new Error(`room member '${current.role_name}' archived supervisor is not proven stopped`);
|
|
237
|
+
// A terminated launch can archive itself before room retirement begins.
|
|
238
|
+
// Accept its exact durable provenance only when no identity needs removal.
|
|
239
|
+
await (deps.removeIdentity ?? removeExactMemberIdentity)(current);
|
|
240
|
+
await assertMemberIdentityAbsent(current);
|
|
241
|
+
const latest = getRoomRecord(roomId)?.member_seats.find(seat => seat.role_name === current.role_name);
|
|
242
|
+
if (existsSync(agentDir(current.role_name, true))
|
|
243
|
+
|| latest?.launch?.launch_id !== current.launch.launch_id
|
|
244
|
+
|| latest?.launch?.action_id !== current.launch.action_id
|
|
245
|
+
|| latest?.identity_cid !== current.identity_cid)
|
|
246
|
+
throw new Error(`room member '${current.role_name}' changed during archived retirement proof`);
|
|
247
|
+
advanceMemberRetirement(roomId, current.role_name, 'identity_absent', current.launch.launch_id, archived);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
151
250
|
}
|
|
152
251
|
if (current.launch?.state === 'pending' && current.launch.attempt === 0) {
|
|
153
252
|
if (existsSync(agentDir(current.role_name, true))) {
|
|
@@ -157,12 +256,31 @@ async function retireMember(roomId, seat, deps) {
|
|
|
157
256
|
advanceMemberRetirement(roomId, current.role_name, 'identity_absent', 'never-launched');
|
|
158
257
|
return;
|
|
159
258
|
}
|
|
259
|
+
if (!deps.inspectMember && !existsSync(agentDir(current.role_name, true))) {
|
|
260
|
+
await (deps.removeIdentity ?? removeExactMemberIdentity)(current);
|
|
261
|
+
if (existsSync(agentDir(current.role_name, true)))
|
|
262
|
+
throw new Error(`room member '${current.role_name}' acquired replacement state during retirement`);
|
|
263
|
+
advanceMemberRetirement(roomId, current.role_name, 'identity_absent', 'absent-verified');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
160
266
|
const ownership = await (deps.inspectMember ?? inspectMember)(current);
|
|
161
267
|
room = advanceMemberRetirement(roomId, current.role_name, 'stop_requested', ownership.launchId);
|
|
162
268
|
current = room.member_seats.find(candidate => candidate.role_name === seat.role_name);
|
|
163
269
|
retirement = current.retirement;
|
|
164
270
|
}
|
|
271
|
+
if (['stop_requested', 'liveness_absent', 'archive_secured'].includes(retirement.phase) && !deps.requestStop
|
|
272
|
+
&& !existsSync(agentDir(current.role_name, true))) {
|
|
273
|
+
const archive = tempArchiveForLaunch(current.role_name, retirement.launch_id);
|
|
274
|
+
if (archive && await tempSupervisorLiveness(archive) !== 'stopped')
|
|
275
|
+
throw new Error('Archived supervisor is not proven stopped');
|
|
276
|
+
await (deps.removeIdentity ?? removeExactMemberIdentity)(current);
|
|
277
|
+
await assertMemberIdentityAbsent(current);
|
|
278
|
+
advanceMemberRetirement(roomId, current.role_name, 'identity_absent', retirement.launch_id, archive, !archive);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
165
281
|
if (retirement.phase === 'stop_requested') {
|
|
282
|
+
if (!deps.inspectMember && (await inspectMember(current)).launchId !== retirement.launch_id)
|
|
283
|
+
throw new Error('Member launch changed before stop');
|
|
166
284
|
await (deps.requestStop ?? (async (role) => { await stopTempSupervisor(role); }))(current.role_name);
|
|
167
285
|
await (deps.waitForLivenessAbsent ?? waitForLivenessAbsent)(current.role_name, retirement.launch_id);
|
|
168
286
|
room = advanceMemberRetirement(roomId, current.role_name, 'liveness_absent', retirement.launch_id);
|
|
@@ -192,17 +310,35 @@ export async function closeManagedRoom(input) {
|
|
|
192
310
|
const lock = deps.lock ?? withFileLock;
|
|
193
311
|
return lock(roomCloseLockPath(input.roomId), async () => {
|
|
194
312
|
let room = beginRoomClose(input.roomId);
|
|
195
|
-
if (room.state === 'closed')
|
|
313
|
+
if (room.state === 'closed') {
|
|
314
|
+
for (const seat of room.member_seats) {
|
|
315
|
+
assertMemberNotPermanent(seat.role_name);
|
|
316
|
+
await retireMemberRegistration(room, seat, deps);
|
|
317
|
+
}
|
|
318
|
+
if (!deps.inspectMember)
|
|
319
|
+
for (const seat of room.member_seats) {
|
|
320
|
+
if (existsSync(agentDir(seat.role_name, true)))
|
|
321
|
+
throw new Error('Closed room has replacement live state');
|
|
322
|
+
await assertMemberIdentityAbsent(seat);
|
|
323
|
+
}
|
|
196
324
|
return room;
|
|
325
|
+
}
|
|
197
326
|
try {
|
|
198
327
|
if (room.close?.phase === 'retire_members') {
|
|
328
|
+
room = await recoverMemberCids(room, input.cowork);
|
|
199
329
|
for (const seat of room.member_seats) {
|
|
200
330
|
await retireMember(input.roomId, seat, deps);
|
|
201
331
|
}
|
|
202
332
|
room = advanceRoomClose(input.roomId, 'close_cowork');
|
|
203
333
|
}
|
|
204
334
|
if (room.close?.phase === 'close_cowork') {
|
|
205
|
-
|
|
335
|
+
try {
|
|
336
|
+
await input.cowork.closeRoom(input.roomId);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (!(error instanceof CoworkProtocolError && error.code === 'not_found'))
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
206
342
|
}
|
|
207
343
|
return closeRoom(input.roomId);
|
|
208
344
|
}
|
|
@@ -216,6 +352,9 @@ export async function closeManagedRoom(input) {
|
|
|
216
352
|
export async function deleteLegacyClosedRooms(input) {
|
|
217
353
|
const deleted = [];
|
|
218
354
|
for (const room of listRoomRecords({ state: 'closed' })) {
|
|
355
|
+
// This compatibility sweep is retirement, never explicit workspace deletion.
|
|
356
|
+
if (room.workspace)
|
|
357
|
+
continue;
|
|
219
358
|
try {
|
|
220
359
|
await input.cowork.deleteRoom(room.room_id);
|
|
221
360
|
}
|
|
@@ -232,14 +371,84 @@ export async function deleteLegacyClosedRooms(input) {
|
|
|
232
371
|
}
|
|
233
372
|
/** Retire live resources through the existing cursor, then delete retained state. */
|
|
234
373
|
export async function deleteManagedRoom(input) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
374
|
+
if (getRoomRecord(input.roomId))
|
|
375
|
+
await closeManagedRoom(input);
|
|
376
|
+
return withFileLock(roomCloseLockPath(input.roomId), async () => {
|
|
377
|
+
try {
|
|
378
|
+
try {
|
|
379
|
+
await input.cowork.deleteRoom(input.roomId);
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
// An earlier explicit delete may have removed the remote room before
|
|
383
|
+
// local filesystem cleanup failed. Never restore it or repeat creation.
|
|
384
|
+
if (!(error instanceof CoworkProtocolError && error.code === 'not_found'))
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
const retained = getRoomRecord(input.roomId);
|
|
388
|
+
if (retained?.workspace && !retained.task_id) {
|
|
389
|
+
assertWorkspaceDeletable(retained.workspace, 'room', input.roomId);
|
|
390
|
+
collectWorkspaceArchives(retained.workspace);
|
|
391
|
+
deleteWorkspace(retained.workspace, 'room', input.roomId);
|
|
392
|
+
}
|
|
393
|
+
if (retained)
|
|
394
|
+
await eraseMemberArtifacts('room', input.roomId, retained.member_seats, [input.roomId]);
|
|
395
|
+
if (retained?.task_id)
|
|
396
|
+
detachDeletedRoom(retained.task_id, input.roomId);
|
|
397
|
+
deleteRoomRecord(input.roomId);
|
|
398
|
+
return { room_id: input.roomId, deleted: true };
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (getRoomRecord(input.roomId))
|
|
402
|
+
setRoomCloseError(input.roomId, errorText(error), `Fix the cleanup error and explicitly retry room delete ${input.roomId} ${input.roomId}.`);
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
405
|
+
}, {}, CLOSE_LOCK_STALE_MS);
|
|
406
|
+
}
|
|
407
|
+
/** Recover only an authenticated seat tied to this exact room and issued invite.
|
|
408
|
+
* Display names and the current daemon name inventory are never binding proof.
|
|
409
|
+
*/
|
|
410
|
+
async function recoverMemberCids(room, cowork) {
|
|
411
|
+
if (!room.member_seats.some(seat => !seat.identity_cid))
|
|
412
|
+
return room;
|
|
413
|
+
const profile = room.member_seats.some(seat => seat.launch?.action_id) ? readClientProfile(process.env) : undefined;
|
|
414
|
+
let remote;
|
|
415
|
+
if (cowork.getRoom && room.room_identity_cid) {
|
|
416
|
+
try {
|
|
417
|
+
remote = await cowork.getRoom(room.room_id);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
if (!(error instanceof CoworkProtocolError && error.code === 'not_found'))
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
if (remote && (remote.room_id !== room.room_id || remote.identity_cid.toLowerCase() !== room.room_identity_cid.toLowerCase()))
|
|
424
|
+
throw new Error('Room identity mismatch during deletion recovery');
|
|
242
425
|
}
|
|
243
|
-
|
|
244
|
-
|
|
426
|
+
const seats = room.member_seats.map(seat => {
|
|
427
|
+
let runtimeCid;
|
|
428
|
+
if (profile && seat.launch?.action_id) {
|
|
429
|
+
const dir = join(stateRoot(), 'private-ours', binderKey(profile.expectedInstanceId, seat.role_name));
|
|
430
|
+
try {
|
|
431
|
+
const state = JSON.parse(readFileSync(join(dir, 'state.json'), 'utf8'));
|
|
432
|
+
const instance = JSON.parse(readFileSync(join(dir, 'instance.json'), 'utf8'));
|
|
433
|
+
if (state.name === seat.role_name && state.daemon === profile.expectedInstanceId
|
|
434
|
+
&& state.lifetime === 'temporary' && state.action === seat.launch.action_id
|
|
435
|
+
&& instance.role === seat.role_name && instance.temporary === true && instance.instance === state.instance)
|
|
436
|
+
runtimeCid = state.cid;
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
if (error.code !== 'ENOENT')
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const matches = seat.invite_id ? remote?.seats.filter(value => value.invite_id === seat.invite_id && value.role === seat.cowork_role) ?? [] : [];
|
|
444
|
+
if (matches.length > 1)
|
|
445
|
+
throw new Error('Ambiguous authenticated member identity during deletion recovery');
|
|
446
|
+
const candidates = [seat.identity_cid, runtimeCid, matches[0]?.identity_cid].filter((v) => !!v);
|
|
447
|
+
if (candidates.some(cid => !/^[a-f0-9]{64}$/i.test(cid)))
|
|
448
|
+
throw new Error('Invalid authenticated member CID');
|
|
449
|
+
if (new Set(candidates.map(cid => cid.toLowerCase())).size > 1)
|
|
450
|
+
throw new Error('Contradictory authenticated member CID evidence');
|
|
451
|
+
return candidates.length ? { ...seat, identity_cid: candidates[0] } : seat;
|
|
452
|
+
});
|
|
453
|
+
return recordRetirementMemberCids(room.room_id, seats);
|
|
245
454
|
}
|
|
@@ -4,7 +4,7 @@ export declare class RoomsTasksConfigError extends Error {
|
|
|
4
4
|
constructor(path: string, message: string);
|
|
5
5
|
}
|
|
6
6
|
export declare function fingerprint(invite: string): string;
|
|
7
|
-
export declare function validateRoomsConfig(raw: unknown, vars: Record<string, string>, path: string): RoomsConfig & {
|
|
7
|
+
export declare function validateRoomsConfig(raw: unknown, vars: Record<string, string>, path: string, deferredInviteFile?: string): RoomsConfig & {
|
|
8
8
|
_invite?: {
|
|
9
9
|
value: string;
|
|
10
10
|
fingerprint: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute } from 'node:path';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import { ROOMS_KEYS as RK, ROOMS_OWNER_KEYS as ROK, ROOMS_COWORK_KEYS as RCK, ROOMS_DEFAULTS_KEYS as RDK, TASKS_KEYS as TK, TEMPLATE_KEYS as TPK, TEMPLATE_MEMBER_KEYS as TMK, TEMPLATE_ROOM_KEYS as TRK, } from './types.js';
|
|
4
5
|
const isPlainObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
@@ -18,7 +19,7 @@ function rejectUnknown(obj, allowed, path, section) {
|
|
|
18
19
|
export function fingerprint(invite) {
|
|
19
20
|
return createHash('sha256').update(invite.trim()).digest('hex');
|
|
20
21
|
}
|
|
21
|
-
function resolveInvite(owner, vars, path) {
|
|
22
|
+
function resolveInvite(owner, vars, path, deferredInviteFile) {
|
|
22
23
|
const inline = owner.public_invite;
|
|
23
24
|
const file = owner.public_invite_file;
|
|
24
25
|
if (inline && file)
|
|
@@ -28,6 +29,10 @@ function resolveInvite(owner, vars, path) {
|
|
|
28
29
|
let raw;
|
|
29
30
|
if (file) {
|
|
30
31
|
const resolved = file.replace(/\$\{(\w+)\}/g, (m, k) => (k in vars ? String(vars[k]) : m));
|
|
32
|
+
// Tunnel setup writes this one file itself after enrollment; until then it may be absent.
|
|
33
|
+
// Only the literal absolute path counts: a relative spelling that happens to reach it does not.
|
|
34
|
+
if (!existsSync(resolved) && deferredInviteFile !== undefined && isAbsolute(deferredInviteFile) && resolved === deferredInviteFile)
|
|
35
|
+
return undefined;
|
|
31
36
|
if (!existsSync(resolved))
|
|
32
37
|
throw new RoomsTasksConfigError(path, `rooms.owner.public_invite_file: not found: ${resolved}`);
|
|
33
38
|
raw = readFileSync(resolved, 'utf8').trim();
|
|
@@ -39,7 +44,7 @@ function resolveInvite(owner, vars, path) {
|
|
|
39
44
|
throw new RoomsTasksConfigError(path, 'rooms.owner: invite is empty');
|
|
40
45
|
return { value: raw, fingerprint: fingerprint(raw) };
|
|
41
46
|
}
|
|
42
|
-
export function validateRoomsConfig(raw, vars, path) {
|
|
47
|
+
export function validateRoomsConfig(raw, vars, path, deferredInviteFile) {
|
|
43
48
|
if (!isPlainObject(raw))
|
|
44
49
|
throw new RoomsTasksConfigError(path, 'rooms: must be a mapping');
|
|
45
50
|
// Migration-only input: provider selection was exposed before rooms shipped,
|
|
@@ -54,7 +59,9 @@ export function validateRoomsConfig(raw, vars, path) {
|
|
|
54
59
|
if (!isPlainObject(raw.cowork))
|
|
55
60
|
throw new RoomsTasksConfigError(path, 'rooms.cowork: must be a mapping');
|
|
56
61
|
rejectUnknown(raw.cowork, RCK, path, 'rooms.cowork');
|
|
57
|
-
|
|
62
|
+
if (raw.cowork.config !== undefined)
|
|
63
|
+
throw new RoomsTasksConfigError(path, 'rooms.cowork.config is no longer supported; remove it and configure ~/.ours-client/profile.json for all services');
|
|
64
|
+
cowork = {};
|
|
58
65
|
}
|
|
59
66
|
if (!isPlainObject(raw.owner))
|
|
60
67
|
throw new RoomsTasksConfigError(path, 'rooms.owner: required and must be a mapping');
|
|
@@ -66,7 +73,7 @@ export function validateRoomsConfig(raw, vars, path) {
|
|
|
66
73
|
if (!CID_RE.test(expectedCid))
|
|
67
74
|
throw new RoomsTasksConfigError(path, 'rooms.owner.expected_cid: must be exactly 64 hexadecimal characters');
|
|
68
75
|
const ownerRole = raw.owner.role ?? 'Owner';
|
|
69
|
-
const invite = resolveInvite(raw.owner, vars, path);
|
|
76
|
+
const invite = resolveInvite(raw.owner, vars, path, deferredInviteFile);
|
|
70
77
|
const owner = {
|
|
71
78
|
provider: ownerProvider,
|
|
72
79
|
expected_cid: expectedCid.toLowerCase(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed client for the ours-cowork v1
|
|
2
|
+
* Typed client for the ours-cowork v1 gateway management protocol.
|
|
3
3
|
*
|
|
4
4
|
* Cowork owns room state. Fleet only invokes the versioned JSON RPC exposed
|
|
5
|
-
*
|
|
5
|
+
* through the shared HTTP gateway and projects the response into orchestration data.
|
|
6
6
|
*/
|
|
7
7
|
import type { Socket } from 'node:net';
|
|
8
8
|
import type { RoomHistoryEvidence } from './types.js';
|
|
@@ -108,6 +108,4 @@ export interface CoworkAdapterOptions {
|
|
|
108
108
|
home?: string;
|
|
109
109
|
connect?: (path: string) => Socket;
|
|
110
110
|
}
|
|
111
|
-
/** Resolve the same config/state inputs as ours-cowork and return its Unix socket. */
|
|
112
|
-
export declare function resolveCoworkSocketPath(options?: CoworkAdapterOptions): string;
|
|
113
111
|
export declare function createCoworkAdapter(options?: CoworkAdapterOptions): CoworkAdapter;
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed client for the ours-cowork v1
|
|
2
|
+
* Typed client for the ours-cowork v1 gateway management protocol.
|
|
3
3
|
*
|
|
4
4
|
* Cowork owns room state. Fleet only invokes the versioned JSON RPC exposed
|
|
5
|
-
*
|
|
5
|
+
* through the shared HTTP gateway and projects the response into orchestration data.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
|
-
import { join, resolve } from 'node:path';
|
|
10
|
-
import { createConnection } from 'node:net';
|
|
11
7
|
import { readClientProfile } from '../client-profile.js';
|
|
12
8
|
import { coworkHttpCall } from './cowork-http.js';
|
|
13
|
-
const RPC_VERSION = 1;
|
|
14
9
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
|
-
const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
16
10
|
export class CoworkUnavailableError extends Error {
|
|
17
|
-
constructor(message = 'Cowork management
|
|
11
|
+
constructor(message = 'Cowork gateway management is not reachable', options) {
|
|
18
12
|
super(message, options);
|
|
19
13
|
this.name = 'CoworkUnavailableError';
|
|
20
14
|
}
|
|
@@ -171,121 +165,13 @@ function projectHistoryRecord(value) {
|
|
|
171
165
|
}
|
|
172
166
|
return undefined;
|
|
173
167
|
}
|
|
174
|
-
/** Resolve the same config/state inputs as ours-cowork and return its Unix socket. */
|
|
175
|
-
export function resolveCoworkSocketPath(options = {}) {
|
|
176
|
-
if (options.socketPath)
|
|
177
|
-
return resolve(options.socketPath);
|
|
178
|
-
const env = options.env ?? process.env;
|
|
179
|
-
const home = options.home ?? homedir();
|
|
180
|
-
const configuredPath = options.configPath ?? env.OURS_COWORK_CONFIG;
|
|
181
|
-
const configPath = resolve(configuredPath ?? join(home, '.ours-cowork', 'config.json'));
|
|
182
|
-
let stateDir = resolve(home, '.ours-cowork');
|
|
183
|
-
if (existsSync(configPath)) {
|
|
184
|
-
let parsed;
|
|
185
|
-
try {
|
|
186
|
-
parsed = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
throw new CoworkProtocolError('config', `malformed config at ${configPath}`, 'invalid_config', { cause: error });
|
|
190
|
-
}
|
|
191
|
-
const config = object(parsed, 'config', 'config');
|
|
192
|
-
if (config.version !== 1 || typeof config.stateDir !== 'string' || !config.stateDir)
|
|
193
|
-
throw new CoworkProtocolError('config', `invalid Cowork v1 config at ${configPath}`, 'invalid_config');
|
|
194
|
-
stateDir = resolve(config.stateDir);
|
|
195
|
-
}
|
|
196
|
-
else if (configuredPath !== undefined) {
|
|
197
|
-
throw new CoworkProtocolError('config', `configured Cowork config does not exist: ${configPath}`, 'invalid_config');
|
|
198
|
-
}
|
|
199
|
-
if (env.OURS_COWORK_STATE_DIR)
|
|
200
|
-
stateDir = resolve(env.OURS_COWORK_STATE_DIR);
|
|
201
|
-
return join(stateDir, 'management.sock');
|
|
202
|
-
}
|
|
203
|
-
function rpcCall(socketPath, method, params, timeoutMs, connect) {
|
|
204
|
-
const id = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
205
|
-
const request = `${JSON.stringify({ version: RPC_VERSION, id, method, params })}\n`;
|
|
206
|
-
return new Promise((resolveCall, rejectCall) => {
|
|
207
|
-
const socket = connect(socketPath);
|
|
208
|
-
let bytes = '';
|
|
209
|
-
let size = 0;
|
|
210
|
-
let settled = false;
|
|
211
|
-
let connected = false;
|
|
212
|
-
const finishError = (error) => {
|
|
213
|
-
if (settled)
|
|
214
|
-
return;
|
|
215
|
-
settled = true;
|
|
216
|
-
clearTimeout(timer);
|
|
217
|
-
socket.destroy();
|
|
218
|
-
rejectCall(error);
|
|
219
|
-
};
|
|
220
|
-
const timer = setTimeout(() => finishError(new CoworkUnavailableError(connected ? 'Cowork did not answer the management socket' : 'Cowork management socket is not reachable')), timeoutMs);
|
|
221
|
-
socket.setEncoding('utf8');
|
|
222
|
-
socket.once('connect', () => {
|
|
223
|
-
connected = true;
|
|
224
|
-
socket.write(request);
|
|
225
|
-
});
|
|
226
|
-
socket.on('data', (chunk) => {
|
|
227
|
-
if (settled)
|
|
228
|
-
return;
|
|
229
|
-
const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
|
230
|
-
size += Buffer.byteLength(text);
|
|
231
|
-
if (size > MAX_RESPONSE_BYTES) {
|
|
232
|
-
finishError(new CoworkProtocolError(method, 'response exceeded 4 MiB'));
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
bytes += text;
|
|
236
|
-
const newline = bytes.indexOf('\n');
|
|
237
|
-
if (newline < 0)
|
|
238
|
-
return;
|
|
239
|
-
if (bytes.slice(newline + 1).trim() !== '') {
|
|
240
|
-
finishError(new CoworkProtocolError(method, 'daemon returned more than one response'));
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
let response;
|
|
244
|
-
try {
|
|
245
|
-
response = object(JSON.parse(bytes.slice(0, newline)), method, 'RPC response');
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
finishError(error instanceof CoworkProtocolError
|
|
249
|
-
? error : new CoworkProtocolError(method, 'daemon returned malformed JSON', 'protocol_error', { cause: error }));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (response.version !== RPC_VERSION || response.id !== id
|
|
253
|
-
|| (Object.hasOwn(response, 'error') === Object.hasOwn(response, 'result'))) {
|
|
254
|
-
finishError(new CoworkProtocolError(method, 'daemon returned an invalid RPC response'));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
settled = true;
|
|
258
|
-
clearTimeout(timer);
|
|
259
|
-
socket.destroy();
|
|
260
|
-
if (Object.hasOwn(response, 'error')) {
|
|
261
|
-
const error = object(response.error, method, 'RPC error');
|
|
262
|
-
rejectCall(new CoworkProtocolError(method, typeof error.message === 'string' ? error.message : 'unknown RPC error', typeof error.code === 'string' ? error.code : 'rpc_error'));
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
resolveCall(response.result);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
socket.once('end', () => {
|
|
269
|
-
if (!settled)
|
|
270
|
-
finishError(new CoworkProtocolError(method, 'daemon closed without a complete response'));
|
|
271
|
-
});
|
|
272
|
-
socket.once('error', (error) => {
|
|
273
|
-
if (!settled)
|
|
274
|
-
finishError(new CoworkUnavailableError(error.code === 'EACCES' || error.code === 'EPERM'
|
|
275
|
-
? 'Cowork management socket access denied' : 'Cowork management socket is not reachable', { cause: error }));
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
168
|
export function createCoworkAdapter(options = {}) {
|
|
280
169
|
const env = options.env ?? process.env;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const profile =
|
|
284
|
-
const remote = profile?.serverUrl ? profile : undefined;
|
|
285
|
-
const socketPath = remote ? undefined : resolveCoworkSocketPath(options);
|
|
170
|
+
if (options.socketPath !== undefined || options.configPath !== undefined)
|
|
171
|
+
throw new CoworkProtocolError('config', 'Local Cowork selectors are unsupported; configure the shared gateway client profile', 'invalid_config');
|
|
172
|
+
const profile = readClientProfile({ ...env, HOME: options.home ?? env.HOME });
|
|
286
173
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
287
|
-
const
|
|
288
|
-
const call = (method, params) => remote ? coworkHttpCall(remote, method, params, timeoutMs) : rpcCall(socketPath, method, params, timeoutMs, connect);
|
|
174
|
+
const call = (method, params) => coworkHttpCall(profile, method, params, timeoutMs);
|
|
289
175
|
return {
|
|
290
176
|
async available() {
|
|
291
177
|
try {
|