@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.71
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 +279 -68
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -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 +19 -0
- package/dist/agent-ours/mcp-endpoint.js +98 -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 +24 -1
- package/dist/agent-ours/service.js +97 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +34 -0
- package/dist/application/conversation-history.js +280 -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 +40 -0
- package/dist/application/resource-observer.js +278 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +1 -1
- package/dist/application/role-repository.js +30 -7
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +61 -17
- package/dist/application/session-control.js +207 -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 +97 -31
- package/dist/build-info.json +9 -4
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +400 -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 +14 -9
- package/dist/daemon-recovery.js +124 -115
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +207 -19
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +24 -8
- 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 +22 -0
- package/dist/notifications/fleet-producer.js +237 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +11 -0
- package/dist/notifications/target-cleanup.js +63 -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 +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +43 -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 +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 +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 +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 +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-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +52 -8
- package/dist/rooms-tasks/task-state.js +251 -26
- 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 +33 -4
- package/dist/runner.js +410 -62
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +213 -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 +206 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +32 -0
- package/dist/startup-readiness.js +101 -0
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +77 -0
- package/dist/subscriptions/login.js +215 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +143 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +19 -33
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -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 +25 -0
- package/dist/web/conversation-stream.js +231 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- 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 +248 -0
- package/dist/web/resource-views.d.ts +124 -0
- package/dist/web/resource-views.js +424 -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 +802 -67
- 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 +580 -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/conversation-stream.md +196 -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-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 +24 -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 +4 -2
- 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 +12 -11
- package/presets/fleet/roles/Critic.yaml +13 -0
- package/presets/fleet/roles/Developer.yaml +13 -0
- package/presets/fleet/roles/Engineer.yaml +53 -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,220 @@
|
|
|
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 } from './atomic-file.js';
|
|
7
|
+
import { stateRoot } 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 = () => join(workspaceDir(), 'replacement.json');
|
|
14
|
+
const opaque = /^[\w-]{43}$/;
|
|
15
|
+
function binding(value) {
|
|
16
|
+
if (!value || !opaque.test(value.workspaceId) || !opaque.test(value.hostWorkspaceId) || (value.serverCid !== undefined && !/^[a-f0-9]{64}$/i.test(value.serverCid))
|
|
17
|
+
|| (value.proofRootCid !== undefined && !/^[a-f0-9]{64}$/i.test(value.proofRootCid)))
|
|
18
|
+
throw Error('Existing workspace binding is invalid; restore it before tunnel setup');
|
|
19
|
+
return { ...value, appOrigin: validateAccountOrigin(value.appOrigin ?? 'https://app.ours.network') };
|
|
20
|
+
}
|
|
21
|
+
export function readWorkspaceBinding() {
|
|
22
|
+
const file = join(workspaceDir(), 'binding.json');
|
|
23
|
+
if (!existsSync(file))
|
|
24
|
+
return;
|
|
25
|
+
let value;
|
|
26
|
+
try {
|
|
27
|
+
value = JSON.parse(readFileSync(file, 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw Error('Existing workspace binding is invalid');
|
|
31
|
+
}
|
|
32
|
+
return binding(value);
|
|
33
|
+
}
|
|
34
|
+
export function sameWorkspaceBinding(previous, payload) {
|
|
35
|
+
return previous.workspaceId === payload.challenge.workspaceId && previous.appOrigin === payload.appOrigin && (!previous.serverCid || previous.serverCid.toUpperCase() === payload.serverCid.toUpperCase());
|
|
36
|
+
}
|
|
37
|
+
/** A workspace ID is never retired to rerun or migrate that same workspace. */
|
|
38
|
+
export function requiresWorkspaceReplacement(previous, target, migrateAppOrigin = false) {
|
|
39
|
+
if (previous.workspaceId !== target.workspaceId) {
|
|
40
|
+
if (!previous.serverCid)
|
|
41
|
+
throw Error('Legacy registration has no recorded server identity. Rerun its own workspace setup before replacing it');
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (previous.serverCid && previous.serverCid.toUpperCase() !== target.serverCid.toUpperCase())
|
|
45
|
+
throw Error('Same-workspace setup must retain the enrollment server identity; existing setup preserved');
|
|
46
|
+
if (previous.appOrigin !== target.appOrigin && !migrateAppOrigin)
|
|
47
|
+
throw Error('Account origin change requires explicit --migrate-app-origin; existing setup preserved');
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/** Ask before redeeming the one-use grant, whose destination is deliberately opaque. */
|
|
51
|
+
export async function confirmWorkspaceReplacement(previous, explicit = false, effects = {}) {
|
|
52
|
+
const write = effects.write ?? (value => { process.stderr.write(value); });
|
|
53
|
+
let hostname = '';
|
|
54
|
+
try {
|
|
55
|
+
const tunnel = JSON.parse(readFileSync(join(workspaceDir(), 'tunnel.json'), 'utf8'));
|
|
56
|
+
if (typeof tunnel.origin === 'string' && /^https:\/\/[a-z0-9][a-z0-9-]{2,60}\.ours-tunnel\.com$/.test(tunnel.origin))
|
|
57
|
+
hostname = ` (${new URL(tunnel.origin).hostname})`;
|
|
58
|
+
}
|
|
59
|
+
catch { }
|
|
60
|
+
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`);
|
|
61
|
+
if (explicit)
|
|
62
|
+
return true;
|
|
63
|
+
if (!(effects.interactive ?? process.stdin.isTTY)) {
|
|
64
|
+
write('Existing setup preserved. Run in a terminal to confirm, or pass --replace-registration to confirm explicitly.\n');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const ask = effects.ask ?? (async () => {
|
|
68
|
+
const terminal = createInterface({ input: process.stdin, output: process.stderr });
|
|
69
|
+
try {
|
|
70
|
+
return await terminal.question('Replace the existing registration if this command names another workspace? [y/N] ');
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
terminal.close();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
let answer = '';
|
|
77
|
+
try {
|
|
78
|
+
answer = await ask();
|
|
79
|
+
}
|
|
80
|
+
catch { /* EOF, cancellation and input failure mean no consent. */ }
|
|
81
|
+
if (/^(y|yes)$/i.test(answer.trim()))
|
|
82
|
+
return true;
|
|
83
|
+
write('Existing setup preserved.\n');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
export function saveWorkspaceReplacement(record) {
|
|
87
|
+
replaceFileAtomically(replacementPath(), JSON.stringify(record) + '\n', 0o600);
|
|
88
|
+
}
|
|
89
|
+
export function readWorkspaceReplacement() {
|
|
90
|
+
const file = replacementPath();
|
|
91
|
+
if (!existsSync(file))
|
|
92
|
+
return;
|
|
93
|
+
const stat = lstatSync(file);
|
|
94
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0 || stat.size > 65536)
|
|
95
|
+
throw Error('Unfinished replacement must be an owned private regular file');
|
|
96
|
+
let record;
|
|
97
|
+
try {
|
|
98
|
+
record = JSON.parse(readFileSync(file, 'utf8'));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw Error('Unfinished replacement record is invalid');
|
|
102
|
+
}
|
|
103
|
+
if (![1, 2].includes(record.version) || !opaque.test(record.operationNonce) || typeof record.configuration !== 'string' || resolve(record.configuration) !== record.configuration
|
|
104
|
+
|| (record.rootCid !== undefined && !/^[a-f0-9]{64}$/i.test(record.rootCid)) || (record.retired !== undefined && typeof record.retired !== 'boolean')
|
|
105
|
+
|| (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))
|
|
106
|
+
throw Error('Unfinished replacement record is invalid');
|
|
107
|
+
record.previous = binding(record.previous);
|
|
108
|
+
if (!record.previous.serverCid)
|
|
109
|
+
throw Error('Unfinished replacement has no pinned server identity');
|
|
110
|
+
record.payload = decodeWorkspacePayload(Buffer.from(JSON.stringify(record.payload)).toString('base64url'), { allowExpired: true });
|
|
111
|
+
return record;
|
|
112
|
+
}
|
|
113
|
+
export function clearWorkspaceReplacement() { rmSync(replacementPath(), { force: true }); }
|
|
114
|
+
/** Explicit recovery after the user removes an unsuccessful successor in the App. */
|
|
115
|
+
export function discardRetiredWorkspaceReplacement() {
|
|
116
|
+
const record = readWorkspaceReplacement(), current = readWorkspaceBinding();
|
|
117
|
+
if (!record?.retired || !record.cleaned)
|
|
118
|
+
throw Error('Only a fully retired and locally cleaned replacement can be discarded; resume or safely abandon this operation');
|
|
119
|
+
if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
|
|
120
|
+
throw Error('The successor binding was committed or the binding changed; resume its setup instead of discarding recovery');
|
|
121
|
+
clearWorkspaceReplacement();
|
|
122
|
+
}
|
|
123
|
+
function checkReplacementService(configuration) {
|
|
124
|
+
const manager = new WebServiceManager(), service = manager.readMetadata();
|
|
125
|
+
if (service?.configuration && resolve(service.configuration) !== resolve(configuration))
|
|
126
|
+
throw Error('Installed web service uses another configuration; existing setup preserved');
|
|
127
|
+
if (!service && existsSync(webControlPath()))
|
|
128
|
+
throw Error('Stop the foreground Fleet web service before replacing its registration; existing setup preserved');
|
|
129
|
+
}
|
|
130
|
+
export async function beginWorkspaceReplacement(previous, payload, configuration, effects = {}) {
|
|
131
|
+
checkWorkspaceConnector(payload);
|
|
132
|
+
if (!requiresWorkspaceReplacement(previous, { workspaceId: payload.challenge.workspaceId, appOrigin: payload.appOrigin, serverCid: payload.serverCid }))
|
|
133
|
+
throw Error('The same workspace must never be retired');
|
|
134
|
+
checkReplacementService(configuration);
|
|
135
|
+
const record = { version: 2, previous: previous, payload, configuration: resolve(configuration), operationNonce: randomBytes(32).toString('base64url'), dispatchAttempted: false, operationExpiresAt: Date.now() + 86400000 - 5 * 60000 };
|
|
136
|
+
record.rootCid = await (effects.preflight ?? unregisterWorkspace)(record.previous, payload, record.operationNonce, effects.attach, { checkOnly: true, operationExpiresAt: record.operationExpiresAt });
|
|
137
|
+
saveWorkspaceReplacement(record);
|
|
138
|
+
return record;
|
|
139
|
+
}
|
|
140
|
+
/** Abandon only after a private receipt confirms no deletion and dispatch was unsent or terminally rejected. */
|
|
141
|
+
export async function abandonWorkspaceReplacement(effects = {}) {
|
|
142
|
+
const record = readWorkspaceReplacement();
|
|
143
|
+
if (!record)
|
|
144
|
+
throw Error('There is no unfinished replacement');
|
|
145
|
+
if (record.retired || record.cleaned || record.version !== 2)
|
|
146
|
+
throw Error('Retirement may already have completed; resume this replacement instead of abandoning it');
|
|
147
|
+
const current = readWorkspaceBinding();
|
|
148
|
+
if (!current || current.workspaceId !== record.previous.workspaceId || current.hostWorkspaceId !== record.previous.hostWorkspaceId || current.appOrigin !== record.previous.appOrigin || current.serverCid?.toUpperCase() !== record.previous.serverCid.toUpperCase())
|
|
149
|
+
throw Error('Workspace binding changed; replacement was not abandoned');
|
|
150
|
+
let deleted, rejected = false, expired = false, deadlineEnforced = false, oldBindingPresent = false;
|
|
151
|
+
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; } });
|
|
152
|
+
if (root.toUpperCase() !== record.rootCid?.toUpperCase() || deleted !== false || (record.dispatchAttempted && !(rejected && !expired && Date.now() <= record.operationExpiresAt || oldBindingPresent && deadlineEnforced && Date.now() > record.operationExpiresAt + 5 * 60000)))
|
|
153
|
+
throw Error('Retirement may already have been sent; run --resume or abandon only after the account service confirms terminal rejection');
|
|
154
|
+
clearWorkspaceReplacement();
|
|
155
|
+
}
|
|
156
|
+
/** A failed/expired one-time command can be renewed only for its pinned successor. */
|
|
157
|
+
export function refreshWorkspaceReplacement(record, payload, configuration) {
|
|
158
|
+
if (resolve(configuration) !== record.configuration)
|
|
159
|
+
throw Error('Replacement requires its original configuration');
|
|
160
|
+
if (payload.challenge.workspaceId !== record.payload.challenge.workspaceId || payload.challenge.accountId !== record.payload.challenge.accountId
|
|
161
|
+
|| payload.appOrigin !== record.payload.appOrigin || payload.serverCid.toUpperCase() !== record.payload.serverCid.toUpperCase()
|
|
162
|
+
|| payload.hostname !== record.payload.hostname)
|
|
163
|
+
throw Error('Unfinished replacement requires a fresh command for the same target workspace');
|
|
164
|
+
record.payload = payload.connectorToken === undefined && record.payload.connectorToken !== undefined ? { ...payload, connectorToken: record.payload.connectorToken } : payload;
|
|
165
|
+
saveWorkspaceReplacement(record);
|
|
166
|
+
}
|
|
167
|
+
/** Only tunnel files and account-issued device capabilities belong to this registration. */
|
|
168
|
+
export async function clearReplacedWorkspaceLocalState() {
|
|
169
|
+
const manager = new WebServiceManager();
|
|
170
|
+
if (manager.readMetadata())
|
|
171
|
+
await manager.stop();
|
|
172
|
+
const devices = new WorkspaceDeviceStore();
|
|
173
|
+
try {
|
|
174
|
+
devices.revokeAll();
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
devices.close();
|
|
178
|
+
}
|
|
179
|
+
for (const file of ['tunnel.json', 'connector', 'pending-setup.json'])
|
|
180
|
+
rmSync(join(workspaceDir(), file), { force: true });
|
|
181
|
+
}
|
|
182
|
+
/** Persist every irreversible boundary; the original binding stays until enrollment writes its successor. */
|
|
183
|
+
export async function finishWorkspaceReplacement(record, effects = {}) {
|
|
184
|
+
const current = readWorkspaceBinding();
|
|
185
|
+
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)))
|
|
186
|
+
throw Error('Workspace binding changed during replacement; review it before continuing');
|
|
187
|
+
// A saved successor binding may predate a refreshed challenge. Enrollment
|
|
188
|
+
// checks the exact proof receipt: it reuses confirmed proof, or signs the new
|
|
189
|
+
// challenge, rather than assuming every challenge for this binding is done.
|
|
190
|
+
if (!record.retired) {
|
|
191
|
+
checkReplacementService(record.configuration);
|
|
192
|
+
if (record.version === 2 && Date.now() > record.operationExpiresAt + 5 * 60000) {
|
|
193
|
+
if (record.payload.challenge.expiresAt <= Date.now())
|
|
194
|
+
throw Error('Retirement deadline passed. Obtain a fresh App command for the same target and pass --replace-registration to continue');
|
|
195
|
+
let deleted, enforced = false;
|
|
196
|
+
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; } });
|
|
197
|
+
if (root.toUpperCase() !== record.rootCid?.toUpperCase())
|
|
198
|
+
throw Error('Original proof root changed; existing recovery preserved');
|
|
199
|
+
if (deleted === false && enforced) {
|
|
200
|
+
// The expired command cannot act. Existing consent covers this exact
|
|
201
|
+
// previous/successor pair, including signed absence after a lost receipt.
|
|
202
|
+
record.operationNonce = randomBytes(32).toString('base64url');
|
|
203
|
+
record.operationExpiresAt = Date.now() + 86400000 - 5 * 60000;
|
|
204
|
+
record.dispatchAttempted = false;
|
|
205
|
+
saveWorkspaceReplacement(record);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
record.rootCid = await (effects.unregister ?? unregisterWorkspace)(record.previous, record.payload, record.operationNonce, effects.attach, { operationExpiresAt: record.operationExpiresAt, beforeSend: () => { record.dispatchAttempted = true; saveWorkspaceReplacement(record); } });
|
|
209
|
+
record.retired = true;
|
|
210
|
+
saveWorkspaceReplacement(record);
|
|
211
|
+
}
|
|
212
|
+
if (!record.cleaned) {
|
|
213
|
+
await (effects.cleanup ?? clearReplacedWorkspaceLocalState)();
|
|
214
|
+
record.cleaned = true;
|
|
215
|
+
saveWorkspaceReplacement(record);
|
|
216
|
+
}
|
|
217
|
+
if (record.payload.challenge.expiresAt <= Date.now())
|
|
218
|
+
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');
|
|
219
|
+
return (effects.enroll ?? enrollWorkspace)(record.payload, record.configuration, { preserveProfile: true, replacingWorkspace: record.previous.workspaceId, attach: effects.attach });
|
|
220
|
+
}
|
|
@@ -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.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Conversation event stream (wire version 1)
|
|
2
|
+
|
|
3
|
+
`GET /api/v1/roles/:id/conversation/events?expectedGeneration=GENERATION&after=CURSOR`
|
|
4
|
+
uses the existing Fleet route boundary, including `/fleet/api/v1/...` through the
|
|
5
|
+
prefix gateway. Use authenticated `fetch`, then read the response body. Cookies
|
|
6
|
+
work for standalone and same-origin frame access; the hosted App sends its current
|
|
7
|
+
workspace access bearer in `Authorization`. Query parameters are data, never
|
|
8
|
+
credentials. `after` is a nonnegative safe integer; generation is required.
|
|
9
|
+
No new WebSocket path, controller lease, notification acknowledgement, work
|
|
10
|
+
submission, or read receipt is introduced.
|
|
11
|
+
|
|
12
|
+
## Framing
|
|
13
|
+
|
|
14
|
+
Content type is `text/event-stream; charset=utf-8`, with `Cache-Control: no-store`
|
|
15
|
+
and `X-Accel-Buffering: no`. Every frame is exactly:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
event: conversation
|
|
19
|
+
data: {"version":1,"type":"TYPE","payload":...}
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The six logical types are:
|
|
24
|
+
|
|
25
|
+
- `role`: complete public role replacement (`roleId`, `roleRevision`,
|
|
26
|
+
`roleLifetime`, `availability`, `health`, optional backend/fallback/failure).
|
|
27
|
+
Local state paths are omitted. This ordering is independent of session state.
|
|
28
|
+
- `ledger`: either a single durable conversation event, or a coverage marker
|
|
29
|
+
`{coveredThrough:number}`. All events covered by a marker precede it. Replay
|
|
30
|
+
can legitimately advance coverage without visible events. Never advance a
|
|
31
|
+
recovery cursor from maximum rendered event sequence alone.
|
|
32
|
+
- `ready`: `{sessionGeneration,stateRevision,ledgerBarrier,coveredThrough,
|
|
33
|
+
capability:"complete",authorizedUntil}`. The coherent initial snapshot arrives
|
|
34
|
+
as a preceding `state` record, keeping its full 16 MiB payload budget. Hold
|
|
35
|
+
initial state/replay as provisional until matching ready; validate generation,
|
|
36
|
+
stateRevision and ledgerBarrier against the staged state and coverage against
|
|
37
|
+
delivered replay. Only successful private replay certifies `coveredThrough`;
|
|
38
|
+
the snapshot barrier alone does not. Disconnect between state and ready must
|
|
39
|
+
not publish live initial state or advance the certified cursor. Reconnect with
|
|
40
|
+
same-generation certified coverage. State revision and event sequence differ.
|
|
41
|
+
- `state`: complete conversation observation replacement, including
|
|
42
|
+
`sessionGeneration`, `stateRevision` and `ledgerBarrier`. Do not merge it as a
|
|
43
|
+
patch or use its barrier as coverage.
|
|
44
|
+
- `resync`: `{reason,retry,capability}`. Clear partial assembly and resynchronize
|
|
45
|
+
explicitly or enter supported fallback. Temporary role read/watch failure is
|
|
46
|
+
retryable; a fresh observer can recover after repair. `retry:false` alone does
|
|
47
|
+
not establish permanent incapability on older servers. Explicit unsupported
|
|
48
|
+
capability or persistent oversize remains supported fallback without tight reconnects. `capability` is
|
|
49
|
+
`degraded`, except `auth-expired` below.
|
|
50
|
+
- `keepalive`: `{at:number}`. Transport liveness only; it cannot certify role,
|
|
51
|
+
session, replay, or private-control health.
|
|
52
|
+
|
|
53
|
+
A large logical payload uses `start`, ordered `chunk`, then `commit` frames,
|
|
54
|
+
without the `payload` field:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{"version":1,"type":"start","id":"3","recordType":"state","totalBytes":1048576,"generation":"g","revision":7}
|
|
58
|
+
{"version":1,"type":"chunk","id":"3","index":0,"data":"a slice of the serialized payload JSON"}
|
|
59
|
+
{"version":1,"type":"commit","id":"3","totalBytes":1048576,"generation":"g","revision":7}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Chunks contain JSON text, **not base64**. Concatenate `data`, UTF-8 count it,
|
|
63
|
+
then JSON-parse only after commit. The optional generation/revision are derived
|
|
64
|
+
from payload sessionGeneration/stateRevision or roleRevision;
|
|
65
|
+
commit and reconstructed payload must match start. Reject duplicate/out-of-order
|
|
66
|
+
chunks, empty chunks, mismatched identities/revisions/lengths, overlapping assemblies, invalid
|
|
67
|
+
UTF-8, over-budget frames, and an incomplete end. Never publish partial snapshots.
|
|
68
|
+
`ConversationStreamDecoder` implements these framing checks without Node APIs.
|
|
69
|
+
It is not a domain reducer: consumers must additionally enforce current workspace,
|
|
70
|
+
generation and monotonic state/role revisions before applying records.
|
|
71
|
+
|
|
72
|
+
## Limits and ownership
|
|
73
|
+
|
|
74
|
+
Encoded frames, including SSE and JSON overhead, are at most 512 KiB. Payload JSON
|
|
75
|
+
is at most 16 MiB; one assembly is allowed at a time. The encoder splits at safe
|
|
76
|
+
UTF-16 boundaries into at most 48 Ki code units, so JSON escaping still fits the
|
|
77
|
+
encoded-frame bound. The active logical record is streamed progressively while
|
|
78
|
+
waiting encoded records are capped at 8 MiB per web connection. A valid 16 MiB
|
|
79
|
+
ledger event is sent independently of its coverage marker so the page envelope
|
|
80
|
+
does not consume its logical-record budget. Oversize records explicitly degrade.
|
|
81
|
+
The writer waits for socket drain before writing another frame; overflow sends a
|
|
82
|
+
resync when writable, otherwise closes. Ledger delivery has no batching delay
|
|
83
|
+
(therefore no intentional wait beyond the required 50 ms); network backpressure
|
|
84
|
+
can delay actual receipt.
|
|
85
|
+
|
|
86
|
+
The private supervisor separately owns its 8 MiB live replay backlog and writes
|
|
87
|
+
64 KiB slices under socket backpressure. The adapter bounds each raw NDJSON line
|
|
88
|
+
at 16 MiB plus the maximum computed serialized replay/ready/state/event envelope
|
|
89
|
+
(replay has an empty events array; ready omits its null placeholder; coverage uses
|
|
90
|
+
`Number.MAX_SAFE_INTEGER`). This admits one exact 16 MiB event;
|
|
91
|
+
malformed or oversized passive input closes the stream. The receiving browser
|
|
92
|
+
owns its 16 MiB assembly limit. A parsed record, serialized UTF-16 string, encoded
|
|
93
|
+
frame, and socket buffers can coexist: these are logical budgets, not an RSS cap.
|
|
94
|
+
Aggregate memory scales with connections, as do role watchers and private sockets.
|
|
95
|
+
There is no global subscriber cap. HTTP/1 browsers commonly share about six
|
|
96
|
+
connections per origin; multiple tabs, pane consumers and voice can exhaust that
|
|
97
|
+
pool. Dispose surplus consumers; HTTP/2 deployment and browser pooling vary.
|
|
98
|
+
|
|
99
|
+
Each web connection owns one role observer, including while the role is missing.
|
|
100
|
+
Session describe/follow borrow it. Missing/replaced roles stop the old follow;
|
|
101
|
+
reappearance can open another follow, still bound to the requested generation.
|
|
102
|
+
Abort, revocation, server shutdown and late connector completion release owners
|
|
103
|
+
idempotently. Pane observation does not acquire or release voice's independent
|
|
104
|
+
controller. Private heartbeats remain serialized every two seconds; six seconds
|
|
105
|
+
without one ends private health even if SSE keepalives continue.
|
|
106
|
+
|
|
107
|
+
Fleet emits browser keepalives every second. Each App pane/voice attempt independently
|
|
108
|
+
bounds opening through complete ready to ten seconds, regardless of incoming activity.
|
|
109
|
+
After ready, nonempty byte chunks (including keepalives/comments and partial records)
|
|
110
|
+
reset a ten-second silence deadline. Elapsed checks before activity and on browser
|
|
111
|
+
visibility/pageshow resume give no hidden grace; suspended code expires at its first
|
|
112
|
+
subsequent check. Expiry retires health and aborts the old attempt before bounded
|
|
113
|
+
recovery/fallback. These browser clocks do not renew or replace credential deadlines.
|
|
114
|
+
Comment-only/blank SSE frames are transport activity and publish no domain record.
|
|
115
|
+
|
|
116
|
+
## Authentication, fallback and filesystem assumptions
|
|
117
|
+
|
|
118
|
+
The stream is bound to its **opening** credential deadline. Rotating access lasts
|
|
119
|
+
at most five minutes; it is not renewable in place. On `auth-expired`, obtain the
|
|
120
|
+
renewed access credential and reconnect using only same-generation certified
|
|
121
|
+
coverage. Count this normal auth reconnect separately from degraded operation.
|
|
122
|
+
Fixed credentials use their device/sunset deadline. Cookie idle expiry is checked
|
|
123
|
+
without updating lastSeenAt, including the exact 30-minute boundary; ordinary
|
|
124
|
+
requests still update activity. Absolute expiry and revocation also end streams.
|
|
125
|
+
The terminal resync uses `capability:"auth-expired"`, with retry `"authenticate"`
|
|
126
|
+
for rotating credentials or `"reauthenticate"` for cookies/fixed credentials.
|
|
127
|
+
Actions after expiry must reauthenticate; heartbeats cannot refresh session policy.
|
|
128
|
+
|
|
129
|
+
`conversation_live_complete` describes current intersection of healthy role
|
|
130
|
+
observation and private replay/heartbeat capability. It never certifies requested
|
|
131
|
+
history. REST remains for initial latest history, user-requested older pages,
|
|
132
|
+
explicit recovery, and older/unsupported/degraded server fallback. Healthy active
|
|
133
|
+
consumers must not periodically fetch role details or history.
|
|
134
|
+
|
|
135
|
+
Active pane and independent voice consumers recover transient transport, temporary observer or
|
|
136
|
+
bootstrap failures after their bounded fast attempts: one delayed attempt after 10 seconds,
|
|
137
|
+
then 20 seconds, then capped 30 seconds between failed settled attempts. Fallback refresh ticks
|
|
138
|
+
do not start probes. Retire/abort the fallback request and timer before recovery takes cursor
|
|
139
|
+
ownership; fence late responses without waiting indefinitely for cancellation. Resume only
|
|
140
|
+
same-generation certified coverage; otherwise establish a canonical latest baseline. Ordinary
|
|
141
|
+
REST progress and identical unsuccessful resync never reset the fast restart budget. Complete
|
|
142
|
+
ready ends fallback polling and resets delayed backoff. Pane generation replacement may load a
|
|
143
|
+
new baseline; voice generation replacement stops that session until explicit Start, without
|
|
144
|
+
transferring controller/provider leases. Auth-required/expired ends automatic reads/probes.
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
Complete role delivery relies on native directory notifications for fleet config,
|
|
148
|
+
role manifests and session files. Qualified native host filesystems are the
|
|
149
|
+
intended deployment (macOS local APFS or Linux local filesystems with functional
|
|
150
|
+
inotify). Watch errors, unexpected closure, unsupported watching, or invalid
|
|
151
|
+
current role reads withdraw completeness. Silent event loss is not detectable
|
|
152
|
+
without polling and is not promised away. Docker must expose the same state files
|
|
153
|
+
and private socket to the Fleet process, with working directory-change delivery
|
|
154
|
+
across the selected bind mount/volume. Docker Desktop host-to-VM mounts, network
|
|
155
|
+
filesystems and remote/FUSE mounts require deployment-specific notification tests;
|
|
156
|
+
use degraded REST fallback when those assumptions cannot be established. No ACP
|
|
157
|
+
library changes are required.
|
|
158
|
+
|
|
159
|
+
## Admission receipt provenance
|
|
160
|
+
|
|
161
|
+
Prompt admission responses additionally expose optional `sessionGeneration`: the
|
|
162
|
+
original durable admission generation. Both backend session generations are immutable
|
|
163
|
+
for the admitting lifetime. Cached receipts retain that generation, and reopened command
|
|
164
|
+
indexes derive it from the original `prompt.admitted` event, never a replacement session.
|
|
165
|
+
Historical receipts without provable generation leave the field absent.
|
|
166
|
+
|
|
167
|
+
This permits an explicitly retried pre-change `{text, commandId}` browser draft to settle
|
|
168
|
+
only when matching receipt identity and original generation validate against its captured
|
|
169
|
+
current owner. Missing/old provenance keeps a visible unresolved draft and its idempotency
|
|
170
|
+
ID. Existing generation-bound drafts retain their original fences. `eventCursor` and causal
|
|
171
|
+
coverage/inventory settlement remain unchanged; provenance is not a new settlement watermark.
|
|
172
|
+
|
|
173
|
+
## Cross-repository qualification
|
|
174
|
+
|
|
175
|
+
`test/web/fixtures/conversation-stack.ts` shares the real HTTP and private-control
|
|
176
|
+
stack with the legacy conversation tests and the matching App checkout's optional
|
|
177
|
+
`tests/fleet-live-data.integration.mjs`. ACP uses the existing child-process protocol
|
|
178
|
+
fixture; native Codex uses the existing app-server protocol driver. Both exercise
|
|
179
|
+
actual session, ledger, control adapter, HTTP framing and App decoder/reconciler/UI.
|
|
180
|
+
Role availability changes are controlled fixture inputs; this does not qualify native
|
|
181
|
+
filesystem event delivery on an arbitrary mount.
|
|
182
|
+
|
|
183
|
+
Run Fleet's full tests and typecheck after integrated changes. Retain the SSE,
|
|
184
|
+
workspace-auth-provenance and workspace-tunnel suites for frame/hosted authentication,
|
|
185
|
+
expiry/revocation, delayed-open disposal and bounded slow consumers. App reports
|
|
186
|
+
healthy content requests separately from bootstrap, paging, credential reconnect,
|
|
187
|
+
explicit recovery and unsupported-host fallback. DOM mutation and fixture latency
|
|
188
|
+
measurements have no claim about real-provider inference or physical-device audio.
|
|
189
|
+
The App and Fleet changes require coordinated upstream delivery; source qualification
|
|
190
|
+
does not establish that installed or serving packages contain them.
|
|
191
|
+
|
|
192
|
+
## Full resource delivery qualification
|
|
193
|
+
|
|
194
|
+
Directory, configuration/definitions, previews, provider/status, layout and admitted-operation views share the authenticated ephemeral resource connection with per-view epochs. Complete ready requires the canonical subscribed payload; a heartbeat does not certify a failed upstream watcher. User commands remain HTTP. Controller, voice and selected-daemon health are independent owners. The selected-daemon health deadline passes its cancellation signal through the public SDK selection/request API, cancelling underlying HTTP I/O as well as retiring the result.
|
|
195
|
+
|
|
196
|
+
The matching App source qualification composes the existing ACP/Codex browser fixture and records sixty-second healthy windows separately from initial/history/recovery/command/health/lease/long-poll traffic. Unit and HTTP producer checks are not proof of every rendered resource combination or installed deployment. Preserve existing room-layout suite sources and qualify released service/dependency versions separately.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Upgrade to one Fleet service
|
|
2
|
+
|
|
3
|
+
Permanent and task agents share one systemd user service on Linux or one
|
|
4
|
+
LaunchAgent on macOS. Install the package and explicitly run `ours-fleet init`;
|
|
5
|
+
package installation alone does not migrate services. New task/temporary launches
|
|
6
|
+
refuse `FLEET_SERVICE_NOT_INSTALLED` until init succeeds. Native supervision uses
|
|
7
|
+
the OS user's home; a custom `OURS_FLEET_HOME` is refused. Manual/isolated fleets
|
|
8
|
+
can use `OURS_FLEET_SUPERVISOR=none` without boot recovery.
|
|
9
|
+
|
|
10
|
+
Before init, back up Fleet configuration, private member/runtime/session state,
|
|
11
|
+
task/room records and native units/plists. Preserve operator readiness commands,
|
|
12
|
+
timeout/restart settings and environment in the common parent's explicit
|
|
13
|
+
systemd drop-ins (`~/.config/systemd/user/ours-fleet.service.d/`). Conflicting
|
|
14
|
+
per-agent settings need operator resolution. Migration does not copy scripts or
|
|
15
|
+
silently discard hooks. Resolve `OPERATOR_DROPIN_REQUIRED` or `UNSUPPORTED_DROPIN`,
|
|
16
|
+
reload the user manager, verify its effective settings, then retry.
|
|
17
|
+
|
|
18
|
+
Init preflights configured permanent agents, retires only their proven old native
|
|
19
|
+
services and registers them after exact old-process absence. `up NAME` migrates
|
|
20
|
+
only NAME. Existing identity and conversation files remain in place. On Linux,
|
|
21
|
+
enabled legacy roles transfer running boot intent, including enabled inactive
|
|
22
|
+
roles; disabled inactive roles remain untouched. macOS stopped roles stay stopped.
|
|
23
|
+
Interrupted transfers retain receipts; retry with the original state instead of
|
|
24
|
+
deleting proof or starting old and new supervisors together.
|
|
25
|
+
|
|
26
|
+
The parent starts existing agent runners. Adding a member does not restart it.
|
|
27
|
+
Restarting the parent affects every managed agent. Permanent agents resume their
|
|
28
|
+
conversation with their existing readiness/continuity prompts and may receive a
|
|
29
|
+
new private runtime instance after orderly release. Task agents retain their
|
|
30
|
+
original identity, runtime owner and conversation. Managed stop persists across
|
|
31
|
+
parent restart/reboot. Non-resumable temporary agents retire on parent shutdown.
|
|
32
|
+
|
|
33
|
+
## Existing tasks
|
|
34
|
+
|
|
35
|
+
Running pre-upgrade transient task members remain untouched and report
|
|
36
|
+
`migration_pending`; init does not make them durable. Clean legacy shutdown or
|
|
37
|
+
explicit temporary stop releases/archives them. After abrupt process loss,
|
|
38
|
+
`ours-fleet task recover-members TASK_ID` can adopt a stopped single-room member
|
|
39
|
+
only with its original owner, identity, launch, workspace, conversation and room
|
|
40
|
+
admission intact and exact process absence proven. Missing/mismatched evidence
|
|
41
|
+
and unmarked legacy layouts require reconciliation. There is no automatic live
|
|
42
|
+
handover: let old work retire normally, or preserve and inspect its original
|
|
43
|
+
state before a controlled transition. Do not create replacement identities,
|
|
44
|
+
redeem invitations again or fabricate ownership records to force recovery.
|
|
45
|
+
|
|
46
|
+
## Rollback
|
|
47
|
+
|
|
48
|
+
Stop the common parent and prove all managed process groups absent. Preserve an
|
|
49
|
+
offline backup of catalog, receipts, private state and operator drop-ins. Restore
|
|
50
|
+
the prior Fleet release and reviewed legacy units/plists, enabling only intended
|
|
51
|
+
running agents. Keep the common parent disabled while legacy supervision runs.
|
|
52
|
+
Re-entering central mode requires retained-proof reconciliation and old-process
|
|
53
|
+
absence. Physical reboot/boot ordering, real macOS launchd and live host migration
|
|
54
|
+
have not been qualified by the isolated process and injected native tests.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Live voice managed-session boundary
|
|
2
|
+
|
|
3
|
+
App Live voice sends transcripts through existing ACP managed sessions and reads the actual agent conversation. Updated ACP and native Codex supervisors advertise `generation_bound_prompts`, `idle_bound_prompts` and `targeted_voice_interrupt`; `/api/v1/roles/:id/voice-capabilities` authenticates the browser and reports whether all are present.
|
|
4
|
+
|
|
5
|
+
`submit_voice_prompt` accepts `requireIdle: true`. Receipt replay precedes the idle check so retrying a lost acknowledgment does not create a second prompt or fail just because the first is running. New admission checks generation and idle/queue/tool/permission state inside the existing turn arbiter before queuing. Unrelated work is never interrupted to make room.
|
|
6
|
+
|
|
7
|
+
`interrupt_voice_v2` requires a command ID, expected session generation and exact prompt ID. A matching active prompt uses normal managed-session cancellation; a settled or different prompt is a harmless no-op. A stale generation is rejected. Receipts are idempotent and conflicting reuse of an interrupt command is rejected. The browser's `/interrupt` route selects this path only when the complete target is present, with no legacy unbound fallback.
|
|
8
|
+
|
|
9
|
+
Authenticated CSRF-protected `POST /api/v1/roles/:id/live-presence` takes `{ expectedSessionGeneration }`, validates the current generation before attaching `followConversation`, and streams NDJSON readiness/heartbeats. The HTTP connection owns one attended controller until disconnect. Close, generation change, auth expiry, slow consumer or attach failure detach it. The browser's ordinary workspace fetch supports this stream without provider credentials in a URL. Existing ACP permission expiry and controller detach grace remain in force.
|
|
10
|
+
|
|
11
|
+
The daemon/App changes and configuration instructions live in the corresponding `ours-app/docs/live-voice.md` and `ours-sdk` Live voice documentation. The daemon owns the OpenAI key and 60-second Realtime credential minting; Fleet never receives the provider key.
|
|
12
|
+
|
|
13
|
+
Coverage includes actual ACP/control-socket busy/retry/targeted-cancel tests, native Codex transport tests, authenticated presence-boundary tests and the App native browser/audio integration with separate Codex/Claude ACP protocol fixtures. Provider inference and physical-device acceptance are separate operator checks.
|