@ours.network/fleet 1.2.0-nightly.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -68
- package/dist/account-origin.d.ts +5 -0
- package/dist/account-origin.js +31 -0
- package/dist/agent-ours/bridge.js +26 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
- package/dist/agent-ours/mcp-endpoint.js +101 -25
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +27 -1
- package/dist/agent-ours/service.js +107 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +31 -0
- package/dist/application/conversation-history.js +339 -0
- package/dist/application/conversation-role-observer.d.ts +62 -0
- package/dist/application/conversation-role-observer.js +206 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +3 -3
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/resource-observer.d.ts +43 -0
- package/dist/application/resource-observer.js +323 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +3 -1
- package/dist/application/role-repository.js +89 -54
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +63 -17
- package/dist/application/session-control.js +221 -23
- package/dist/application/supervisor-ours-tools.js +5 -11
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +261 -0
- package/dist/application/task-room-service.d.ts +101 -3
- package/dist/application/task-room-service.js +286 -33
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +34 -1
- package/dist/atomic-file.js +161 -18
- package/dist/briefing.d.ts +5 -1
- package/dist/briefing.js +138 -31
- package/dist/build-info.json +10 -5
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +391 -38
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +14 -9
- package/dist/daemon-recovery.js +124 -115
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +402 -20
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +13 -0
- package/dist/fleet-command-audit.js +31 -9
- package/dist/harness/agent-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +21 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +24 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +9 -34
- package/dist/monitor.js +90 -153
- package/dist/notifications/fleet-producer.d.ts +31 -0
- package/dist/notifications/fleet-producer.js +351 -0
- package/dist/notifications/outbox.d.ts +59 -0
- package/dist/notifications/outbox.js +199 -0
- package/dist/notifications/target-cleanup.d.ts +16 -0
- package/dist/notifications/target-cleanup.js +75 -0
- package/dist/notifications/task-producer.d.ts +20 -0
- package/dist/notifications/task-producer.js +100 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +5 -0
- package/dist/preset-migration.js +55 -0
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.js +42 -0
- package/dist/reminders/manager.d.ts +75 -0
- package/dist/reminders/manager.js +278 -0
- package/dist/reminders/runtime.d.ts +27 -0
- package/dist/reminders/runtime.js +32 -0
- package/dist/reminders/schedule.d.ts +37 -0
- package/dist/reminders/schedule.js +104 -0
- package/dist/reminders/store.d.ts +341 -0
- package/dist/reminders/store.js +762 -0
- package/dist/reminders/tools.d.ts +20 -0
- package/dist/reminders/tools.js +114 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +84 -0
- package/dist/rooms-tasks/archived-absence.d.ts +4 -0
- package/dist/rooms-tasks/archived-absence.js +11 -0
- package/dist/rooms-tasks/cli.js +182 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +224 -36
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
- package/dist/rooms-tasks/cowork-adapter.js +15 -121
- package/dist/rooms-tasks/cowork-http.js +56 -6
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +66 -10
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +286 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-review.d.ts +113 -0
- package/dist/rooms-tasks/task-review.js +561 -0
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +51 -5
- package/dist/rooms-tasks/task-state.js +358 -50
- package/dist/rooms-tasks/task-tools.d.ts +35 -0
- package/dist/rooms-tasks/task-tools.js +134 -0
- package/dist/rooms-tasks/types.d.ts +78 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +23 -4
- package/dist/runner.js +409 -61
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +219 -23
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-observation.d.ts +25 -0
- package/dist/session/conversation-observation.js +100 -0
- package/dist/session/conversation-replay.d.ts +50 -0
- package/dist/session/conversation-replay.js +203 -0
- package/dist/session/conversation-store.d.ts +15 -1
- package/dist/session/conversation-store.js +213 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +20 -1
- package/dist/startup-readiness.js +66 -3
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +77 -0
- package/dist/subscriptions/login.js +215 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +143 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +18 -37
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +130 -0
- package/dist/watchdog/query.d.ts +3 -1
- package/dist/watchdog/query.js +2 -1
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +65 -2
- package/dist/web/auth.js +201 -11
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/conversation-stream-codec.d.ts +23 -0
- package/dist/web/conversation-stream-codec.js +110 -0
- package/dist/web/conversation-stream.d.ts +26 -0
- package/dist/web/conversation-stream.js +239 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- package/dist/web/fleet-config-service.d.ts +7 -1
- package/dist/web/fleet-config-service.js +61 -16
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +248 -0
- package/dist/web/resource-views.d.ts +138 -0
- package/dist/web/resource-views.js +519 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +132 -34
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.js +893 -71
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +83 -0
- package/dist/workspace-enrollment.js +605 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +238 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +216 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/self-reminders.md +374 -0
- package/docs/task-progress-review.md +143 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +25 -20
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +21 -13
- package/presets/fleet/roles/Critic.yaml +24 -0
- package/presets/fleet/roles/Developer.yaml +32 -0
- package/presets/fleet/roles/Engineer.yaml +72 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
package/dist/runner.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
|
+
import { readProvenance } from './creation.js';
|
|
2
|
+
import { firstManagedSessionMayStart, managedRecoveryPath, readManagedRecovery, writeManagedRecovery } from './managed-recovery.js';
|
|
3
|
+
import { StartupTelemetry } from './startup-telemetry.js';
|
|
4
|
+
import { managedStartupPrompt, managedTaskPrompt, worklogRecordsPriorWork } from './startup-prompt.js';
|
|
5
|
+
import { createLayoutControl } from './rooms-tasks/layout-control.js';
|
|
6
|
+
import { TemporaryChatIdle } from './temp-idle.js';
|
|
7
|
+
import { taskSupervisorMayRun, taskMemberRetirementProof } from './task-supervision.js';
|
|
1
8
|
import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
|
|
2
9
|
import { prepareManagedHarness } from './agent-ours/harness.js';
|
|
10
|
+
import { prepareManagedCliLaunch } from './managed-cli.js';
|
|
3
11
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, realpathSync } from 'node:fs';
|
|
4
12
|
import { join } from 'node:path';
|
|
5
13
|
import { fileURLToPath } from 'node:url';
|
|
6
14
|
import { randomUUID } from 'node:crypto';
|
|
7
15
|
import { parse } from 'yaml';
|
|
8
16
|
import { agentDir, stateRoot } from './paths.js';
|
|
9
|
-
import { loadConfig, findRole, isolationContextFor,
|
|
17
|
+
import { loadConfig, findRole, isolationContextFor, resolveSnapshotMonitor, resolvePermissions, } from './config.js';
|
|
10
18
|
import { getAdapter } from './harness/registry.js';
|
|
11
19
|
import { createMonitor, probeIdentityPresence, } from './monitor.js';
|
|
12
|
-
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
|
|
20
|
+
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, acquireSelectedDaemonHealth, } from './daemon-recovery.js';
|
|
13
21
|
import { realExec } from './exec.js';
|
|
14
22
|
import { resolveIsolation } from './isolation/policy.js';
|
|
23
|
+
import { pinSubscriptionForLaunch } from './subscriptions/launch.js';
|
|
15
24
|
import { selectIsolationBackend } from './isolation/registry.js';
|
|
16
25
|
import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
|
|
17
26
|
import { resolveLaunchRuntime } from './isolation/runtime.js';
|
|
18
27
|
import { controlRequest, RoleControlServer } from './session/control.js';
|
|
19
28
|
import { ACP_CANCEL_DEADLINE_EXCEEDED, CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED, classifyShellStatus, } from './session/types.js';
|
|
29
|
+
import { sessionBackendCapabilities } from './session/types.js';
|
|
30
|
+
import { bindRoleReminders } from './reminders/runtime.js';
|
|
20
31
|
import { effectiveModelForRole, modelRecoveryHeld, reconcileModelRecovery, recordModelFailure, classifyFailureText, } from './model-recovery.js';
|
|
21
32
|
import { rotateWorklog } from './worklog.js';
|
|
22
33
|
import { reconcilePermanentRoleIdentities } from './creation.js';
|
|
@@ -27,7 +38,7 @@ import { ScheduledLoopManager, } from './loops/manager.js';
|
|
|
27
38
|
import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-proxy.js';
|
|
28
39
|
import { effectivePermissionMode } from './permissions.js';
|
|
29
40
|
import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
|
|
30
|
-
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
41
|
+
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, readTempSupervisor, } from './temp-lifecycle.js';
|
|
31
42
|
import { FleetCommandAuditStore } from './fleet-command-audit.js';
|
|
32
43
|
export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
|
|
33
44
|
export class SupervisorRecycleRequiredError extends Error {
|
|
@@ -37,6 +48,7 @@ export class SupervisorRecycleRequiredError extends Error {
|
|
|
37
48
|
this.name = 'SupervisorRecycleRequiredError';
|
|
38
49
|
}
|
|
39
50
|
}
|
|
51
|
+
const defaultProbeGeneration = (env) => probeDaemonGeneration((url, init) => globalThis.fetch(url, init), env);
|
|
40
52
|
const defaultDeps = () => ({
|
|
41
53
|
prepareAgentOurs: prepareManagedAgent,
|
|
42
54
|
releaseAgentOurs: releaseManagedAgent,
|
|
@@ -53,7 +65,7 @@ const defaultDeps = () => ({
|
|
|
53
65
|
now: () => Date.now(),
|
|
54
66
|
log: line => process.stderr.write(line + '\n'),
|
|
55
67
|
fetch: (url, init) => globalThis.fetch(url, init),
|
|
56
|
-
probeGeneration:
|
|
68
|
+
probeGeneration: defaultProbeGeneration,
|
|
57
69
|
createMonitor: opts => createMonitor(opts),
|
|
58
70
|
createOwnerChannel: opts => new OwnerChannel(opts),
|
|
59
71
|
startAgentSession: (adapter, options) => adapter.start(options),
|
|
@@ -75,12 +87,16 @@ const defaultDeps = () => ({
|
|
|
75
87
|
});
|
|
76
88
|
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
77
89
|
const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
|
|
78
|
-
function localFleetAuditor(stateDir, caller, log) {
|
|
90
|
+
export function localFleetAuditor(stateDir, caller, log, session) {
|
|
91
|
+
const bindings = new Map();
|
|
79
92
|
const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
|
|
80
93
|
return {
|
|
81
94
|
async begin(requestId, argv) {
|
|
82
95
|
let attempt = store.begin(requestId, caller, argv);
|
|
83
96
|
if (attempt.invocation === 'sending') {
|
|
97
|
+
const binding = session.taskNoticeBinding?.();
|
|
98
|
+
if (binding)
|
|
99
|
+
bindings.set(attempt.correlationId, binding);
|
|
84
100
|
log(`[${caller}] fleet proxy command ${attempt.correlationId} `
|
|
85
101
|
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
86
102
|
attempt = store.invocation(attempt.correlationId, caller, 'delivered');
|
|
@@ -94,6 +110,19 @@ function localFleetAuditor(stateDir, caller, log) {
|
|
|
94
110
|
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
95
111
|
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
96
112
|
});
|
|
113
|
+
const binding = bindings.get(attempt.correlationId);
|
|
114
|
+
if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
|
|
115
|
+
for (const p of attempt.outcome.presentations ?? [])
|
|
116
|
+
if (p.kind === 'task' && p.operation === 'create') {
|
|
117
|
+
try {
|
|
118
|
+
session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
log(`[${caller}] task-created conversation notice could not be persisted`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
bindings.delete(attempt.correlationId);
|
|
125
|
+
}
|
|
97
126
|
if (attempt.outcome?.delivery === 'sending')
|
|
98
127
|
attempt = store.outcome(attempt.correlationId, caller, 'delivered');
|
|
99
128
|
return attempt;
|
|
@@ -300,6 +329,11 @@ const carriedForward = (previous) => ({
|
|
|
300
329
|
...(previous.abruptTerminations ? { abruptTerminations: previous.abruptTerminations } : {}),
|
|
301
330
|
...(previous.supervisorStartedAt ? { supervisorStartedAt: previous.supervisorStartedAt } : {}),
|
|
302
331
|
});
|
|
332
|
+
function unrefTimer(fn, ms) {
|
|
333
|
+
const timer = setTimeout(fn, ms);
|
|
334
|
+
timer.unref?.();
|
|
335
|
+
return () => clearTimeout(timer);
|
|
336
|
+
}
|
|
303
337
|
/** Bounded exponential backoff for the nth consecutive immediate failure. */
|
|
304
338
|
export function backoffFor(consecutiveFailures) {
|
|
305
339
|
if (consecutiveFailures <= 0)
|
|
@@ -414,7 +448,7 @@ export function loadTempRole(name) {
|
|
|
414
448
|
// Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
|
|
415
449
|
// with no monitor block keeps the historical native/no-supervisor behavior.
|
|
416
450
|
if (role.monitor)
|
|
417
|
-
role.monitor =
|
|
451
|
+
role.monitor = resolveSnapshotMonitor(role.monitor);
|
|
418
452
|
role.__temp = true;
|
|
419
453
|
return role;
|
|
420
454
|
}
|
|
@@ -448,6 +482,8 @@ export function isRecoverableTempStartupCancellation(temp, result) {
|
|
|
448
482
|
/** One session lifecycle. `runSupervised` (or a one-shot caller) drives it. */
|
|
449
483
|
export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
450
484
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
485
|
+
const startupTelemetry = new StartupTelemetry(deps.now, line => deps.log(`[${name}] ${line}`));
|
|
486
|
+
startupTelemetry.mark('supervisor_started');
|
|
451
487
|
const temp = opts.temp === true;
|
|
452
488
|
const dir = agentDir(name, temp);
|
|
453
489
|
const configPath = temp ? opts.configPath : resolveConfigPath(dir, opts.configPath);
|
|
@@ -486,14 +522,43 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
486
522
|
deps.log(`[${name}] worklog rotation deferred: concurrent modification detected`);
|
|
487
523
|
else if (rotation.rotated)
|
|
488
524
|
deps.log(`[${name}] worklog rotated: ${rotation.beforeBytes} -> ${rotation.afterBytes} bytes`);
|
|
525
|
+
const durableTask = Boolean(readTempSupervisor(dir)?.taskOwner);
|
|
526
|
+
const retainedManaged = durableTask || Boolean(deps.suspendOnStop && adapter.supportsResume);
|
|
489
527
|
const sidFile = join(dir, '.session-id');
|
|
528
|
+
const backendSidFile = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
|
|
529
|
+
let recovery = retainedManaged ? readManagedRecovery(dir) : undefined;
|
|
530
|
+
// Native Codex shares .session-id with the runner's pre-launch UUID, so file
|
|
531
|
+
// existence alone is not evidence that its first backend thread was created.
|
|
532
|
+
const firstSessionRetry = retainedManaged && existsSync(join(dir, '.booted'))
|
|
533
|
+
&& (!existsSync(backendSidFile) || (role.session === 'codex-app-server' && recovery?.session === 'pending'))
|
|
534
|
+
&& firstManagedSessionMayStart(dir);
|
|
535
|
+
if (retainedManaged && existsSync(join(dir, '.booted'))
|
|
536
|
+
&& (!existsSync(sidFile) || !existsSync(backendSidFile)) && !firstSessionRetry)
|
|
537
|
+
throw Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
490
538
|
if (!existsSync(sidFile))
|
|
491
539
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
492
540
|
const sessionId = readFileSync(sidFile, 'utf8').trim();
|
|
493
541
|
const bootedFile = join(dir, '.booted');
|
|
494
542
|
const exitFile = join(dir, '.exit-status');
|
|
495
543
|
const booted = existsSync(bootedFile);
|
|
496
|
-
|
|
544
|
+
if (durableTask && !adapter.supportsResume)
|
|
545
|
+
throw Error('TASK_HARNESS_RESUME_UNSUPPORTED');
|
|
546
|
+
const mode = booted && adapter.supportsResume && !firstSessionRetry ? 'resume' : 'fresh';
|
|
547
|
+
const requireResume = retainedManaged && mode === 'resume';
|
|
548
|
+
if (retainedManaged && !recovery) {
|
|
549
|
+
// Older admitted sessions have no cursor. Continue once, without replaying
|
|
550
|
+
// their assignment; fresh sessions record intent before the first attempt.
|
|
551
|
+
recovery = { version: 1, session: booted ? 'established' : 'pending', readiness: booted,
|
|
552
|
+
initial: booted ? 'dispatching' : 'pending', active: booted };
|
|
553
|
+
writeManagedRecovery(dir, recovery);
|
|
554
|
+
}
|
|
555
|
+
const recoveryBefore = recovery ? { ...recovery } : undefined;
|
|
556
|
+
const saveRecovery = (update) => {
|
|
557
|
+
if (!recovery)
|
|
558
|
+
return;
|
|
559
|
+
recovery = { ...recovery, ...update };
|
|
560
|
+
writeManagedRecovery(dir, recovery);
|
|
561
|
+
};
|
|
497
562
|
// Stamp EVERY attempt, not just the first. `.booted` used to be written only
|
|
498
563
|
// on the fresh path, so after a restart — including one the supervisor never
|
|
499
564
|
// saw, like an OOM-kill — its mtime still read the original boot and any
|
|
@@ -502,10 +567,22 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
502
567
|
writeFileSync(bootedFile, `${new Date(deps.now()).toISOString()} ${mode}\n`);
|
|
503
568
|
const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
|
|
504
569
|
role = { ...role, monitor: { ...role.monitor, mode: 'fleet' } };
|
|
505
|
-
|
|
570
|
+
// Native policy for Fleet commands run from the harness command sandbox. This
|
|
571
|
+
// is the same step for configured, spawned and task-room agents alike.
|
|
572
|
+
const prep = prepareManagedCliLaunch(role, {
|
|
573
|
+
// A temporary agent has no config path threaded through its detached
|
|
574
|
+
// supervisor; spawn records the one it was created from beside its snapshot.
|
|
575
|
+
stateDir: dir, runCwd, configPath: configPath ?? resolveConfigPath(dir), temp,
|
|
576
|
+
prep: await adapter.prepareSession(role, { stateDir: dir, runCwd }), log: deps.log,
|
|
577
|
+
});
|
|
506
578
|
const sessionBackend = role.session ?? 'acp';
|
|
507
579
|
const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
|
|
508
580
|
let launch = adapter.agentSession.prepareLaunch(role, prep);
|
|
581
|
+
// Resolve the fleet-wide active subscription profile once, now: a later switch
|
|
582
|
+
// never changes this session (the operator restarts agents to move them).
|
|
583
|
+
const subscription = pinSubscriptionForLaunch(role, dir);
|
|
584
|
+
if (subscription?.pin.home)
|
|
585
|
+
deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
|
|
509
586
|
// Preserve the role's existing isolation policy.
|
|
510
587
|
let wrappedArgv = launch.argv;
|
|
511
588
|
if (role.isolation) {
|
|
@@ -516,6 +593,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
516
593
|
launch = { ...launch, argv: runtime.argv };
|
|
517
594
|
const ctx = {
|
|
518
595
|
...isolationContextFor(role), stateDir: dir, runCwd,
|
|
596
|
+
subscriptionHome: subscription?.writablePath,
|
|
519
597
|
runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
|
|
520
598
|
};
|
|
521
599
|
const policy = resolveIsolation(role.isolation, ctx);
|
|
@@ -538,9 +616,16 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
538
616
|
if (rprefix.length)
|
|
539
617
|
wrappedArgv = [...rprefix, ...wrappedArgv];
|
|
540
618
|
}
|
|
619
|
+
startupTelemetry.mark('identity_prepare_started');
|
|
541
620
|
const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
|
|
621
|
+
startupTelemetry.mark('identity_ready');
|
|
622
|
+
let layoutRetiring = false;
|
|
623
|
+
// Self-reminders live and die with this attempt's runtime binding: every exit
|
|
624
|
+
// path below stops the dispatcher before that binding is released.
|
|
625
|
+
let reminderManager;
|
|
626
|
+
let selectedHealth;
|
|
542
627
|
try {
|
|
543
|
-
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
|
|
628
|
+
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
|
|
544
629
|
// Start-stagger: space this launch at least start_stagger_ms after the previous
|
|
545
630
|
// agent launch across the whole host, so a burst of boots (systemd starts every
|
|
546
631
|
// user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
|
|
@@ -556,10 +641,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
556
641
|
}
|
|
557
642
|
}
|
|
558
643
|
// Prime the supervisor mail monitor's notification cursor at the
|
|
559
|
-
// stream tip BEFORE the session launches so no arrival is missed during boot
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
// treated as native (monitor may be undefined on an old role.yaml).
|
|
644
|
+
// stream tip BEFORE the session launches so no arrival is missed during boot.
|
|
645
|
+
// Monitor.run checks the body-free initial unread summary after readiness;
|
|
646
|
+
// managed native hooks do not own backlog delivery.
|
|
563
647
|
let sessionHandle;
|
|
564
648
|
let modelRecovery;
|
|
565
649
|
const resolvedMonitorDeps = monitorDeps(deps, role.env);
|
|
@@ -587,8 +671,15 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
587
671
|
name, identity: role.identity, agentDir: dir, cfg: role.monitor,
|
|
588
672
|
deps: resolvedMonitorDeps,
|
|
589
673
|
}) : null;
|
|
674
|
+
// Injected legacy probes keep their test/embedding contract. Production uses
|
|
675
|
+
// one retained owner for generation and identity health in this process.
|
|
676
|
+
const retainHealth = deps.probeGeneration === defaultProbeGeneration
|
|
677
|
+
&& !deps.identityProbeDeps ? acquireSelectedDaemonHealth : undefined;
|
|
678
|
+
selectedHealth = retainHealth?.(resolvedMonitorDeps.env);
|
|
590
679
|
const daemonObserver = new DaemonGenerationObserver();
|
|
591
|
-
const initialDaemon = daemonObserver.observe(
|
|
680
|
+
const initialDaemon = daemonObserver.observe(selectedHealth
|
|
681
|
+
? (await selectedHealth.sample()).generation
|
|
682
|
+
: await deps.probeGeneration(resolvedMonitorDeps.env));
|
|
592
683
|
let sessionStartedWithoutDaemonBaseline = initialDaemon.kind !== 'baseline';
|
|
593
684
|
if (monitor)
|
|
594
685
|
await monitor.prime({ resetCursor: resetMonitorCursor });
|
|
@@ -599,6 +690,20 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
599
690
|
let unsubscribeRecovery;
|
|
600
691
|
let monitorLoop;
|
|
601
692
|
let sessionStartupComplete = false;
|
|
693
|
+
let startupRecoveryAllowed = false;
|
|
694
|
+
let successfulTurnObserved = false;
|
|
695
|
+
let monitorMayStart = false;
|
|
696
|
+
let monitorStarted = false;
|
|
697
|
+
const startMonitorAfterStartup = () => {
|
|
698
|
+
if (!monitorStarted && monitorMayStart && sessionStartupComplete && !deps.shouldStop?.()) {
|
|
699
|
+
monitorStarted = true;
|
|
700
|
+
monitorLoop = monitor?.run(pid);
|
|
701
|
+
}
|
|
702
|
+
// Reminder wakes follow the same gate as mail wakes: never into a role
|
|
703
|
+
// whose protected startup turn has not succeeded.
|
|
704
|
+
if (monitorMayStart && sessionStartupComplete && !deps.shouldStop?.())
|
|
705
|
+
reminderManager?.start();
|
|
706
|
+
};
|
|
602
707
|
let sessionClosed = false;
|
|
603
708
|
let ownerChannel;
|
|
604
709
|
let ownerBinder;
|
|
@@ -617,23 +722,66 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
617
722
|
if (perms.unattended === 'deny')
|
|
618
723
|
deps.log(`[${name}] permission policy: unattended=deny — with no console attached, ` +
|
|
619
724
|
`permission requests are automatically denied once each (reject_once) and the turn continues`);
|
|
725
|
+
startupTelemetry.mark('harness_starting');
|
|
620
726
|
agentSession = await managedService.runtime.startHarness(() => deps.startAgentSession(adapter.agentSession, {
|
|
621
727
|
managedOurs: managedHarness.ours,
|
|
622
728
|
role, prep,
|
|
623
729
|
launch: { ...launch, argv: wrappedArgv, env: managedHarness.env },
|
|
624
|
-
cwd: runCwd, stateDir: dir, mode, permissions: perms,
|
|
730
|
+
cwd: runCwd, stateDir: dir, mode, requireResume, deferRecoveredPrompts: true, permissions: perms,
|
|
625
731
|
permissionMode: effectivePermissionMode(role), log: deps.log,
|
|
626
732
|
}));
|
|
733
|
+
managedService.setFileDelivery?.(agentSession.sendFileToChat?.bind(agentSession));
|
|
734
|
+
startupTelemetry.mark('harness_ready');
|
|
735
|
+
saveRecovery({ session: 'established' });
|
|
627
736
|
pid = agentSession.pid;
|
|
628
737
|
arbiter = new RoleTurnArbiter(agentSession);
|
|
629
738
|
sessionHandle = arbiter;
|
|
630
|
-
|
|
739
|
+
reminderManager = bindRoleReminders({
|
|
740
|
+
role: name, stateDir: dir, runtime: managedService.setReminders ? managedService.runtime.snapshot : {},
|
|
741
|
+
promptInput: sessionBackendCapabilities(sessionBackend, role.harness).promptInput,
|
|
742
|
+
admitter: arbiter, service: managedService,
|
|
743
|
+
deps: {
|
|
744
|
+
now: deps.now, log: deps.log,
|
|
745
|
+
setTimer: (callback, ms) => setTimeout(callback, ms),
|
|
746
|
+
clearTimer: timer => clearTimeout(timer),
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
const unsubscribeFailure = agentSession.subscribe(event => {
|
|
631
750
|
if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
|
|
632
751
|
return;
|
|
633
752
|
const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
|
|
634
753
|
if (evidence)
|
|
635
754
|
resolvedMonitorDeps.onFailureEvidence?.(evidence);
|
|
636
755
|
});
|
|
756
|
+
const unsubscribeTelemetry = agentSession.subscribeConversation?.(event => startupTelemetry.observe(event));
|
|
757
|
+
const unsubscribeStartup = agentSession.subscribeConversation?.(event => {
|
|
758
|
+
if (event.kind !== 'turn.completed' || !('outcome' in event.payload) || event.payload.outcome !== 'completed')
|
|
759
|
+
return;
|
|
760
|
+
successfulTurnObserved = true;
|
|
761
|
+
if (startupRecoveryAllowed) {
|
|
762
|
+
sessionStartupComplete = true;
|
|
763
|
+
startMonitorAfterStartup();
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
const activePrompts = new Set();
|
|
767
|
+
const unsubscribeContinuity = agentSession.subscribeConversation?.(event => {
|
|
768
|
+
if (!recovery?.readiness)
|
|
769
|
+
return;
|
|
770
|
+
if (event.kind === 'prompt.started') {
|
|
771
|
+
if (event.promptId)
|
|
772
|
+
activePrompts.add(event.promptId);
|
|
773
|
+
saveRecovery({ active: true });
|
|
774
|
+
}
|
|
775
|
+
else if (event.kind === 'turn.completed' && 'outcome' in event.payload) {
|
|
776
|
+
if (event.promptId)
|
|
777
|
+
activePrompts.delete(event.promptId);
|
|
778
|
+
// A shutdown/failed turn needs continuation; a successful terminal turn
|
|
779
|
+
// proves it may stay idle. Never store prompt bodies in this cursor.
|
|
780
|
+
if (event.payload.outcome === 'completed')
|
|
781
|
+
saveRecovery({ active: activePrompts.size > 0 });
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); unsubscribeTelemetry?.(); unsubscribeContinuity?.(); };
|
|
637
785
|
if (role.owner_channel) {
|
|
638
786
|
try {
|
|
639
787
|
ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
|
|
@@ -670,11 +818,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
670
818
|
...(configPath ? { configPath } : {}),
|
|
671
819
|
});
|
|
672
820
|
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
821
|
+
control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
|
|
673
822
|
control.setFleetAuditor(ownerChannel ? {
|
|
674
823
|
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
675
824
|
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
676
825
|
present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
|
|
677
|
-
} : localFleetAuditor(dir, name, deps.log));
|
|
826
|
+
} : localFleetAuditor(dir, name, deps.log, agentSession));
|
|
827
|
+
control.setLayoutControl?.(createLayoutControl({
|
|
828
|
+
agent: name, temporary: temp, standalone: !role.roomMemberStartup,
|
|
829
|
+
runtime: managedService.runtime, session: arbiter,
|
|
830
|
+
stopping: () => Boolean(deps.shouldStop?.()),
|
|
831
|
+
retire: async () => { layoutRetiring = true; await arbiter.close(); },
|
|
832
|
+
}));
|
|
678
833
|
await control.start();
|
|
679
834
|
}
|
|
680
835
|
catch (error) {
|
|
@@ -700,15 +855,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
700
855
|
}
|
|
701
856
|
return event;
|
|
702
857
|
});
|
|
858
|
+
// Recovered prompts may run the managed Fleet CLI immediately. Start them
|
|
859
|
+
// only now that the control socket, audit and spawn handlers are live, and
|
|
860
|
+
// never for a role being stopped: they stay durable for its next start.
|
|
861
|
+
if (!deps.shouldStop?.())
|
|
862
|
+
agentSession.activateRecoveredPrompts?.();
|
|
703
863
|
resolvedMonitorDeps.delivery = {
|
|
704
864
|
// A wake is only delivered when its turn TERMINATES successfully. A
|
|
705
865
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
706
866
|
// the monitor must keep its cursor and try again.
|
|
707
867
|
submit: async (text, options) => {
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
// steer into the live turn instead; after it completes, honor the
|
|
711
|
-
// configured interrupt policy normally.
|
|
868
|
+
// Delivery starts after the protected startup turn succeeds. Honor the
|
|
869
|
+
// configured interrupt policy for subsequent turns.
|
|
712
870
|
const policy = options?.interrupt;
|
|
713
871
|
const interrupt = policy === true && sessionStartupComplete;
|
|
714
872
|
const promptOptions = {
|
|
@@ -716,8 +874,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
716
874
|
...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
|
|
717
875
|
origin: { kind: 'fleet-monitor' },
|
|
718
876
|
};
|
|
719
|
-
//
|
|
720
|
-
// steer rather than waiting on/cancelling the runner-owned first turn.
|
|
877
|
+
// after_tool waits for a safe boundary in subsequent active turns.
|
|
721
878
|
const result = policy === 'after_tool' && sessionStartupComplete
|
|
722
879
|
? await arbiter.submitPromptAfterTool(text, promptOptions)
|
|
723
880
|
: await arbiter.submitPrompt(text, promptOptions);
|
|
@@ -746,17 +903,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
746
903
|
};
|
|
747
904
|
},
|
|
748
905
|
};
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
906
|
+
// The worklog is continuity input only when it records work. It outlives `.booted`,
|
|
907
|
+
// which a rotation to a fresh conversation clears, so the log itself is the evidence.
|
|
908
|
+
const priorWork = worklogRecordsPriorWork(dir);
|
|
909
|
+
const firstPrompt = managedStartupPrompt(dir, mode, priorWork);
|
|
752
910
|
// Wait for the first turn's TERMINAL result. An agent that accepts the
|
|
753
911
|
// startup prompt and then refuses it has not started; logging the role as
|
|
754
|
-
// up would hide a role that never
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
//
|
|
759
|
-
|
|
912
|
+
// up would hide a role that never completed its readiness turn.
|
|
913
|
+
const retainedResume = retainedManaged && mode === 'resume' && recoveryBefore?.readiness === true;
|
|
914
|
+
if (!retainedResume)
|
|
915
|
+
startupTelemetry.mark('startup_submitted');
|
|
916
|
+
// The backend already verified the saved conversation. Resuming a managed
|
|
917
|
+
// member admits new wakes/console input without replaying startup or work.
|
|
918
|
+
const starting = retainedResume
|
|
919
|
+
? Promise.resolve({ accepted: false, succeeded: true, outcome: 'completed', detail: 'retained backend resumed; no readiness prompt submitted' })
|
|
920
|
+
: arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
|
|
921
|
+
// Keep the early cursor baseline; do not deliver wakes into the protected
|
|
922
|
+
// first turn. Steering can pre-empt startup just like cancellation.
|
|
760
923
|
// Owner traffic and agent replies must be observed during a long first
|
|
761
924
|
// turn. Ordinary owner input steers until startup has proved successful.
|
|
762
925
|
if (ownerChannel) {
|
|
@@ -778,6 +941,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
778
941
|
}
|
|
779
942
|
}
|
|
780
943
|
const started = await starting;
|
|
944
|
+
startupTelemetry.finish();
|
|
781
945
|
// A temporary role's first turn can be the active turn when an ours wake
|
|
782
946
|
// needs immediate attention. A typed console/monitor cancellation ends
|
|
783
947
|
// only that turn: the already-live agent session and any queued wake remain
|
|
@@ -816,7 +980,38 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
816
980
|
else if (interruptedForWake)
|
|
817
981
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
818
982
|
+ 'keeping temporary supervisor alive');
|
|
819
|
-
|
|
983
|
+
startupRecoveryAllowed = interruptedForWake;
|
|
984
|
+
sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
|
|
985
|
+
if (started.succeeded && !retainedResume)
|
|
986
|
+
startupTelemetry.mark('readiness_turn_completed');
|
|
987
|
+
if (started.succeeded)
|
|
988
|
+
saveRecovery({ readiness: true });
|
|
989
|
+
// Readiness is now complete. Configured work (or retained continuity) is
|
|
990
|
+
// admitted as a distinct turn; its terminal result must not block console
|
|
991
|
+
// readiness, mail delivery, or supervisor liveness. Empty fresh agents wait
|
|
992
|
+
// for authenticated console input or the monitor's unread/arrival wake.
|
|
993
|
+
const configuredWork = Boolean(role.mission?.trim() || role.roomMemberStartup?.task?.trim() || role.briefing_file?.trim());
|
|
994
|
+
const initialPending = retainedManaged && recoveryBefore?.initial === 'pending';
|
|
995
|
+
const interruptedWork = retainedManaged && (recoveryBefore?.active || recoveryBefore?.initial === 'dispatching');
|
|
996
|
+
const workPrompt = retainedManaged
|
|
997
|
+
? initialPending && configuredWork ? managedTaskPrompt(dir, 'fresh', false)
|
|
998
|
+
: interruptedWork ? 'Fleet supervisor restarted. Continue pending work in the restored conversation; do not redo completed steps. '
|
|
999
|
+
+ (recoveryBefore?.initial === 'dispatching' ? `If you have not yet received your assignment, read ${join(dir, 'briefing.md')} once before acting. ` : '')
|
|
1000
|
+
+ 'Read the continuation log only as needed to recover your next action. If no work remains, end this turn and wait for a concrete task or supervisor wake.' : undefined
|
|
1001
|
+
: configuredWork || booted || priorWork ? managedTaskPrompt(dir, mode, priorWork) : undefined;
|
|
1002
|
+
if (started.succeeded && !deps.shouldStop?.() && workPrompt) {
|
|
1003
|
+
saveRecovery({ initial: 'dispatching', active: true });
|
|
1004
|
+
const queued = await arbiter.queuePrompt(workPrompt, {
|
|
1005
|
+
origin: { kind: 'startup' },
|
|
1006
|
+
});
|
|
1007
|
+
void queued.completion.then(result => {
|
|
1008
|
+
if (result.succeeded)
|
|
1009
|
+
saveRecovery({ initial: 'completed', active: activePrompts.size > 0 });
|
|
1010
|
+
deps.log(`[${name}] configured task turn ${result.outcome}`);
|
|
1011
|
+
}, () => deps.log(`[${name}] configured task turn failed`));
|
|
1012
|
+
}
|
|
1013
|
+
else if (started.succeeded && initialPending && !configuredWork)
|
|
1014
|
+
saveRecovery({ initial: 'completed' });
|
|
820
1015
|
reloadLoopConfig = async () => {
|
|
821
1016
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
822
1017
|
const definitions = nextRole.loops ?? [];
|
|
@@ -873,9 +1068,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
873
1068
|
}
|
|
874
1069
|
}
|
|
875
1070
|
deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1071
|
+
const healthyAt = deps.now();
|
|
1072
|
+
opts.onHealthy?.();
|
|
1073
|
+
// Keep arrivals behind the early prime() cursor; never re-prime at startup.
|
|
1074
|
+
// Recoverable cancellation preserves the supervisor, not monitor readiness.
|
|
1075
|
+
monitorMayStart = true;
|
|
1076
|
+
startMonitorAfterStartup();
|
|
879
1077
|
recoveryController = new RoleRecoveryController({
|
|
880
1078
|
role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
|
|
881
1079
|
log: deps.log,
|
|
@@ -902,6 +1100,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
902
1100
|
}
|
|
903
1101
|
},
|
|
904
1102
|
});
|
|
1103
|
+
let chatIdle;
|
|
1104
|
+
try {
|
|
1105
|
+
if (temp && !role.roomMemberStartup && !readTempSupervisor(dir)?.taskOwner)
|
|
1106
|
+
chatIdle = new TemporaryChatIdle(dir, deps.now());
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
|
|
1110
|
+
}
|
|
905
1111
|
const start = deps.now();
|
|
906
1112
|
let nextLoopReloadAt = deps.now() + 30_000;
|
|
907
1113
|
let nextIdentityPollAt = deps.now();
|
|
@@ -916,7 +1122,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
916
1122
|
const now = deps.now();
|
|
917
1123
|
if (now >= nextDaemonProbeAt) {
|
|
918
1124
|
nextDaemonProbeAt = now + 2_000;
|
|
919
|
-
const observation = daemonObserver.observe(
|
|
1125
|
+
const observation = daemonObserver.observe(selectedHealth
|
|
1126
|
+
? (await selectedHealth.sample()).generation
|
|
1127
|
+
: await deps.probeGeneration(resolvedMonitorDeps.env));
|
|
920
1128
|
if (observation.kind === 'lost')
|
|
921
1129
|
recoveryController.noteLoss(observation.reason);
|
|
922
1130
|
if (observation.kind === 'changed' || observation.kind === 'available'
|
|
@@ -925,7 +1133,21 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
925
1133
|
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
926
1134
|
}
|
|
927
1135
|
}
|
|
1136
|
+
let idleExpired = false;
|
|
1137
|
+
try {
|
|
1138
|
+
idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
|
|
1139
|
+
}
|
|
1140
|
+
catch {
|
|
1141
|
+
chatIdle = undefined;
|
|
1142
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
|
|
1143
|
+
}
|
|
1144
|
+
if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
|
|
1145
|
+
retirementReason = 'idle-timeout';
|
|
1146
|
+
sessionClosed = true;
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
928
1149
|
if (deps.shouldStop?.()) {
|
|
1150
|
+
reminderManager?.stop();
|
|
929
1151
|
if (temp) {
|
|
930
1152
|
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
931
1153
|
deps.log(`[${name}] temporary supervisor retirement requested (${retirementReason})`);
|
|
@@ -936,21 +1158,25 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
936
1158
|
}
|
|
937
1159
|
if (temp && now >= nextIdentityPollAt) {
|
|
938
1160
|
nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
|
|
939
|
-
const presence =
|
|
1161
|
+
const presence = selectedHealth
|
|
1162
|
+
? selectedHealth.presence(role.identity, await selectedHealth.sample())
|
|
1163
|
+
: await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
|
|
1164
|
+
const identityObservedAt = deps.now();
|
|
940
1165
|
if (presence.state === 'present') {
|
|
941
1166
|
identityObserved = true;
|
|
942
1167
|
identityAbsentSince = undefined;
|
|
943
1168
|
}
|
|
944
1169
|
else if (presence.state === 'absent' && identityObserved) {
|
|
945
|
-
identityAbsentSince ??=
|
|
1170
|
+
identityAbsentSince ??= identityObservedAt;
|
|
946
1171
|
// Require a continuous, time-bounded run of authoritative absence.
|
|
947
1172
|
// The first positive observation is the readiness gate: cold harness
|
|
948
1173
|
// starts may spend minutes loading the harness and briefing before the
|
|
949
1174
|
// agent creates/binds its identity, and absence before then is not a
|
|
950
1175
|
// close event. After readiness, debounce a real disappearance.
|
|
951
|
-
if (
|
|
1176
|
+
if (identityObservedAt - identityAbsentSince >= TEMP_IDENTITY_CLOSE_DEBOUNCE_MS) {
|
|
952
1177
|
retirementReason = 'identity-closed';
|
|
953
1178
|
deps.log(`[${name}] temporary identity '${role.identity}' closed; retiring session and supervisor`);
|
|
1179
|
+
reminderManager?.stop();
|
|
954
1180
|
await sessionHandle.close();
|
|
955
1181
|
sessionClosed = true;
|
|
956
1182
|
break;
|
|
@@ -973,6 +1199,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
973
1199
|
}
|
|
974
1200
|
}
|
|
975
1201
|
}
|
|
1202
|
+
monitorMayStart = false;
|
|
1203
|
+
reminderManager?.stop();
|
|
976
1204
|
if (loopManager) {
|
|
977
1205
|
control?.setLoopManager(undefined);
|
|
978
1206
|
await loopManager.stop();
|
|
@@ -1018,11 +1246,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1018
1246
|
const rotate = (why) => {
|
|
1019
1247
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
1020
1248
|
rmSync(bootedFile, { force: true });
|
|
1249
|
+
rmSync(managedRecoveryPath(dir), { force: true });
|
|
1021
1250
|
rotated = true;
|
|
1022
1251
|
deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
|
|
1023
1252
|
};
|
|
1024
|
-
if (!temp && deps.shouldStop?.())
|
|
1025
|
-
deps.log(`[${name}] supervisor
|
|
1253
|
+
if (readTempSupervisor(dir)?.taskOwner || ((!temp || deps.suspendOnStop) && deps.shouldStop?.()))
|
|
1254
|
+
deps.log(`[${name}] supervisor/context retained -> next start RESUMES context`);
|
|
1026
1255
|
else if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
1027
1256
|
|| exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
|
|
1028
1257
|
// This is a deliberate adapter reclamation, not evidence that resume state
|
|
@@ -1053,11 +1282,15 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1053
1282
|
throw new SupervisorRecycleRequiredError();
|
|
1054
1283
|
return {
|
|
1055
1284
|
elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
|
|
1056
|
-
|
|
1285
|
+
healthySecs: (deps.now() - healthyAt) / 1000,
|
|
1286
|
+
...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
|
|
1057
1287
|
};
|
|
1058
1288
|
}
|
|
1059
1289
|
finally {
|
|
1060
|
-
|
|
1290
|
+
selectedHealth?.release();
|
|
1291
|
+
reminderManager?.stop();
|
|
1292
|
+
managedService.setReminders?.(undefined);
|
|
1293
|
+
await managedService.close(layoutRetiring);
|
|
1061
1294
|
}
|
|
1062
1295
|
}
|
|
1063
1296
|
/**
|
|
@@ -1072,11 +1305,13 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1072
1305
|
*/
|
|
1073
1306
|
export async function runSupervised(name, opts = {}, partialDeps = {}, attempt = runOnce) {
|
|
1074
1307
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
1075
|
-
const
|
|
1308
|
+
const taskMember = opts.temp === true;
|
|
1309
|
+
const dir = agentDir(name, taskMember);
|
|
1076
1310
|
mkdirSync(dir, { recursive: true });
|
|
1077
1311
|
let stopping = false;
|
|
1078
1312
|
const requestStop = () => { stopping = true; };
|
|
1079
|
-
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1313
|
+
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1314
|
+
|| (taskMember && requestedTempStopReason(dir) === 'operator-stop');
|
|
1080
1315
|
deps.shouldStop = shouldStop;
|
|
1081
1316
|
const stamp = () => new Date(deps.now()).toISOString();
|
|
1082
1317
|
// Record how the PREVIOUS supervisor process ended before doing anything
|
|
@@ -1104,10 +1339,12 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1104
1339
|
process.on('SIGINT', requestStop);
|
|
1105
1340
|
try {
|
|
1106
1341
|
while (!shouldStop()) {
|
|
1342
|
+
if (taskMember && !taskSupervisorMayRun(name))
|
|
1343
|
+
break;
|
|
1107
1344
|
let ledger = readRestartLedger(dir);
|
|
1108
1345
|
try {
|
|
1109
1346
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1110
|
-
const role = findRole(loadConfig(configPath), name);
|
|
1347
|
+
const role = taskMember ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1111
1348
|
reconcileModelRecovery(dir, role, stamp());
|
|
1112
1349
|
if (modelRecoveryHeld(dir)) {
|
|
1113
1350
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
@@ -1123,20 +1360,74 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1123
1360
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1124
1361
|
continue;
|
|
1125
1362
|
}
|
|
1363
|
+
if (attempt === runOnce && taskMember) {
|
|
1364
|
+
// Boot-time daemon unavailability must not consume the harness crash budget.
|
|
1365
|
+
const { waitForRoleDaemon } = await import('./startup-readiness.js');
|
|
1366
|
+
await waitForRoleDaemon(name, undefined, {}, true);
|
|
1367
|
+
}
|
|
1368
|
+
if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
|
|
1369
|
+
break;
|
|
1370
|
+
if (attempt === runOnce && taskMember) {
|
|
1371
|
+
// Cowork can come up after the daemon on boot. Wait read-only so a
|
|
1372
|
+
// gateway still starting its upstream does not spend the failure budget.
|
|
1373
|
+
const { waitForTaskRoomManagement } = await import('./startup-readiness.js');
|
|
1374
|
+
await waitForTaskRoomManagement(name, { shouldStop });
|
|
1375
|
+
}
|
|
1376
|
+
if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
|
|
1377
|
+
break;
|
|
1378
|
+
const fastFailSecs = taskMember ? tempFastFailSecs(name) : fastFailSecsFor(name, opts.configPath);
|
|
1379
|
+
// The fast-fail boundary starts a recovery episode; it must not also be
|
|
1380
|
+
// the boundary that declares recovery successful. Otherwise alternating
|
|
1381
|
+
// 19s and 20s deaths erase one another forever. Require the configured
|
|
1382
|
+
// number of fast-fail windows to survive before closing an active streak.
|
|
1383
|
+
// This hysteresis stays adapter-relative (100s for the current 20s/5-attempt
|
|
1384
|
+
// policy) and still lets a genuinely sustained session reset the breaker.
|
|
1385
|
+
const stableRecoverySecs = fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1386
|
+
// A session that stays up that long has recovered, even if it never
|
|
1387
|
+
// returns: a host reset or OOM-kill ends it without reaching the
|
|
1388
|
+
// post-attempt reset below, and a stale streak would then hold the role
|
|
1389
|
+
// down after a few transient boot failures spread over days.
|
|
1390
|
+
const streakAtStart = ledger;
|
|
1391
|
+
const clearSustainedStreak = () => {
|
|
1392
|
+
if (streakAtStart.consecutiveImmediateFailures === 0)
|
|
1393
|
+
return;
|
|
1394
|
+
const current = readRestartLedger(dir);
|
|
1395
|
+
// Never overwrite an operator reset, an open circuit or a successor's state.
|
|
1396
|
+
if (current.circuit === 'open' || current.updatedAt !== streakAtStart.updatedAt
|
|
1397
|
+
|| current.consecutiveImmediateFailures !== streakAtStart.consecutiveImmediateFailures
|
|
1398
|
+
|| readRunMarker(dir)?.startedAt !== startedAt)
|
|
1399
|
+
return;
|
|
1400
|
+
writeRestartLedger(dir, {
|
|
1401
|
+
...emptyLedger(),
|
|
1402
|
+
...carriedForward(current),
|
|
1403
|
+
lastReason: `sustained healthy session (${stableRecoverySecs}s)`,
|
|
1404
|
+
updatedAt: stamp(),
|
|
1405
|
+
});
|
|
1406
|
+
deps.log(`[${name}] healthy for ${stableRecoverySecs}s; cleared `
|
|
1407
|
+
+ `${current.consecutiveImmediateFailures} earlier immediate failure(s)`);
|
|
1408
|
+
};
|
|
1409
|
+
let cancelStabilityTimer;
|
|
1410
|
+
const onHealthy = () => {
|
|
1411
|
+
if (cancelStabilityTimer || streakAtStart.consecutiveImmediateFailures === 0)
|
|
1412
|
+
return;
|
|
1413
|
+
cancelStabilityTimer = (deps.setTimer ?? unrefTimer)(clearSustainedStreak, stableRecoverySecs * 1000);
|
|
1414
|
+
};
|
|
1126
1415
|
let result;
|
|
1127
1416
|
try {
|
|
1128
1417
|
// Service-manager boot and automatic retry bypass the operator-facing
|
|
1129
1418
|
// up/restart commands. Reconcile again immediately before every real
|
|
1130
1419
|
// permanent harness attempt; the operation is idempotent and releases its
|
|
1131
1420
|
// provisioning lease before the agent or owner channel binds.
|
|
1132
|
-
if (attempt === runOnce) {
|
|
1421
|
+
if (attempt === runOnce && !taskMember) {
|
|
1133
1422
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1134
1423
|
const role = findRole(loadConfig(configPath), name);
|
|
1135
1424
|
await reconcilePermanentRoleIdentities(role, undefined, deps.log);
|
|
1136
1425
|
}
|
|
1137
|
-
result = await attempt(name, { configPath: opts.configPath,
|
|
1426
|
+
result = await attempt(name, { configPath: opts.configPath, ...(taskMember ? { temp: true } : {}),
|
|
1427
|
+
allowResumeRotation: taskMember ? false : !ledger.resumeDiscarded, onHealthy }, deps);
|
|
1138
1428
|
}
|
|
1139
1429
|
catch (e) {
|
|
1430
|
+
cancelStabilityTimer?.();
|
|
1140
1431
|
if (e instanceof SupervisorRecycleRequiredError) {
|
|
1141
1432
|
writeRestartLedger(dir, {
|
|
1142
1433
|
...ledger,
|
|
@@ -1156,8 +1447,14 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1156
1447
|
mode: 'fresh',
|
|
1157
1448
|
};
|
|
1158
1449
|
}
|
|
1159
|
-
|
|
1450
|
+
cancelStabilityTimer?.();
|
|
1451
|
+
if (stopping || (taskMember && (shouldStop() || result.retirementReason))) {
|
|
1452
|
+
// An orderly stop of a session that was healthy long enough is a
|
|
1453
|
+
// recovery too; only the timer's race with the stop is decided here.
|
|
1454
|
+
if ((result.healthySecs ?? 0) >= stableRecoverySecs)
|
|
1455
|
+
clearSustainedStreak();
|
|
1160
1456
|
break;
|
|
1457
|
+
}
|
|
1161
1458
|
// Re-read: the attempt itself may have taken minutes, and an operator may
|
|
1162
1459
|
// have reset the ledger meanwhile.
|
|
1163
1460
|
ledger = readRestartLedger(dir);
|
|
@@ -1174,14 +1471,6 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1174
1471
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1175
1472
|
continue;
|
|
1176
1473
|
}
|
|
1177
|
-
const fastFailSecs = fastFailSecsFor(name, opts.configPath);
|
|
1178
|
-
// The fast-fail boundary starts a recovery episode; it must not also be
|
|
1179
|
-
// the boundary that declares recovery successful. Otherwise alternating
|
|
1180
|
-
// 19s and 20s deaths erase one another forever. Require the configured
|
|
1181
|
-
// number of fast-fail windows to survive before closing an active streak.
|
|
1182
|
-
// This hysteresis stays adapter-relative (100s for the current 20s/5-attempt
|
|
1183
|
-
// policy) and still lets a genuinely sustained session reset the breaker.
|
|
1184
|
-
const stableRecoverySecs = fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1185
1474
|
const recoveryFailed = result.elapsedSecs < fastFailSecs
|
|
1186
1475
|
|| (ledger.consecutiveImmediateFailures > 0 && result.elapsedSecs < stableRecoverySecs);
|
|
1187
1476
|
if (!recoveryFailed) {
|
|
@@ -1211,7 +1500,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1211
1500
|
next.nextDelayMs = 0;
|
|
1212
1501
|
writeRestartLedger(dir, next);
|
|
1213
1502
|
deps.log(`[${name}] HELD DOWN after ${failures} immediate failures at ${next.openedAt} — ` +
|
|
1214
|
-
`${reason};
|
|
1503
|
+
`${reason}; recover with: ${taskMember ? `ours-fleet task recover-members ${readTempSupervisor(dir).taskOwner.taskId}` : `ours-fleet restart ${name}`}`);
|
|
1215
1504
|
continue;
|
|
1216
1505
|
}
|
|
1217
1506
|
writeRestartLedger(dir, next);
|
|
@@ -1226,7 +1515,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1226
1515
|
process.off('SIGINT', requestStop);
|
|
1227
1516
|
// An orderly shutdown clears the marker; an unhandled signal or OOM-kill
|
|
1228
1517
|
// leaves it so the successor can identify an abrupt termination.
|
|
1229
|
-
if (shouldStop())
|
|
1518
|
+
if (!taskMember && shouldStop())
|
|
1230
1519
|
await deps.releaseAgentOurs(findRole(loadConfig(opts.configPath), name));
|
|
1231
1520
|
releaseSupervisorRun(dir);
|
|
1232
1521
|
}
|
|
@@ -1245,9 +1534,67 @@ function fastFailSecsFor(name, configPath) {
|
|
|
1245
1534
|
return 20;
|
|
1246
1535
|
}
|
|
1247
1536
|
}
|
|
1537
|
+
function tempFastFailSecs(name) {
|
|
1538
|
+
try {
|
|
1539
|
+
return getAdapter(loadTempRole(name).harness).exitPolicy.fastFailSecs;
|
|
1540
|
+
}
|
|
1541
|
+
catch {
|
|
1542
|
+
return 20;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1248
1545
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
1546
|
+
/** The parent uses the same adapter-relative sustained-uptime reset boundary. */
|
|
1547
|
+
export function stableSupervisorWindow(name, temp, configPath) {
|
|
1548
|
+
try {
|
|
1549
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1550
|
+
return getAdapter(role.harness).exitPolicy.fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1551
|
+
}
|
|
1552
|
+
catch {
|
|
1553
|
+
return 20 * RESTART_FAIL_THRESHOLD;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1249
1556
|
export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
1250
1557
|
const dir = agentDir(name, true);
|
|
1558
|
+
const metadata = readTempSupervisor(dir);
|
|
1559
|
+
// Non-resumable standalone temporaries retain their prior finite lifecycle:
|
|
1560
|
+
// a parent stop retires them rather than promising retained conversation.
|
|
1561
|
+
if (!metadata?.taskOwner && deps.suspendOnStop && !getAdapter(loadTempRole(name).harness).supportsResume)
|
|
1562
|
+
deps = { ...deps, suspendOnStop: false };
|
|
1563
|
+
if (!metadata?.taskOwner) {
|
|
1564
|
+
const role = loadTempRole(name);
|
|
1565
|
+
const provenance = readProvenance(dir);
|
|
1566
|
+
if (role.roomMemberStartup?.task_id
|
|
1567
|
+
|| (name.startsWith('layout-') && provenance?.creationActionId?.startsWith('task-')))
|
|
1568
|
+
throw Error('TASK_SUPERVISOR_OWNER_MISSING');
|
|
1569
|
+
}
|
|
1570
|
+
if (metadata?.taskOwner) {
|
|
1571
|
+
// Serialize the entire process lifetime before metadata/session writes.
|
|
1572
|
+
const lease = await acquireOwnerBinderLease(join(dir, '.task-supervisor'), 'task-supervisor', name);
|
|
1573
|
+
let ownershipVerified = false;
|
|
1574
|
+
try {
|
|
1575
|
+
const mayRun = taskSupervisorMayRun(name);
|
|
1576
|
+
ownershipVerified = true;
|
|
1577
|
+
if (!mayRun)
|
|
1578
|
+
return;
|
|
1579
|
+
await markTempSupervisorActive(dir);
|
|
1580
|
+
await runSupervised(name, { temp: true }, deps, attempt);
|
|
1581
|
+
}
|
|
1582
|
+
finally {
|
|
1583
|
+
try {
|
|
1584
|
+
if (ownershipVerified && requestedTempStopReason(dir)) {
|
|
1585
|
+
// Parent shutdown suspends; an explicit stop retires the exact saved
|
|
1586
|
+
// owner even when no harness attempt ran (for example held circuits).
|
|
1587
|
+
await (deps.releaseAgentOurs ?? releaseManagedAgent)(loadTempRole(name), taskMemberRetirementProof(name));
|
|
1588
|
+
const archived = archiveTempState(name, 'operator-stop', 'retired', 'explicit task member retirement');
|
|
1589
|
+
deps.log?.(`[${name}] task member retired: operator-stop${archived ? `; evidence archived at ${archived}` : ''}`);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
finally {
|
|
1593
|
+
lease.release();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1251
1598
|
await markTempSupervisorActive(dir);
|
|
1252
1599
|
let signal;
|
|
1253
1600
|
const onTerm = () => { signal = 'SIGTERM'; };
|
|
@@ -1274,7 +1621,8 @@ export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
|
1274
1621
|
process.off('SIGTERM', onTerm);
|
|
1275
1622
|
process.off('SIGINT', onInt);
|
|
1276
1623
|
// A recycle replaces the supervisor process, not the logical temporary agent.
|
|
1277
|
-
if (!(
|
|
1624
|
+
if (!((signal || deps.shouldStop?.()) && deps.suspendOnStop && !requestedTempStopReason(dir))
|
|
1625
|
+
&& (!(failure instanceof SupervisorRecycleRequiredError) || signal || deps.shouldStop?.())) {
|
|
1278
1626
|
const requested = requestedTempStopReason(dir);
|
|
1279
1627
|
const reason = requested
|
|
1280
1628
|
?? result?.retirementReason
|