@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.70
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 +27 -0
- package/dist/application/conversation-history.js +215 -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 +2 -2
- 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 +17 -3
- 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 +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 +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 +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 +1 -8
- package/dist/daemon-recovery.js +8 -113
- 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 +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 +10 -0
- package/dist/notifications/target-cleanup.js +63 -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 +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 +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 +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 +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 +33 -4
- package/dist/runner.js +389 -56
- 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 +76 -0
- package/dist/subscriptions/login.js +207 -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/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/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/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 +128 -33
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +639 -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 +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 +190 -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
package/dist/doctor.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { userInfo
|
|
1
|
+
import { describeRegistration, refreshServiceRegistrations, registrationDrift } from './supervisor/registration-refresh.js';
|
|
2
|
+
import { userInfo } from 'node:os';
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import { attachOursClient, } from '@ours.network/sdk/client';
|
|
5
5
|
import { realExec } from './exec.js';
|
|
6
6
|
import { isolationContextFor, loadConfig } from './config.js';
|
|
7
7
|
import { getAdapter, productionAdapters } from './harness/registry.js';
|
|
8
8
|
import { analyzeFleetPermissions, formatNative } from './permissions.js';
|
|
9
|
+
import { managedCliReport } from './managed-cli.js';
|
|
9
10
|
import { resolveBundledAcpAgent } from './harness/acp-agent.js';
|
|
10
11
|
import { agentDir, deriveXdgRuntimeDir } from './paths.js';
|
|
11
12
|
import { resolveIsolation, harnessRuntimeDir } from './isolation/policy.js';
|
|
12
13
|
import { hermesConfiguredProvider } from './harness/hermes-startup.js';
|
|
13
14
|
import { makeBubblewrapBackend } from './isolation/bubblewrap.js';
|
|
14
|
-
import { authResolutionHint, resolveEndpoint, } from './monitor.js';
|
|
15
15
|
import { readClientProfile } from './client-profile.js';
|
|
16
16
|
import { readScheduledLoops, storedLoopHealth } from './loops/state.js';
|
|
17
17
|
import { controlSocketPath } from './session/control.js';
|
|
@@ -37,25 +37,21 @@ function resolveMonitorProfiles(roles) {
|
|
|
37
37
|
const profiles = [];
|
|
38
38
|
for (const role of roles.filter(r => r.monitor?.mode === 'fleet')) {
|
|
39
39
|
const env = { ...process.env, ...(role.env ?? {}) };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
existing.roles.push(role.name);
|
|
47
|
-
else
|
|
48
|
-
profiles.push({ client, roles: [role.name] });
|
|
40
|
+
let client;
|
|
41
|
+
try {
|
|
42
|
+
client = readClientProfile(env);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
profiles.push({ roles: [role.name], error: error instanceof Error ? error.message : 'Invalid gateway profile' });
|
|
49
46
|
continue;
|
|
50
47
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
&& p.endpoint.headers['x-ours-api-token'] === token);
|
|
48
|
+
const existing = profiles.find(p => p.client?.endpoint === client.endpoint
|
|
49
|
+
&& p.client.expectedInstanceId === client.expectedInstanceId
|
|
50
|
+
&& p.client.credentialPath === client.credentialPath);
|
|
55
51
|
if (existing)
|
|
56
52
|
existing.roles.push(role.name);
|
|
57
53
|
else
|
|
58
|
-
profiles.push({
|
|
54
|
+
profiles.push({ client, roles: [role.name] });
|
|
59
55
|
}
|
|
60
56
|
return profiles;
|
|
61
57
|
}
|
|
@@ -101,6 +97,25 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
101
97
|
// trustworthy as the answer, and a second install with the same semver and
|
|
102
98
|
// different behaviour is invisible to `--version`.
|
|
103
99
|
checks.push(...installChecks(opts.installScan));
|
|
100
|
+
if (opts.serviceRegistration) {
|
|
101
|
+
try {
|
|
102
|
+
const reports = await refreshServiceRegistrations({
|
|
103
|
+
...opts.serviceRegistration, write: false, exec, platform,
|
|
104
|
+
});
|
|
105
|
+
for (const report of reports.filter(report => report.state !== 'absent'))
|
|
106
|
+
checks.push({
|
|
107
|
+
name: `service: ${report.service}`,
|
|
108
|
+
// Same predicate as `service check`, so a pending reload is drift here too.
|
|
109
|
+
ok: registrationDrift([report]).length === 0,
|
|
110
|
+
detail: describeRegistration(report).replace(/^\S+\s+/, '')
|
|
111
|
+
+ (report.state === 'stale' ? '; run: ours-fleet service refresh' : ''),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
checks.push({ name: 'service definitions', ok: false,
|
|
116
|
+
detail: `could not inspect: ${error?.message ?? String(error)}` });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
104
119
|
const major = Number(process.versions.node.split('.')[0]);
|
|
105
120
|
checks.push({
|
|
106
121
|
name: 'node', ok: major >= 20,
|
|
@@ -156,7 +171,7 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
156
171
|
// ── Rooms/tasks checks ────────────────────────────────────────
|
|
157
172
|
if (loaded.ok && loaded.rooms) {
|
|
158
173
|
const rooms = loaded.rooms;
|
|
159
|
-
// Cowork
|
|
174
|
+
// Cowork gateway reachability
|
|
160
175
|
const coworkConfig = rooms.cowork?.config;
|
|
161
176
|
try {
|
|
162
177
|
const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
|
|
@@ -165,14 +180,14 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
165
180
|
checks.push({
|
|
166
181
|
name: 'cowork', ok: reachable,
|
|
167
182
|
detail: reachable
|
|
168
|
-
? 'management
|
|
169
|
-
: `management
|
|
183
|
+
? 'gateway management reachable'
|
|
184
|
+
: `gateway management unreachable${coworkConfig ? ` (config: ${coworkConfig})` : ''}`,
|
|
170
185
|
});
|
|
171
186
|
}
|
|
172
187
|
catch (e) {
|
|
173
188
|
checks.push({
|
|
174
189
|
name: 'cowork', ok: false,
|
|
175
|
-
detail: `management
|
|
190
|
+
detail: `gateway management error${coworkConfig ? ` (config: ${coworkConfig})` : ''}: ${e?.message ?? e}`,
|
|
176
191
|
});
|
|
177
192
|
}
|
|
178
193
|
// Owner CID shape
|
|
@@ -219,17 +234,6 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
219
234
|
: `template '${defaultTemplate}' not found`,
|
|
220
235
|
});
|
|
221
236
|
}
|
|
222
|
-
// Shared-daemon selection coherence
|
|
223
|
-
if (rooms.cowork?.config) {
|
|
224
|
-
const { existsSync: exists } = await import('node:fs');
|
|
225
|
-
const configOk = exists(rooms.cowork.config);
|
|
226
|
-
checks.push({
|
|
227
|
-
name: 'rooms: cowork config', ok: configOk,
|
|
228
|
-
detail: configOk
|
|
229
|
-
? `cowork config at ${rooms.cowork.config}`
|
|
230
|
-
: `cowork config not found: ${rooms.cowork.config}`,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
237
|
// Hard room-management capability check
|
|
234
238
|
try {
|
|
235
239
|
const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
|
|
@@ -273,20 +277,17 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
273
277
|
}
|
|
274
278
|
catch { /* state dir may not exist yet */ }
|
|
275
279
|
}
|
|
276
|
-
let managedSelection =
|
|
280
|
+
let managedSelection = true;
|
|
277
281
|
try {
|
|
278
282
|
// Mark explicit selection before validation so a damaged managed profile does
|
|
279
283
|
// not produce advice to start an unrelated local daemon.
|
|
280
|
-
managedSelection =
|
|
281
|
-
|| existsSync(join(process.env.HOME || homedir(), '.ours-client', 'profile.json'));
|
|
284
|
+
managedSelection = true;
|
|
282
285
|
const selected = readClientProfile(process.env);
|
|
283
286
|
managedSelection = selected !== undefined;
|
|
284
|
-
const client = await attachDaemon(
|
|
287
|
+
const client = await attachDaemon({
|
|
285
288
|
endpoint: selected.endpoint, expectedInstanceId: selected.expectedInstanceId,
|
|
286
289
|
credentialPath: selected.credentialPath, env: {}, sessionMode: 'external',
|
|
287
290
|
leaseToken: `ours-fleet-doctor-${process.pid}`,
|
|
288
|
-
} : {
|
|
289
|
-
env: process.env, leaseToken: `ours-fleet-doctor-${process.pid}`, clientPid: process.pid,
|
|
290
291
|
});
|
|
291
292
|
let daemonVersion;
|
|
292
293
|
try {
|
|
@@ -368,6 +369,32 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
368
369
|
+ `grants only ${analysis.capabilities.join(', ') || '(nothing)'}`,
|
|
369
370
|
});
|
|
370
371
|
}
|
|
372
|
+
// Managed CLI policy. Three separate rows on purpose: what Fleet generated
|
|
373
|
+
// (static), what the running session loaded, and what the supervisor audit
|
|
374
|
+
// ledger actually recorded. The first two never stand in for the third.
|
|
375
|
+
if (roles.some(role => role.managed_cli?.length)) {
|
|
376
|
+
const report = managedCliReport({ roles }, opts.configPath, { write: false, platform });
|
|
377
|
+
for (const role of report.roles.filter(item => item.setup.state !== 'not-declared')) {
|
|
378
|
+
checks.push({
|
|
379
|
+
name: `managed CLI setup: ${role.role}`,
|
|
380
|
+
ok: ['prepared', 'generated-at-launch', 'not-required'].includes(role.setup.state),
|
|
381
|
+
detail: `static: ${role.setup.state} — ${role.setup.detail}`
|
|
382
|
+
+ (['missing', 'stale'].includes(role.setup.state) ? '; run: ours-fleet managed-cli setup' : '')
|
|
383
|
+
+ (role.warnings.length ? `; ${role.warnings.join('; ')}` : ''),
|
|
384
|
+
});
|
|
385
|
+
if (role.session_policy !== 'not-applicable')
|
|
386
|
+
checks.push({
|
|
387
|
+
name: `managed CLI session: ${role.role}`, ok: true,
|
|
388
|
+
detail: role.session_policy === 'restart-required'
|
|
389
|
+
? 'the last launch loaded a different policy — restart this agent to load the current one'
|
|
390
|
+
: role.session_policy === 'never-launched' ? 'not launched since setup; the policy loads at its next start'
|
|
391
|
+
: 'the last launch loaded the current policy',
|
|
392
|
+
});
|
|
393
|
+
checks.push({
|
|
394
|
+
name: `managed CLI observed: ${role.role}`, ok: true, detail: role.observed.summary,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
371
398
|
for (const role of roles.filter(candidate => candidate.auth_proxy !== undefined)) {
|
|
372
399
|
const proxy = role.auth_proxy;
|
|
373
400
|
const credentialKeys = Object.keys(role.env ?? {}).filter(key => /^(ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|AUTHORIZATION)$/i.test(key)
|
|
@@ -448,14 +475,18 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
448
475
|
// shared-mode misconfig (401) surfaces here rather than as a silent deaf monitor.
|
|
449
476
|
const monitorProfiles = resolveMonitorProfiles(roles);
|
|
450
477
|
for (const profile of monitorProfiles) {
|
|
451
|
-
const
|
|
478
|
+
const selected = profile.client;
|
|
452
479
|
const checkName = monitorProfiles.length === 1
|
|
453
480
|
? 'monitor: daemon API'
|
|
454
481
|
: `monitor: daemon API (${profile.roles.join(', ')})`;
|
|
455
|
-
let ok = false, detail;
|
|
482
|
+
let ok = false, detail = 'gateway profile unavailable';
|
|
483
|
+
if (profile.error) {
|
|
484
|
+
checks.push({ name: checkName, ok: false, detail: profile.error });
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
456
487
|
try {
|
|
457
488
|
if (selected) {
|
|
458
|
-
const daemon = await
|
|
489
|
+
const daemon = await attachDaemon({
|
|
459
490
|
endpoint: selected.endpoint,
|
|
460
491
|
expectedInstanceId: selected.expectedInstanceId,
|
|
461
492
|
credentialPath: selected.credentialPath,
|
|
@@ -464,7 +495,7 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
464
495
|
});
|
|
465
496
|
try {
|
|
466
497
|
const [info, identities] = await Promise.all([
|
|
467
|
-
daemon.version(), daemon.identities(),
|
|
498
|
+
daemon.version(), daemon.identities?.(),
|
|
468
499
|
]);
|
|
469
500
|
if (info.name !== 'ours' || !Array.isArray(identities))
|
|
470
501
|
throw new Error('selected daemon metadata is invalid');
|
|
@@ -477,27 +508,11 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
|
|
|
477
508
|
checks.push({ name: checkName, ok, detail });
|
|
478
509
|
continue;
|
|
479
510
|
}
|
|
480
|
-
const live = await fetchImpl(`${endpoint.origin}/state-dir`, {});
|
|
481
|
-
if (!live.ok) {
|
|
482
|
-
detail = `daemon on :${endpoint.port} answered /state-dir with HTTP ${live.status} — not the ours daemon?`;
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
const auth = await fetchImpl(`${endpoint.origin}/identities`, { headers: endpoint.headers });
|
|
486
|
-
if (auth.status === 401)
|
|
487
|
-
detail = `reachable on :${endpoint.port} but the API token was rejected (401) — ` +
|
|
488
|
-
authResolutionHint(endpoint);
|
|
489
|
-
else if (!auth.ok)
|
|
490
|
-
detail = `reachable on :${endpoint.port} but /identities returned HTTP ${auth.status}`;
|
|
491
|
-
else {
|
|
492
|
-
ok = true;
|
|
493
|
-
detail = `reachable on :${endpoint.port}, authorized — supervisor wake stream available`;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
511
|
}
|
|
497
512
|
catch (e) {
|
|
498
|
-
const target = selected
|
|
513
|
+
const target = selected.endpoint;
|
|
499
514
|
detail = `unreachable on ${target} — monitored roles run degraded until it is up ` +
|
|
500
|
-
`(
|
|
515
|
+
`(check the configured gateway and issued credential) [${e?.message ?? e}]`;
|
|
501
516
|
}
|
|
502
517
|
checks.push({ name: checkName, ok, detail });
|
|
503
518
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const erasedArg: (value: string) => string;
|
|
2
|
+
export declare function redactErasedContent<T>(value: T): T;
|
|
3
|
+
/** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
|
|
4
|
+
export declare function writePrivacyFilteredLedger(path: string, value: unknown): void;
|
|
5
|
+
/** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
|
|
6
|
+
export declare function eraseResourcePresentations(resources: ReadonlyArray<{
|
|
7
|
+
kind: 'task' | 'room' | 'agent';
|
|
8
|
+
id: string;
|
|
9
|
+
}>): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { replaceFileAtomically, withSynchronousFileLock } from './atomic-file.js';
|
|
5
|
+
import { stateRoot } from './paths.js';
|
|
6
|
+
const registryPath = () => join(stateRoot(), 'erased-resources.json');
|
|
7
|
+
const lockPath = () => join(stateRoot(), 'locks', 'audit-erasure');
|
|
8
|
+
const ledgerNames = ['.fleet-command-audit.json', '.owner-channel-command-audit.json', '.owner-channel-lifecycle-outbox.json'];
|
|
9
|
+
export const erasedArg = (value) => `[erased:${createHash('sha256').update(value).digest('hex')}]`;
|
|
10
|
+
function registry() {
|
|
11
|
+
if (!existsSync(registryPath()))
|
|
12
|
+
return new Set();
|
|
13
|
+
const value = JSON.parse(readFileSync(registryPath(), 'utf8'));
|
|
14
|
+
if (!Array.isArray(value) || value.some(v => typeof v !== 'string'))
|
|
15
|
+
throw new Error('Invalid erasure registry');
|
|
16
|
+
return new Set(value);
|
|
17
|
+
}
|
|
18
|
+
/** Preserve delivery/deduplication metadata while removing owned presentation content. */
|
|
19
|
+
function redact(value, erased) {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
return value.map(item => redact(item, erased));
|
|
22
|
+
if (!value || typeof value !== 'object')
|
|
23
|
+
return value;
|
|
24
|
+
const next = { ...value };
|
|
25
|
+
const kind = value.kind === 'lifecycle_failure' ? value.resource?.toLowerCase() : value.kind === 'agent_started' ? 'agent' : value.kind;
|
|
26
|
+
if (typeof value.id === 'string' && erased.has(`${kind}:${value.id}`)) {
|
|
27
|
+
for (const key of ['title', 'reason', 'roomName', 'label', 'template', 'configuration', 'model', 'permissions'])
|
|
28
|
+
delete next[key];
|
|
29
|
+
if (kind === 'task')
|
|
30
|
+
next.agents = [];
|
|
31
|
+
if (kind === 'room') {
|
|
32
|
+
delete next.name;
|
|
33
|
+
next.participants = [];
|
|
34
|
+
}
|
|
35
|
+
if (kind === 'agent') {
|
|
36
|
+
next.name = 'erased';
|
|
37
|
+
next.brain = '[erased]';
|
|
38
|
+
next.role = '[erased]';
|
|
39
|
+
next.inherited = [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (typeof value.roomId === 'string' && erased.has(`room:${value.roomId}`)) {
|
|
43
|
+
delete next.roomName;
|
|
44
|
+
if (kind === 'task')
|
|
45
|
+
next.agents = [];
|
|
46
|
+
}
|
|
47
|
+
const resources = value.outcome?.resourceIds;
|
|
48
|
+
const ownsAttempt = resources && Object.entries(resources).some(([key, id]) => erased.has(`${key}:${id}`));
|
|
49
|
+
const ownsPresentation = value.outcome?.presentations?.some((p) => erased.has(`${p.kind === 'agent_started' ? 'agent' : p.kind}:${p.id}`));
|
|
50
|
+
if ((ownsAttempt || ownsPresentation) && Array.isArray(value.argv) && !value.erased) {
|
|
51
|
+
next.argv = value.argv.map(erasedArg);
|
|
52
|
+
next.erased = true;
|
|
53
|
+
}
|
|
54
|
+
for (const key of Object.keys(next))
|
|
55
|
+
if (typeof next[key] === 'object')
|
|
56
|
+
next[key] = redact(next[key], erased);
|
|
57
|
+
return next;
|
|
58
|
+
}
|
|
59
|
+
export function redactErasedContent(value) { return redact(value, registry()); }
|
|
60
|
+
/** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
|
|
61
|
+
export function writePrivacyFilteredLedger(path, value) {
|
|
62
|
+
withSynchronousFileLock(lockPath(), () => replaceFileAtomically(path, JSON.stringify(redact(value, registry())) + '\n'));
|
|
63
|
+
}
|
|
64
|
+
/** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
|
|
65
|
+
export function eraseResourcePresentations(resources) {
|
|
66
|
+
withSynchronousFileLock(lockPath(), () => {
|
|
67
|
+
const erased = registry();
|
|
68
|
+
for (const resource of resources)
|
|
69
|
+
erased.add(`${resource.kind}:${resource.id}`);
|
|
70
|
+
replaceFileAtomically(registryPath(), JSON.stringify([...erased]));
|
|
71
|
+
for (const root of [join(stateRoot(), 'agents'), join(stateRoot(), 'tmp'), join(stateRoot(), 'recovery', 'temporary')]) {
|
|
72
|
+
if (!existsSync(root))
|
|
73
|
+
continue;
|
|
74
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
75
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
76
|
+
continue;
|
|
77
|
+
for (const name of ledgerNames) {
|
|
78
|
+
const path = join(root, entry.name, name);
|
|
79
|
+
if (!existsSync(path) || !lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
|
|
80
|
+
continue;
|
|
81
|
+
const old = JSON.parse(readFileSync(path, 'utf8'));
|
|
82
|
+
replaceFileAtomically(path, JSON.stringify(redact(old, erased)) + '\n');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FileHandle } from 'node:fs/promises';
|
|
2
|
+
export declare const MAX_FILE_BYTES: number;
|
|
3
|
+
/** Pin every component; neither trusted root ancestors nor agent components follow symlinks. */
|
|
4
|
+
export declare function openDirectory(path: string): Promise<FileHandle>;
|
|
5
|
+
/** The agent's provided path, resolved from its working directory; no export subtree. */
|
|
6
|
+
export declare function chatFilePath(cwd: string, input: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Runs in the harness bridge, never in the supervisor's OS context. Ancestor
|
|
9
|
+
* symlinks resolve normally; the named file itself must be one regular,
|
|
10
|
+
* singly-linked file within the size limit. `path` is the opened file's real path.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openChatFile(cwd: string, input: string): Promise<{
|
|
13
|
+
file: FileHandle;
|
|
14
|
+
path: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { open, readlink } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
4
|
+
export const MAX_FILE_BYTES = 20 * 1024 * 1024;
|
|
5
|
+
const FLAGS = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
6
|
+
/** Pin every component; neither trusted root ancestors nor agent components follow symlinks. */
|
|
7
|
+
export async function openDirectory(path) {
|
|
8
|
+
if (process.platform !== 'linux' || !isAbsolute(path) || path !== resolve(path))
|
|
9
|
+
throw Error('FILE_DELIVERY_REQUIRES_LINUX_ROOT');
|
|
10
|
+
let dir = await open('/', FLAGS | constants.O_DIRECTORY);
|
|
11
|
+
try {
|
|
12
|
+
for (const part of path.split('/').filter(Boolean)) {
|
|
13
|
+
const next = await open(`/proc/self/fd/${dir.fd}/${part}`, FLAGS | constants.O_DIRECTORY);
|
|
14
|
+
await dir.close();
|
|
15
|
+
dir = next;
|
|
16
|
+
}
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
await dir.close();
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The agent's provided path, resolved from its working directory; no export subtree. */
|
|
25
|
+
export function chatFilePath(cwd, input) {
|
|
26
|
+
if (typeof input !== 'string' || !input || input.length > 4096 || /[\x00-\x1f]/.test(input))
|
|
27
|
+
throw Error('INVALID_CHAT_FILE_PATH');
|
|
28
|
+
return resolve(cwd, input);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Runs in the harness bridge, never in the supervisor's OS context. Ancestor
|
|
32
|
+
* symlinks resolve normally; the named file itself must be one regular,
|
|
33
|
+
* singly-linked file within the size limit. `path` is the opened file's real path.
|
|
34
|
+
*/
|
|
35
|
+
export async function openChatFile(cwd, input) {
|
|
36
|
+
if (process.platform !== 'linux')
|
|
37
|
+
throw Error('CHAT_FILE_REQUIRES_LINUX');
|
|
38
|
+
let file;
|
|
39
|
+
try {
|
|
40
|
+
file = await open(chatFilePath(cwd, input), FLAGS);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const code = error.code;
|
|
44
|
+
if (error instanceof Error && error.message === 'INVALID_CHAT_FILE_PATH')
|
|
45
|
+
throw error;
|
|
46
|
+
throw Error(code === 'ELOOP' ? 'CHAT_FILE_IS_SYMLINK' : code === 'ENOENT' || code === 'ENOTDIR' ? 'CHAT_FILE_NOT_FOUND'
|
|
47
|
+
: code === 'EACCES' || code === 'EPERM' ? 'CHAT_FILE_NOT_READABLE' : 'CHAT_FILE_OPEN_FAILED');
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const stat = await file.stat();
|
|
51
|
+
if (!stat.isFile())
|
|
52
|
+
throw Error('CHAT_FILE_NOT_REGULAR');
|
|
53
|
+
if (stat.nlink !== 1)
|
|
54
|
+
throw Error('CHAT_FILE_HARDLINKED');
|
|
55
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
56
|
+
throw Error('FILE_EXCEEDS_20_MIB');
|
|
57
|
+
return { file, path: await readlink(`/proc/self/fd/${file.fd}`) };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
await file.close();
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DeliveredFile, FileDeliveryBinding } from './types.js';
|
|
2
|
+
export declare function artifactDirectory(stateDir: string): string;
|
|
3
|
+
export declare class ArtifactStore {
|
|
4
|
+
private readonly stateDir;
|
|
5
|
+
constructor(stateDir: string);
|
|
6
|
+
copy(source: {
|
|
7
|
+
size: number;
|
|
8
|
+
body: ReadableStream<Uint8Array>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}, name: string, mimeType: string, binding: FileDeliveryBinding, signal: AbortSignal): Promise<DeliveredFile>;
|
|
11
|
+
}
|
|
12
|
+
export declare function readDeliveredFile(stateDir: string, id: string): Promise<{
|
|
13
|
+
record: DeliveredFile;
|
|
14
|
+
bytes: Buffer;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { constants, mkdirSync, readFileSync, readdirSync, lstatSync, openSync, fstatSync, closeSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { open, unlink } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { stateRoot } from '../paths.js';
|
|
6
|
+
import { openDirectory, MAX_FILE_BYTES } from './reader.js';
|
|
7
|
+
export function artifactDirectory(stateDir) {
|
|
8
|
+
const incarnation = readFileSync(join(stateDir, '.session-id'), 'utf8').trim();
|
|
9
|
+
if (!/^[a-f0-9-]{36}$/.test(incarnation))
|
|
10
|
+
throw Error('INVALID_ROLE_INCARNATION');
|
|
11
|
+
const key = createHash('sha256').update(JSON.stringify([stateDir, incarnation])).digest('hex');
|
|
12
|
+
return join(stateRoot(), 'private-file-delivery', key);
|
|
13
|
+
}
|
|
14
|
+
function privateDirectory(path) {
|
|
15
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
16
|
+
const stat = lstatSync(path);
|
|
17
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid?.() || stat.mode & 0o077)
|
|
18
|
+
throw Error('INVALID_ARTIFACT_DIRECTORY');
|
|
19
|
+
}
|
|
20
|
+
export class ArtifactStore {
|
|
21
|
+
stateDir;
|
|
22
|
+
constructor(stateDir) {
|
|
23
|
+
this.stateDir = stateDir;
|
|
24
|
+
}
|
|
25
|
+
async copy(source, name, mimeType, binding, signal) {
|
|
26
|
+
if (!Number.isSafeInteger(source.size) || source.size < 0 || source.size > MAX_FILE_BYTES)
|
|
27
|
+
throw Error('FILE_EXCEEDS_20_MIB');
|
|
28
|
+
const path = artifactDirectory(this.stateDir);
|
|
29
|
+
privateDirectory(join(stateRoot(), 'private-file-delivery'));
|
|
30
|
+
privateDirectory(path);
|
|
31
|
+
const root = await openDirectory(path);
|
|
32
|
+
const id = randomUUID(), base = `/proc/self/fd/${root.fd}/`, partial = base + id + '.part';
|
|
33
|
+
let file;
|
|
34
|
+
const reader = source.body.getReader();
|
|
35
|
+
try {
|
|
36
|
+
// Orphans count toward the same bounded quota. They are never republished.
|
|
37
|
+
const entries = readdirSync(base).filter(p => /\.(data|part)$/.test(p));
|
|
38
|
+
const bytes = entries.reduce((sum, p) => sum + lstatSync(base + p).size, 0);
|
|
39
|
+
if (entries.length >= 100 || bytes + source.size > 200 * 1024 * 1024)
|
|
40
|
+
throw Error('ARTIFACT_QUOTA_EXCEEDED');
|
|
41
|
+
file = await open(partial, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
42
|
+
const hash = createHash('sha256');
|
|
43
|
+
let size = 0;
|
|
44
|
+
for (;;) {
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
const chunk = await reader.read();
|
|
47
|
+
if (chunk.done)
|
|
48
|
+
break;
|
|
49
|
+
size += chunk.value.length;
|
|
50
|
+
if (size > source.size || size > MAX_FILE_BYTES)
|
|
51
|
+
throw Error('SOURCE_SIZE_CHANGED');
|
|
52
|
+
hash.update(chunk.value);
|
|
53
|
+
let offset = 0;
|
|
54
|
+
while (offset < chunk.value.length) {
|
|
55
|
+
const { bytesWritten } = await file.write(chunk.value, offset, chunk.value.length - offset);
|
|
56
|
+
if (!bytesWritten)
|
|
57
|
+
throw Error('SHORT_ARTIFACT_WRITE');
|
|
58
|
+
offset += bytesWritten;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (size !== source.size)
|
|
62
|
+
throw Error('SOURCE_SIZE_CHANGED');
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
await file.chmod(0o400);
|
|
65
|
+
await file.sync();
|
|
66
|
+
await file.close();
|
|
67
|
+
file = undefined;
|
|
68
|
+
const record = { id, name, mimeType, size, sha256: hash.digest('hex'), ...binding };
|
|
69
|
+
renameSync(partial, base + id + '.data');
|
|
70
|
+
writeFileSync(base + id + '.json', JSON.stringify(record), { flag: 'wx', mode: 0o400, flush: true });
|
|
71
|
+
// No pending-send record or recovery: a later failure leaves only unreferenced stored bytes.
|
|
72
|
+
await root.sync();
|
|
73
|
+
// The newly created incarnation directory and store root must also survive
|
|
74
|
+
// a crash before a durable conversation event can reference this file.
|
|
75
|
+
for (const parent of [join(stateRoot(), 'private-file-delivery'), stateRoot()]) {
|
|
76
|
+
const handle = await openDirectory(parent);
|
|
77
|
+
try {
|
|
78
|
+
await handle.sync();
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await handle.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return record;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await file?.close();
|
|
88
|
+
await reader.cancel().catch(() => { });
|
|
89
|
+
await source.close();
|
|
90
|
+
await unlink(partial).catch(() => { });
|
|
91
|
+
await root.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export async function readDeliveredFile(stateDir, id) {
|
|
96
|
+
if (!/^[a-f0-9-]{36}$/.test(id))
|
|
97
|
+
throw Error('ARTIFACT_NOT_FOUND');
|
|
98
|
+
const root = await openDirectory(artifactDirectory(stateDir));
|
|
99
|
+
const read = (suffix, max) => {
|
|
100
|
+
const fd = openSync(`/proc/self/fd/${root.fd}/${id}.${suffix}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
101
|
+
try {
|
|
102
|
+
const stat = fstatSync(fd);
|
|
103
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== process.getuid?.() || stat.size > max)
|
|
104
|
+
throw Error('INVALID_STORED_ARTIFACT');
|
|
105
|
+
const bytes = readFileSync(fd);
|
|
106
|
+
if (bytes.length > max)
|
|
107
|
+
throw Error('ARTIFACT_TOO_LARGE');
|
|
108
|
+
return bytes;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
closeSync(fd);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
const record = JSON.parse(read('json', 4096).toString());
|
|
116
|
+
const bytes = read('data', MAX_FILE_BYTES);
|
|
117
|
+
if (record.id !== id || record.size !== bytes.length || record.sha256 !== createHash('sha256').update(bytes).digest('hex') || typeof record.name !== 'string')
|
|
118
|
+
throw Error('ARTIFACT_INTEGRITY_FAILED');
|
|
119
|
+
return { record, bytes };
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
await root.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface FileDeliveryBinding {
|
|
2
|
+
sessionGeneration: string;
|
|
3
|
+
acpSessionId: string;
|
|
4
|
+
turnId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DeliveredFile extends FileDeliveryBinding {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
size: number;
|
|
11
|
+
sha256: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FileDeliveryInput {
|
|
14
|
+
path: string;
|
|
15
|
+
filename?: string;
|
|
16
|
+
mime?: string;
|
|
17
|
+
}
|
|
18
|
+
export type CopyChatFile = (binding: FileDeliveryBinding) => Promise<DeliveredFile>;
|
|
19
|
+
export type SendChatFile = (input: FileDeliveryInput, signal: AbortSignal, copy: CopyChatFile) => Promise<DeliveredFile>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -113,6 +113,7 @@ export interface FleetCommandClassification {
|
|
|
113
113
|
decision: FleetCommandDecision;
|
|
114
114
|
}
|
|
115
115
|
export interface FleetAuditAttempt {
|
|
116
|
+
erased?: boolean;
|
|
116
117
|
version: 1;
|
|
117
118
|
correlationId: string;
|
|
118
119
|
requestId: string;
|
|
@@ -144,6 +145,12 @@ export declare const fleetProxyTopLevelInventory: Readonly<{
|
|
|
144
145
|
}>;
|
|
145
146
|
/** Classify before Commander parsing. Unknown and internal paths fail closed. */
|
|
146
147
|
export declare function classifyFleetArgv(argv: readonly string[]): FleetCommandClassification;
|
|
148
|
+
/**
|
|
149
|
+
* The pinned configuration of a managed entry (src/managed-cli.ts) is recorded
|
|
150
|
+
* as a fingerprint, not as its path: diagnostics must tell which configuration a
|
|
151
|
+
* row ran under without the ledger holding the path itself.
|
|
152
|
+
*/
|
|
153
|
+
export declare const managedPinMarker: (configuration: string) => string;
|
|
147
154
|
/** Faithful ordered argv with deterministic structural redaction. */
|
|
148
155
|
export declare function redactFleetArgv(argv: readonly string[]): string[];
|
|
149
156
|
/** Durable write-once attempt ledger; contains redacted argv only. */
|