@ours.network/fleet 1.2.0-nightly.9 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -68
- package/dist/account-origin.d.ts +5 -0
- package/dist/account-origin.js +31 -0
- package/dist/agent-ours/bridge.js +26 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
- package/dist/agent-ours/mcp-endpoint.js +101 -25
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +27 -1
- package/dist/agent-ours/service.js +107 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +31 -0
- package/dist/application/conversation-history.js +339 -0
- package/dist/application/conversation-role-observer.d.ts +62 -0
- package/dist/application/conversation-role-observer.js +206 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +3 -3
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/resource-observer.d.ts +43 -0
- package/dist/application/resource-observer.js +323 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +3 -1
- package/dist/application/role-repository.js +89 -54
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +63 -17
- package/dist/application/session-control.js +221 -23
- package/dist/application/supervisor-ours-tools.js +5 -11
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +261 -0
- package/dist/application/task-room-service.d.ts +101 -3
- package/dist/application/task-room-service.js +286 -33
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +34 -1
- package/dist/atomic-file.js +161 -18
- package/dist/briefing.d.ts +3 -1
- package/dist/briefing.js +120 -31
- package/dist/build-info.json +10 -5
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +391 -38
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +14 -9
- package/dist/daemon-recovery.js +124 -115
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +214 -20
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +13 -0
- package/dist/fleet-command-audit.js +31 -9
- package/dist/harness/agent-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +21 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +24 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +9 -34
- package/dist/monitor.js +90 -153
- package/dist/notifications/fleet-producer.d.ts +31 -0
- package/dist/notifications/fleet-producer.js +351 -0
- package/dist/notifications/outbox.d.ts +59 -0
- package/dist/notifications/outbox.js +199 -0
- package/dist/notifications/target-cleanup.d.ts +16 -0
- package/dist/notifications/target-cleanup.js +75 -0
- package/dist/notifications/task-producer.d.ts +20 -0
- package/dist/notifications/task-producer.js +100 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +5 -0
- package/dist/preset-migration.js +55 -0
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.js +42 -0
- package/dist/reminders/manager.d.ts +75 -0
- package/dist/reminders/manager.js +278 -0
- package/dist/reminders/runtime.d.ts +27 -0
- package/dist/reminders/runtime.js +32 -0
- package/dist/reminders/schedule.d.ts +37 -0
- package/dist/reminders/schedule.js +104 -0
- package/dist/reminders/store.d.ts +341 -0
- package/dist/reminders/store.js +762 -0
- package/dist/reminders/tools.d.ts +20 -0
- package/dist/reminders/tools.js +114 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +84 -0
- package/dist/rooms-tasks/archived-absence.d.ts +4 -0
- package/dist/rooms-tasks/archived-absence.js +11 -0
- package/dist/rooms-tasks/cli.js +182 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +224 -36
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
- package/dist/rooms-tasks/cowork-adapter.js +15 -121
- package/dist/rooms-tasks/cowork-http.js +56 -6
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +66 -10
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +286 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-review.d.ts +113 -0
- package/dist/rooms-tasks/task-review.js +561 -0
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -5
- package/dist/rooms-tasks/task-state.js +358 -50
- package/dist/rooms-tasks/task-tools.d.ts +35 -0
- package/dist/rooms-tasks/task-tools.js +134 -0
- package/dist/rooms-tasks/types.d.ts +78 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +23 -4
- package/dist/runner.js +409 -61
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +219 -23
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-observation.d.ts +25 -0
- package/dist/session/conversation-observation.js +100 -0
- package/dist/session/conversation-replay.d.ts +50 -0
- package/dist/session/conversation-replay.js +203 -0
- package/dist/session/conversation-store.d.ts +15 -1
- package/dist/session/conversation-store.js +213 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +20 -1
- package/dist/startup-readiness.js +66 -3
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +77 -0
- package/dist/subscriptions/login.js +215 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +143 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +18 -37
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +130 -0
- package/dist/watchdog/query.d.ts +3 -1
- package/dist/watchdog/query.js +2 -1
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +65 -2
- package/dist/web/auth.js +201 -11
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/conversation-stream-codec.d.ts +23 -0
- package/dist/web/conversation-stream-codec.js +110 -0
- package/dist/web/conversation-stream.d.ts +26 -0
- package/dist/web/conversation-stream.js +239 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- package/dist/web/fleet-config-service.d.ts +7 -1
- package/dist/web/fleet-config-service.js +61 -16
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +248 -0
- package/dist/web/resource-views.d.ts +138 -0
- package/dist/web/resource-views.js +519 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +132 -34
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.js +893 -71
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +83 -0
- package/dist/workspace-enrollment.js +605 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +238 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +216 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/self-reminders.md +374 -0
- package/docs/task-progress-review.md +143 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +25 -20
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +21 -13
- package/presets/fleet/roles/Critic.yaml +24 -0
- package/presets/fleet/roles/Developer.yaml +32 -0
- package/presets/fleet/roles/Engineer.yaml +72 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { enrollWorkspace, unregisterWorkspace, type AttachDaemonClient, type WorkspacePayload } from './workspace-enrollment.js';
|
|
2
|
+
export interface WorkspaceBinding {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
hostWorkspaceId: string;
|
|
5
|
+
appOrigin: string;
|
|
6
|
+
serverCid?: string;
|
|
7
|
+
proofRootCid?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function readWorkspaceBinding(): WorkspaceBinding | undefined;
|
|
10
|
+
export declare function sameWorkspaceBinding(previous: WorkspaceBinding, payload: WorkspacePayload): boolean;
|
|
11
|
+
/** A workspace ID is never retired to rerun or migrate that same workspace. */
|
|
12
|
+
export declare function requiresWorkspaceReplacement(previous: WorkspaceBinding, target: {
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
appOrigin: string;
|
|
15
|
+
serverCid: string;
|
|
16
|
+
}, migrateAppOrigin?: boolean): boolean;
|
|
17
|
+
/** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
|
|
18
|
+
export declare function confirmWorkspaceReplacement(previous: WorkspaceBinding, explicit?: boolean, effects?: {
|
|
19
|
+
write?: (value: string) => void;
|
|
20
|
+
interactive?: boolean;
|
|
21
|
+
ask?: () => Promise<string>;
|
|
22
|
+
}): Promise<boolean>;
|
|
23
|
+
export interface PendingWorkspaceReplacement {
|
|
24
|
+
version: 1 | 2;
|
|
25
|
+
previous: WorkspaceBinding & {
|
|
26
|
+
serverCid: string;
|
|
27
|
+
};
|
|
28
|
+
payload: WorkspacePayload;
|
|
29
|
+
configuration: string;
|
|
30
|
+
operationNonce: string;
|
|
31
|
+
rootCid?: string;
|
|
32
|
+
retired?: boolean;
|
|
33
|
+
cleaned?: boolean;
|
|
34
|
+
dispatchAttempted?: boolean;
|
|
35
|
+
operationExpiresAt?: number;
|
|
36
|
+
}
|
|
37
|
+
export declare function saveWorkspaceReplacement(record: PendingWorkspaceReplacement): void;
|
|
38
|
+
export declare function readWorkspaceReplacement(): PendingWorkspaceReplacement | undefined;
|
|
39
|
+
export declare function clearWorkspaceReplacement(): void;
|
|
40
|
+
/** Explicit recovery after the user removes an unsuccessful successor in the App. */
|
|
41
|
+
export declare function discardRetiredWorkspaceReplacement(): void;
|
|
42
|
+
export declare function beginWorkspaceReplacement(previous: WorkspaceBinding, payload: WorkspacePayload, configuration: string, effects?: {
|
|
43
|
+
preflight?: typeof unregisterWorkspace;
|
|
44
|
+
attach?: AttachDaemonClient;
|
|
45
|
+
}): Promise<PendingWorkspaceReplacement>;
|
|
46
|
+
/** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
|
|
47
|
+
export declare function abandonWorkspaceReplacement(effects?: {
|
|
48
|
+
inspect?: typeof unregisterWorkspace;
|
|
49
|
+
attach?: AttachDaemonClient;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
/** A failed/expired one-time command can be renewed only for its pinned successor. */
|
|
52
|
+
export declare function refreshWorkspaceReplacement(record: PendingWorkspaceReplacement, payload: WorkspacePayload, configuration: string): void;
|
|
53
|
+
/** Only tunnel files and account-issued device capabilities belong to this registration. */
|
|
54
|
+
export declare function clearReplacedWorkspaceLocalState(): Promise<void>;
|
|
55
|
+
/** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
|
|
56
|
+
export declare function finishWorkspaceReplacement(record: PendingWorkspaceReplacement, effects?: {
|
|
57
|
+
unregister?: typeof unregisterWorkspace;
|
|
58
|
+
inspect?: typeof unregisterWorkspace;
|
|
59
|
+
cleanup?: () => Promise<void>;
|
|
60
|
+
enroll?: typeof enrollWorkspace;
|
|
61
|
+
attach?: AttachDaemonClient;
|
|
62
|
+
}): Promise<{
|
|
63
|
+
origin: string;
|
|
64
|
+
hostWorkspaceId: string;
|
|
65
|
+
rootCid: string;
|
|
66
|
+
}>;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { validateAccountOrigin } from './account-origin.js';
|
|
6
|
+
import { replaceFileAtomically, withSynchronousFileLock } from './atomic-file.js';
|
|
7
|
+
import { stateRoot, workspaceBindingLock, workspaceReplacementFile } from './paths.js';
|
|
8
|
+
import { decodeWorkspacePayload, enrollWorkspace, unregisterWorkspace, checkWorkspaceConnector } from './workspace-enrollment.js';
|
|
9
|
+
import { WorkspaceDeviceStore } from './web/workspace-devices.js';
|
|
10
|
+
import { webControlPath } from './web/control.js';
|
|
11
|
+
import { WebServiceManager } from './web/service.js';
|
|
12
|
+
const workspaceDir = () => join(stateRoot(), 'workspace');
|
|
13
|
+
const replacementPath = workspaceReplacementFile;
|
|
14
|
+
const opaque = /^[\w-]{43}$/;
|
|
15
|
+
function binding(value) {
|
|
16
|
+
const text = (id, pattern) => typeof id === 'string' && pattern.test(id);
|
|
17
|
+
if (!value || typeof value !== 'object' || !text(value.workspaceId, opaque) || !text(value.hostWorkspaceId, opaque) || (value.serverCid !== undefined && !text(value.serverCid, /^[a-f0-9]{64}$/i))
|
|
18
|
+
|| (value.proofRootCid !== undefined && !text(value.proofRootCid, /^[a-f0-9]{64}$/i)))
|
|
19
|
+
throw Error('Existing workspace binding is invalid; restore it before tunnel setup');
|
|
20
|
+
return { ...value, appOrigin: validateAccountOrigin(value.appOrigin) };
|
|
21
|
+
}
|
|
22
|
+
export function readWorkspaceBinding() {
|
|
23
|
+
const file = join(workspaceDir(), 'binding.json');
|
|
24
|
+
if (!existsSync(file))
|
|
25
|
+
return;
|
|
26
|
+
let value;
|
|
27
|
+
try {
|
|
28
|
+
value = JSON.parse(readFileSync(file, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw Error('Existing workspace binding is invalid');
|
|
32
|
+
}
|
|
33
|
+
return binding(value);
|
|
34
|
+
}
|
|
35
|
+
export function sameWorkspaceBinding(previous, payload) {
|
|
36
|
+
return previous.workspaceId === payload.challenge.workspaceId && previous.appOrigin === payload.appOrigin && (!previous.serverCid || previous.serverCid.toUpperCase() === payload.serverCid.toUpperCase());
|
|
37
|
+
}
|
|
38
|
+
/** A workspace ID is never retired to rerun or migrate that same workspace. */
|
|
39
|
+
export function requiresWorkspaceReplacement(previous, target, migrateAppOrigin = false) {
|
|
40
|
+
if (previous.workspaceId !== target.workspaceId) {
|
|
41
|
+
if (!previous.serverCid)
|
|
42
|
+
throw Error('Legacy registration has no recorded server identity. Rerun its own workspace setup before replacing it');
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (previous.serverCid && previous.serverCid.toUpperCase() !== target.serverCid.toUpperCase())
|
|
46
|
+
throw Error('Same-workspace setup must retain the enrollment server identity; existing setup preserved');
|
|
47
|
+
if (previous.appOrigin !== target.appOrigin && !migrateAppOrigin)
|
|
48
|
+
throw Error('Account origin change requires explicit --migrate-app-origin; existing setup preserved');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
|
|
52
|
+
export async function confirmWorkspaceReplacement(previous, explicit = false, effects = {}) {
|
|
53
|
+
const write = effects.write ?? (value => { process.stderr.write(value); });
|
|
54
|
+
let hostname = '';
|
|
55
|
+
try {
|
|
56
|
+
const tunnel = JSON.parse(readFileSync(join(workspaceDir(), 'tunnel.json'), 'utf8'));
|
|
57
|
+
if (typeof tunnel.origin === 'string' && validateAccountOrigin(tunnel.origin))
|
|
58
|
+
hostname = ` (${new URL(tunnel.origin).hostname})`;
|
|
59
|
+
}
|
|
60
|
+
catch { }
|
|
61
|
+
write(`This host is registered to workspace ${previous.workspaceId}${hostname} at ${previous.appOrigin}.\nReplacing it removes the old account registration and revokes its linked devices. The account service retires tunnel resources it knows; a migrated database may have no old resources to delete. Installation identities, agents and data are preserved.\n`);
|
|
62
|
+
if (explicit)
|
|
63
|
+
return true;
|
|
64
|
+
if (!(effects.interactive ?? process.stdin.isTTY)) {
|
|
65
|
+
write('Existing setup preserved. Run in a terminal to confirm, or pass --replace-registration to confirm explicitly.\n');
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const ask = effects.ask ?? (async () => {
|
|
69
|
+
const terminal = createInterface({ input: process.stdin, output: process.stderr });
|
|
70
|
+
try {
|
|
71
|
+
return await terminal.question('Replace the existing registration if this command names another workspace? [y/N] ');
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
terminal.close();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
let answer = '';
|
|
78
|
+
try {
|
|
79
|
+
answer = await ask();
|
|
80
|
+
}
|
|
81
|
+
catch { /* EOF, cancellation and input failure mean no consent. */ }
|
|
82
|
+
if (/^(y|yes)$/i.test(answer.trim()))
|
|
83
|
+
return true;
|
|
84
|
+
write('Existing setup preserved.\n');
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
export function saveWorkspaceReplacement(record) {
|
|
88
|
+
withSynchronousFileLock(workspaceBindingLock(), () => replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600));
|
|
89
|
+
}
|
|
90
|
+
export function readWorkspaceReplacement() {
|
|
91
|
+
const file = replacementPath();
|
|
92
|
+
if (!existsSync(file))
|
|
93
|
+
return;
|
|
94
|
+
const stat = lstatSync(file);
|
|
95
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0 || stat.size > 65536)
|
|
96
|
+
throw Error('Unfinished replacement must be an owned private regular file');
|
|
97
|
+
let record;
|
|
98
|
+
try {
|
|
99
|
+
record = JSON.parse(readFileSync(file, 'utf8'));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw Error('Unfinished replacement record is invalid');
|
|
103
|
+
}
|
|
104
|
+
if (![1, 2].includes(record.version) || !opaque.test(record.operationNonce) || typeof record.configuration !== 'string' || resolve(record.configuration) !== record.configuration
|
|
105
|
+
|| (record.rootCid !== undefined && !/^[a-f0-9]{64}$/i.test(record.rootCid)) || (record.retired !== undefined && typeof record.retired !== 'boolean')
|
|
106
|
+
|| (record.cleaned !== undefined && typeof record.cleaned !== 'boolean') || (record.dispatchAttempted !== undefined && typeof record.dispatchAttempted !== 'boolean') || (record.version === 2 && (record.dispatchAttempted === undefined || (!Number.isSafeInteger(record.operationExpiresAt) || record.operationExpiresAt <= 0 || record.operationExpiresAt > Date.now() + 86400000 + 60000))) || (record.cleaned && !record.retired) || (record.retired && !record.rootCid))
|
|
107
|
+
throw Error('Unfinished replacement record is invalid');
|
|
108
|
+
record.previous = binding(record.previous);
|
|
109
|
+
if (!record.previous.serverCid)
|
|
110
|
+
throw Error('Unfinished replacement has no pinned server identity');
|
|
111
|
+
record.payload = decodeWorkspacePayload(Buffer.from(JSON.stringify(record.payload)).toString('base64url'), { allowExpired: true });
|
|
112
|
+
return record;
|
|
113
|
+
}
|
|
114
|
+
export function clearWorkspaceReplacement() { withSynchronousFileLock(workspaceBindingLock(), () => rmSync(replacementPath(), { force: true })); }
|
|
115
|
+
/** Explicit recovery after the user removes an unsuccessful successor in the App. */
|
|
116
|
+
export function discardRetiredWorkspaceReplacement() {
|
|
117
|
+
const record = readWorkspaceReplacement(), current = readWorkspaceBinding();
|
|
118
|
+
if (!record?.retired || !record.cleaned)
|
|
119
|
+
throw Error('Only a fully retired and locally cleaned replacement can be discarded; resume or safely abandon this operation');
|
|
120
|
+
if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
|
|
121
|
+
throw Error('The successor binding was committed or the binding changed; resume its setup instead of discarding recovery');
|
|
122
|
+
clearWorkspaceReplacement();
|
|
123
|
+
}
|
|
124
|
+
function checkReplacementService(configuration) {
|
|
125
|
+
const manager = new WebServiceManager(), service = manager.readMetadata();
|
|
126
|
+
if (service?.configuration && resolve(service.configuration) !== resolve(configuration))
|
|
127
|
+
throw Error('Installed web service uses another configuration; existing setup preserved');
|
|
128
|
+
if (!service && existsSync(webControlPath()))
|
|
129
|
+
throw Error('Stop the foreground Fleet web service before replacing its registration; existing setup preserved');
|
|
130
|
+
}
|
|
131
|
+
export async function beginWorkspaceReplacement(previous, payload, configuration, effects = {}) {
|
|
132
|
+
checkWorkspaceConnector(payload);
|
|
133
|
+
if (!requiresWorkspaceReplacement(previous, { workspaceId: payload.challenge.workspaceId, appOrigin: payload.appOrigin, serverCid: payload.serverCid }))
|
|
134
|
+
throw Error('The same workspace must never be retired');
|
|
135
|
+
checkReplacementService(configuration);
|
|
136
|
+
// The record is created only for the binding this decision was made on, and only if no other
|
|
137
|
+
// replacement or setup changed the binding or the record meanwhile; enrollment checks the same pair.
|
|
138
|
+
const bindingFile = join(workspaceDir(), 'binding.json'), current = () => existsSync(bindingFile) ? readFileSync(bindingFile, 'utf8') : null;
|
|
139
|
+
const unchanged = (bytes) => {
|
|
140
|
+
if (current() !== bytes)
|
|
141
|
+
throw Error('The workspace binding changed during setup; existing setup preserved. Run the setup command again');
|
|
142
|
+
if (existsSync(replacementPath()))
|
|
143
|
+
throw Error('Another replacement started during setup; existing setup preserved. Review it with setup-tunnel --resume');
|
|
144
|
+
};
|
|
145
|
+
const before = withSynchronousFileLock(workspaceBindingLock(), () => {
|
|
146
|
+
const bytes = current();
|
|
147
|
+
unchanged(bytes);
|
|
148
|
+
const now = readWorkspaceBinding();
|
|
149
|
+
if (!now || now.workspaceId !== previous.workspaceId || now.hostWorkspaceId !== previous.hostWorkspaceId || now.appOrigin !== previous.appOrigin || now.serverCid?.toUpperCase() !== previous.serverCid?.toUpperCase())
|
|
150
|
+
throw Error('The workspace binding changed during setup; existing setup preserved. Run the setup command again');
|
|
151
|
+
return bytes;
|
|
152
|
+
});
|
|
153
|
+
const record = { version: 2, previous: previous, payload, configuration: resolve(configuration), operationNonce: randomBytes(32).toString('base64url'), dispatchAttempted: false, operationExpiresAt: Date.now() + 86400000 - 5 * 60000 };
|
|
154
|
+
record.rootCid = await (effects.preflight ?? unregisterWorkspace)(record.previous, payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt });
|
|
155
|
+
withSynchronousFileLock(workspaceBindingLock(), () => { unchanged(before); replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600); });
|
|
156
|
+
return record;
|
|
157
|
+
}
|
|
158
|
+
/** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
|
|
159
|
+
export async function abandonWorkspaceReplacement(effects = {}) {
|
|
160
|
+
const record = readWorkspaceReplacement();
|
|
161
|
+
if (!record)
|
|
162
|
+
throw Error('There is no unfinished replacement');
|
|
163
|
+
if (record.retired || record.cleaned || record.version !== 2)
|
|
164
|
+
throw Error('Retirement may already have completed; resume this replacement instead of abandoning it');
|
|
165
|
+
const current = readWorkspaceBinding();
|
|
166
|
+
if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
|
|
167
|
+
throw Error('Workspace binding changed; replacement was not abandoned');
|
|
168
|
+
let deleted, rejected = false, expired = false, deadlineEnforced = false, oldBindingPresent = false;
|
|
169
|
+
const root = await (effects.inspect ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt, onReceipt: status => { deleted = status.deleted; rejected = status.rejected === true; expired = status.expired === true; deadlineEnforced = status.deadlineEnforced === true; oldBindingPresent = status.oldBindingPresent === true; } });
|
|
170
|
+
if (root.toUpperCase() !== record.rootCid?.toUpperCase() || deleted !== false || (record.dispatchAttempted && !(rejected && !expired && Date.now() <= record.operationExpiresAt || oldBindingPresent && deadlineEnforced && Date.now() > record.operationExpiresAt + 5 * 60000)))
|
|
171
|
+
throw Error('Retirement may already have been sent; run --resume or abandon only after the account service confirms terminal rejection');
|
|
172
|
+
clearWorkspaceReplacement();
|
|
173
|
+
}
|
|
174
|
+
/** A failed/expired one-time command can be renewed only for its pinned successor. */
|
|
175
|
+
export function refreshWorkspaceReplacement(record, payload, configuration) {
|
|
176
|
+
if (resolve(configuration) !== record.configuration)
|
|
177
|
+
throw Error('Replacement requires its original configuration');
|
|
178
|
+
if (payload.challenge.workspaceId !== record.payload.challenge.workspaceId || payload.challenge.accountId !== record.payload.challenge.accountId
|
|
179
|
+
|| payload.appOrigin !== record.payload.appOrigin || payload.serverCid.toUpperCase() !== record.payload.serverCid.toUpperCase()
|
|
180
|
+
|| payload.hostname !== record.payload.hostname)
|
|
181
|
+
throw Error('Unfinished replacement requires a fresh command for the same target workspace');
|
|
182
|
+
record.payload = payload.connectorToken === undefined && record.payload.connectorToken !== undefined ? { ...payload, connectorToken: record.payload.connectorToken } : payload;
|
|
183
|
+
saveWorkspaceReplacement(record);
|
|
184
|
+
}
|
|
185
|
+
/** Only tunnel files and account-issued device capabilities belong to this registration. */
|
|
186
|
+
export async function clearReplacedWorkspaceLocalState() {
|
|
187
|
+
const manager = new WebServiceManager();
|
|
188
|
+
if (manager.readMetadata())
|
|
189
|
+
await manager.stop();
|
|
190
|
+
const devices = new WorkspaceDeviceStore();
|
|
191
|
+
try {
|
|
192
|
+
devices.revokeAll();
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
devices.close();
|
|
196
|
+
}
|
|
197
|
+
for (const file of ['tunnel.json', 'connector', 'pending-setup.json'])
|
|
198
|
+
rmSync(join(workspaceDir(), file), { force: true });
|
|
199
|
+
}
|
|
200
|
+
/** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
|
|
201
|
+
export async function finishWorkspaceReplacement(record, effects = {}) {
|
|
202
|
+
const current = readWorkspaceBinding();
|
|
203
|
+
if (!current || (current.workspaceId === record.previous.workspaceId ? (current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase() || current.hostWorkspaceId !== record.previous.hostWorkspaceId) : !sameWorkspaceBinding(current, record.payload)))
|
|
204
|
+
throw Error('Workspace binding changed during replacement; review it before continuing');
|
|
205
|
+
// A saved successor binding may predate a refreshed challenge. Enrollment
|
|
206
|
+
// checks the exact proof receipt: it reuses confirmed proof, or signs the new
|
|
207
|
+
// challenge, rather than assuming every challenge for this binding is done.
|
|
208
|
+
if (!record.retired) {
|
|
209
|
+
checkReplacementService(record.configuration);
|
|
210
|
+
if (record.version === 2 && Date.now() > record.operationExpiresAt + 5 * 60000) {
|
|
211
|
+
if (record.payload.challenge.expiresAt <= Date.now())
|
|
212
|
+
throw Error('Retirement deadline passed. Obtain a fresh App command for the same target and pass --replace-registration to continue');
|
|
213
|
+
let deleted, enforced = false;
|
|
214
|
+
const root = await (effects.inspect ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt, onReceipt: status => { deleted = status.deleted; enforced = status.deadlineEnforced === true; } });
|
|
215
|
+
if (root.toUpperCase() !== record.rootCid?.toUpperCase())
|
|
216
|
+
throw Error('Original proof root changed; existing recovery preserved');
|
|
217
|
+
if (deleted === false && enforced) {
|
|
218
|
+
// The expired command cannot act. Existing consent covers this exact
|
|
219
|
+
// previous/successor pair, including signed absence after a lost receipt.
|
|
220
|
+
record.operationNonce = randomBytes(32).toString('base64url');
|
|
221
|
+
record.operationExpiresAt = Date.now() + 86400000 - 5 * 60000;
|
|
222
|
+
record.dispatchAttempted = false;
|
|
223
|
+
saveWorkspaceReplacement(record);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
record.rootCid = await (effects.unregister ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { operationExpiresAt: record.operationExpiresAt, beforeSend: () => { record.dispatchAttempted = true; saveWorkspaceReplacement(record); } });
|
|
227
|
+
record.retired = true;
|
|
228
|
+
saveWorkspaceReplacement(record);
|
|
229
|
+
}
|
|
230
|
+
if (!record.cleaned) {
|
|
231
|
+
await (effects.cleanup ?? clearReplacedWorkspaceLocalState)();
|
|
232
|
+
record.cleaned = true;
|
|
233
|
+
saveWorkspaceReplacement(record);
|
|
234
|
+
}
|
|
235
|
+
if (record.payload.challenge.expiresAt <= Date.now())
|
|
236
|
+
throw Error('Old registration was retired, but the replacement setup window expired. Get a fresh App command and pass --replace-registration; the retirement will not be repeated');
|
|
237
|
+
return (effects.enroll ?? enrollWorkspace)(record.payload, record.configuration, { preserveProfile: true, replacingWorkspace: record.previous.workspaceId, attach: effects.attach });
|
|
238
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Sending files in an ACP chat
|
|
2
|
+
|
|
3
|
+
Every managed Agent can attach a file to its current ACP chat with the existing
|
|
4
|
+
Ours `send_file` tool. No Agent setting turns this on and no export directory is
|
|
5
|
+
configured or created:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{"path":"/home/me/project/build/report.pdf"}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Omitting `contact` sends to the current ACP chat. Supplying a non-empty contact
|
|
12
|
+
keeps ordinary Ours delivery:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{"contact":"Alice","path":"build/report.pdf"}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Only an absent contact chooses the chat. Null, empty or whitespace-only contacts,
|
|
19
|
+
unknown fields and unknown recipients fail without falling back to chat.
|
|
20
|
+
Current-chat sending accepts `path`, optional `filename` and `mime`; inline
|
|
21
|
+
base64 and Ours reply fields are unsupported.
|
|
22
|
+
|
|
23
|
+
Each call makes one attempt. Failures are returned to the agent. There is no
|
|
24
|
+
request ID, deduplication, automatic retry or recovery of failed sends. If a user
|
|
25
|
+
asks to resend, another tool call makes a new attempt and can produce another
|
|
26
|
+
attachment. UUIDs identify stored attachments; the stored SHA-256 checks integrity.
|
|
27
|
+
Neither is a retry key.
|
|
28
|
+
|
|
29
|
+
## Paths
|
|
30
|
+
|
|
31
|
+
The Agent passes the path of the file it wants to send. Relative paths resolve
|
|
32
|
+
from the Agent's working directory; `..`, absolute paths, hidden directories and
|
|
33
|
+
symlinked parent directories are all accepted. The file does not have to be
|
|
34
|
+
inside the working directory or any particular subtree. It is opened by the
|
|
35
|
+
Fleet bridge inside the Agent's own OS sandbox, so the Agent can send exactly
|
|
36
|
+
what it can already read.
|
|
37
|
+
|
|
38
|
+
The file itself must pass ordinary integrity checks, each with its own result:
|
|
39
|
+
|
|
40
|
+
| Result | Meaning |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `CHAT_FILE_NOT_FOUND` | nothing exists at the path |
|
|
43
|
+
| `CHAT_FILE_NOT_READABLE` | the Agent's OS user or sandbox cannot read it |
|
|
44
|
+
| `CHAT_FILE_IS_SYMLINK` | the named file is itself a symlink; pass its target |
|
|
45
|
+
| `CHAT_FILE_HARDLINKED` | the file has more than one hard link |
|
|
46
|
+
| `CHAT_FILE_NOT_REGULAR` | a directory, FIFO, socket or device |
|
|
47
|
+
| `FILE_EXCEEDS_20_MIB` | larger than the 20 MiB limit |
|
|
48
|
+
| `CHAT_FILE_CHANGED` / `SOURCE_SIZE_CHANGED` | modified while it was being read; finish writing first |
|
|
49
|
+
| `CHAT_FILE_REQUIRES_LINUX` | the Agent's bridge is not running on Linux |
|
|
50
|
+
| `PRIVATE_STATE_CANNOT_BE_SENT` | Fleet's own credentials, connector profile or stored attachments |
|
|
51
|
+
|
|
52
|
+
`PRIVATE_STATE_CANNOT_BE_SENT` covers only Fleet's private Ours runtime
|
|
53
|
+
directory, its attachment store and the connector profile and credential files,
|
|
54
|
+
checked on the provided path before approval and on the opened file's real path
|
|
55
|
+
afterwards. It is not configurable and is not a general path policy.
|
|
56
|
+
|
|
57
|
+
## Supported sessions
|
|
58
|
+
|
|
59
|
+
Current-chat delivery needs Linux, the managed Ours connector and a Codex or
|
|
60
|
+
Claude ACP session with a tracked active turn. Those Agents need no special
|
|
61
|
+
`harness_options`, MCP restriction or clean configuration home: inherited and
|
|
62
|
+
additional MCP connectors stay as they are, and Fleet's bound bridge is the
|
|
63
|
+
`ours` connector as it already is for every managed Agent.
|
|
64
|
+
|
|
65
|
+
Any other backend still sees the optional `contact`, and omitting it returns a
|
|
66
|
+
precise result rather than a missing tool:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
CURRENT_CHAT_DELIVERY_UNAVAILABLE: sending a file to this chat requires a Codex or Claude ACP session; this Agent runs <harness>/<session>. Pass contact to send through Ours instead
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Idle, scheduled, steered, cancelled and replaced turns return
|
|
73
|
+
`CURRENT_CHAT_DELIVERY_UNAVAILABLE`; a second send while one is in progress
|
|
74
|
+
returns `FILE_DELIVERY_BUSY`.
|
|
75
|
+
|
|
76
|
+
A file is published only for a call that arrives over the Agent's
|
|
77
|
+
capability-authenticated bridge and binds to the turn that is live in that
|
|
78
|
+
Agent's own session. Connector inventory is not consulted, so unrelated or
|
|
79
|
+
similarly named connectors neither enable nor block delivery.
|
|
80
|
+
|
|
81
|
+
## Permissions
|
|
82
|
+
|
|
83
|
+
Existing native tool approval and the neutral role permission policy both apply
|
|
84
|
+
before bytes are read. With `approval: allow`, a path inside the role's
|
|
85
|
+
`filesystem` boundary is allowed automatically; a path outside it is put to the
|
|
86
|
+
attached controller like any other out-of-boundary request, and is denied when
|
|
87
|
+
nobody is attached and `unattended: deny`. An `ask` decision needs an attached
|
|
88
|
+
controller; `deny` refuses with `CURRENT_CHAT_DELIVERY_UNAVAILABLE`, and a
|
|
89
|
+
declined request returns `FILE_SEND_DENIED`. A native policy that refuses MCP
|
|
90
|
+
execution (including Codex `never` without a native tool allowance) can still
|
|
91
|
+
refuse before Fleet receives the call.
|
|
92
|
+
|
|
93
|
+
## Retired configuration
|
|
94
|
+
|
|
95
|
+
`file_delivery` is retired. An existing block still loads so manifests need no
|
|
96
|
+
edit, and has no effect:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
file_delivery:
|
|
100
|
+
enabled: false # ignored: delivery stays available
|
|
101
|
+
directory: deliverables # ignored: not created, not enforced
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Remove it at your convenience. Unknown keys inside it are still rejected.
|
|
105
|
+
|
|
106
|
+
## Storage and download
|
|
107
|
+
|
|
108
|
+
Successful sends copy complete bytes into private immutable storage and append a
|
|
109
|
+
server-origin `file.attached` event containing the actual session generation,
|
|
110
|
+
ACP session and managed turn. One export can run at a time. Cancellation or
|
|
111
|
+
steering before publication revokes it; cancellation after publication does not
|
|
112
|
+
undo it. Unpublished stored bytes are never replayed or republished. Storage is
|
|
113
|
+
bounded to 100 files and 200 MiB per role incarnation, including incomplete and
|
|
114
|
+
orphaned copies. Exhausted storage fails; no automatic recovery is performed.
|
|
115
|
+
|
|
116
|
+
The App renders persisted file cards from these typed events. HTML and images
|
|
117
|
+
also download as files. Downloads require authentication, the matching role
|
|
118
|
+
incarnation, a published conversation event and intact bytes. Deleted, missing,
|
|
119
|
+
changed or orphaned artifacts return unavailable. Remote workspaces fetch bytes
|
|
120
|
+
using their existing device credential, limit the read to 20 MiB and prevent
|
|
121
|
+
saving after a workspace change.
|
|
122
|
+
|
|
123
|
+
## Release prerequisite and qualification
|
|
124
|
+
|
|
125
|
+
This source requires the companion MCP implementation's
|
|
126
|
+
`MANAGED_FILE_DELIVERY_VERSION = 1`. Fleet pins published
|
|
127
|
+
`@ours.network/mcp@1.2.0-nightly.14`, which includes it and already treats the
|
|
128
|
+
export-directory hint as optional, so default delivery needs no MCP change. An
|
|
129
|
+
installed MCP package without the capability fails Agent startup with
|
|
130
|
+
`INCOMPATIBLE_MANAGED_FILE_DELIVERY` instead of quietly requiring a contact.
|
|
131
|
+
Changes to the MCP pin require clean installed-package qualification without a
|
|
132
|
+
built MCP overlay.
|
|
133
|
+
|
|
134
|
+
The deterministic native fixture (`test/file-delivery-native.integration.mjs`)
|
|
135
|
+
uses real ACP adapters, the real managed connector and an isolated daemon. Its
|
|
136
|
+
Agent has no `file_delivery` block and no MCP restriction, and it sends a file
|
|
137
|
+
from a hidden directory outside the working directory. It then downloads the
|
|
138
|
+
generated attachment over the authenticated HTTP route and compares bytes and
|
|
139
|
+
SHA-256, after checking that an anonymous and a forged-cookie request are
|
|
140
|
+
refused. `FLEET_FILE_NATIVE_VARIANT=connectors` adds unrelated MCP connectors
|
|
141
|
+
and requires them to stay usable. A local scripted provider supplies only tool
|
|
142
|
+
intents; approval, read, copy, publication and bytes come from the
|
|
143
|
+
implementation. It does not qualify external-model inference, deployment or
|
|
144
|
+
arbitrary native MCP request-to-ACP tool-call ID mapping. Correlation is the
|
|
145
|
+
authenticated bridge and the actual active Fleet turn, which is revoked by
|
|
146
|
+
cancellation/steering.
|