@ours.network/fleet 1.2.0-nightly.6 → 1.2.0-nightly.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +287 -65
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +35 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +4 -0
- package/dist/agent-ours/mcp-endpoint.js +5 -0
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +21 -1
- package/dist/agent-ours/service.js +94 -14
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +27 -0
- package/dist/application/conversation-history.js +212 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +9 -1
- package/dist/application/role-repository.js +19 -2
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +30 -0
- package/dist/application/session-control.js +72 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +115 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +259 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +285 -32
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/briefing.d.ts +3 -1
- package/dist/briefing.js +70 -30
- package/dist/build-info.json +7 -4
- package/dist/capabilities.d.ts +6 -1
- package/dist/capabilities.js +8 -0
- package/dist/cli.js +407 -37
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +123 -19
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +55 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +26 -9
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +20 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +23 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +8 -34
- package/dist/monitor.js +89 -152
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +8 -0
- package/dist/notifications/target-cleanup.js +41 -0
- package/dist/notifications/task-producer.d.ts +18 -0
- package/dist/notifications/task-producer.js +67 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +25 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +6 -2
- package/dist/rooms-tasks/close.js +246 -37
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +2 -4
- package/dist/rooms-tasks/cowork-adapter.js +7 -121
- package/dist/rooms-tasks/cowork-http.js +29 -1
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +84 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +285 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -8
- package/dist/rooms-tasks/task-state.js +245 -25
- package/dist/rooms-tasks/types.d.ts +40 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +25 -3
- package/dist/runner.js +291 -49
- package/dist/session/acp.d.ts +53 -1
- package/dist/session/acp.js +216 -40
- package/dist/session/arbiter.d.ts +11 -0
- package/dist/session/arbiter.js +43 -2
- package/dist/session/codex-app-server.d.ts +32 -0
- package/dist/session/codex-app-server.js +137 -23
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +68 -3
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-store.d.ts +1 -1
- package/dist/session/conversation-store.js +7 -11
- package/dist/session/conversation-types.d.ts +28 -3
- package/dist/session/prompt-batch.d.ts +63 -0
- package/dist/session/prompt-batch.js +86 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +14 -0
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +4 -0
- package/dist/startup-prompt.js +36 -0
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +75 -0
- package/dist/subscriptions/login.js +182 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +19 -33
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +113 -65
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +63 -2
- package/dist/web/auth.js +191 -10
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +238 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +654 -55
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +82 -0
- package/dist/workspace-enrollment.js +566 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/acp-files.md +146 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +20 -19
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +34 -11
- package/presets/fleet/roles/Engineer.yaml +40 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
|
@@ -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.13`, 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,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.
|