@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/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
20
|
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } 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 {
|
|
@@ -75,12 +86,16 @@ const defaultDeps = () => ({
|
|
|
75
86
|
});
|
|
76
87
|
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
77
88
|
const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
|
|
78
|
-
function localFleetAuditor(stateDir, caller, log) {
|
|
89
|
+
export function localFleetAuditor(stateDir, caller, log, session) {
|
|
90
|
+
const bindings = new Map();
|
|
79
91
|
const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
|
|
80
92
|
return {
|
|
81
93
|
async begin(requestId, argv) {
|
|
82
94
|
let attempt = store.begin(requestId, caller, argv);
|
|
83
95
|
if (attempt.invocation === 'sending') {
|
|
96
|
+
const binding = session.taskNoticeBinding?.();
|
|
97
|
+
if (binding)
|
|
98
|
+
bindings.set(attempt.correlationId, binding);
|
|
84
99
|
log(`[${caller}] fleet proxy command ${attempt.correlationId} `
|
|
85
100
|
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
86
101
|
attempt = store.invocation(attempt.correlationId, caller, 'delivered');
|
|
@@ -94,6 +109,19 @@ function localFleetAuditor(stateDir, caller, log) {
|
|
|
94
109
|
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
95
110
|
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
96
111
|
});
|
|
112
|
+
const binding = bindings.get(attempt.correlationId);
|
|
113
|
+
if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
|
|
114
|
+
for (const p of attempt.outcome.presentations ?? [])
|
|
115
|
+
if (p.kind === 'task' && p.operation === 'create') {
|
|
116
|
+
try {
|
|
117
|
+
session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
log(`[${caller}] task-created conversation notice could not be persisted`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
bindings.delete(attempt.correlationId);
|
|
124
|
+
}
|
|
97
125
|
if (attempt.outcome?.delivery === 'sending')
|
|
98
126
|
attempt = store.outcome(attempt.correlationId, caller, 'delivered');
|
|
99
127
|
return attempt;
|
|
@@ -300,6 +328,11 @@ const carriedForward = (previous) => ({
|
|
|
300
328
|
...(previous.abruptTerminations ? { abruptTerminations: previous.abruptTerminations } : {}),
|
|
301
329
|
...(previous.supervisorStartedAt ? { supervisorStartedAt: previous.supervisorStartedAt } : {}),
|
|
302
330
|
});
|
|
331
|
+
function unrefTimer(fn, ms) {
|
|
332
|
+
const timer = setTimeout(fn, ms);
|
|
333
|
+
timer.unref?.();
|
|
334
|
+
return () => clearTimeout(timer);
|
|
335
|
+
}
|
|
303
336
|
/** Bounded exponential backoff for the nth consecutive immediate failure. */
|
|
304
337
|
export function backoffFor(consecutiveFailures) {
|
|
305
338
|
if (consecutiveFailures <= 0)
|
|
@@ -414,7 +447,7 @@ export function loadTempRole(name) {
|
|
|
414
447
|
// Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
|
|
415
448
|
// with no monitor block keeps the historical native/no-supervisor behavior.
|
|
416
449
|
if (role.monitor)
|
|
417
|
-
role.monitor =
|
|
450
|
+
role.monitor = resolveSnapshotMonitor(role.monitor);
|
|
418
451
|
role.__temp = true;
|
|
419
452
|
return role;
|
|
420
453
|
}
|
|
@@ -427,7 +460,7 @@ export function loadTempRole(name) {
|
|
|
427
460
|
* with "no such role". An empty/missing marker means "use the default", same as
|
|
428
461
|
* no -c was ever given.
|
|
429
462
|
*/
|
|
430
|
-
function resolveConfigPath(dir, explicit) {
|
|
463
|
+
export function resolveConfigPath(dir, explicit) {
|
|
431
464
|
if (explicit)
|
|
432
465
|
return explicit;
|
|
433
466
|
const marker = join(dir, '.config-path');
|
|
@@ -448,6 +481,8 @@ export function isRecoverableTempStartupCancellation(temp, result) {
|
|
|
448
481
|
/** One session lifecycle. `runSupervised` (or a one-shot caller) drives it. */
|
|
449
482
|
export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
450
483
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
484
|
+
const startupTelemetry = new StartupTelemetry(deps.now, line => deps.log(`[${name}] ${line}`));
|
|
485
|
+
startupTelemetry.mark('supervisor_started');
|
|
451
486
|
const temp = opts.temp === true;
|
|
452
487
|
const dir = agentDir(name, temp);
|
|
453
488
|
const configPath = temp ? opts.configPath : resolveConfigPath(dir, opts.configPath);
|
|
@@ -486,14 +521,43 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
486
521
|
deps.log(`[${name}] worklog rotation deferred: concurrent modification detected`);
|
|
487
522
|
else if (rotation.rotated)
|
|
488
523
|
deps.log(`[${name}] worklog rotated: ${rotation.beforeBytes} -> ${rotation.afterBytes} bytes`);
|
|
524
|
+
const durableTask = Boolean(readTempSupervisor(dir)?.taskOwner);
|
|
525
|
+
const retainedManaged = durableTask || Boolean(deps.suspendOnStop && adapter.supportsResume);
|
|
489
526
|
const sidFile = join(dir, '.session-id');
|
|
527
|
+
const backendSidFile = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
|
|
528
|
+
let recovery = retainedManaged ? readManagedRecovery(dir) : undefined;
|
|
529
|
+
// Native Codex shares .session-id with the runner's pre-launch UUID, so file
|
|
530
|
+
// existence alone is not evidence that its first backend thread was created.
|
|
531
|
+
const firstSessionRetry = retainedManaged && existsSync(join(dir, '.booted'))
|
|
532
|
+
&& (!existsSync(backendSidFile) || (role.session === 'codex-app-server' && recovery?.session === 'pending'))
|
|
533
|
+
&& firstManagedSessionMayStart(dir);
|
|
534
|
+
if (retainedManaged && existsSync(join(dir, '.booted'))
|
|
535
|
+
&& (!existsSync(sidFile) || !existsSync(backendSidFile)) && !firstSessionRetry)
|
|
536
|
+
throw Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
490
537
|
if (!existsSync(sidFile))
|
|
491
538
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
492
539
|
const sessionId = readFileSync(sidFile, 'utf8').trim();
|
|
493
540
|
const bootedFile = join(dir, '.booted');
|
|
494
541
|
const exitFile = join(dir, '.exit-status');
|
|
495
542
|
const booted = existsSync(bootedFile);
|
|
496
|
-
|
|
543
|
+
if (durableTask && !adapter.supportsResume)
|
|
544
|
+
throw Error('TASK_HARNESS_RESUME_UNSUPPORTED');
|
|
545
|
+
const mode = booted && adapter.supportsResume && !firstSessionRetry ? 'resume' : 'fresh';
|
|
546
|
+
const requireResume = retainedManaged && mode === 'resume';
|
|
547
|
+
if (retainedManaged && !recovery) {
|
|
548
|
+
// Older admitted sessions have no cursor. Continue once, without replaying
|
|
549
|
+
// their assignment; fresh sessions record intent before the first attempt.
|
|
550
|
+
recovery = { version: 1, session: booted ? 'established' : 'pending', readiness: booted,
|
|
551
|
+
initial: booted ? 'dispatching' : 'pending', active: booted };
|
|
552
|
+
writeManagedRecovery(dir, recovery);
|
|
553
|
+
}
|
|
554
|
+
const recoveryBefore = recovery ? { ...recovery } : undefined;
|
|
555
|
+
const saveRecovery = (update) => {
|
|
556
|
+
if (!recovery)
|
|
557
|
+
return;
|
|
558
|
+
recovery = { ...recovery, ...update };
|
|
559
|
+
writeManagedRecovery(dir, recovery);
|
|
560
|
+
};
|
|
497
561
|
// Stamp EVERY attempt, not just the first. `.booted` used to be written only
|
|
498
562
|
// on the fresh path, so after a restart — including one the supervisor never
|
|
499
563
|
// saw, like an OOM-kill — its mtime still read the original boot and any
|
|
@@ -502,10 +566,22 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
502
566
|
writeFileSync(bootedFile, `${new Date(deps.now()).toISOString()} ${mode}\n`);
|
|
503
567
|
const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
|
|
504
568
|
role = { ...role, monitor: { ...role.monitor, mode: 'fleet' } };
|
|
505
|
-
|
|
569
|
+
// Native policy for Fleet commands run from the harness command sandbox. This
|
|
570
|
+
// is the same step for configured, spawned and task-room agents alike.
|
|
571
|
+
const prep = prepareManagedCliLaunch(role, {
|
|
572
|
+
// A temporary agent has no config path threaded through its detached
|
|
573
|
+
// supervisor; spawn records the one it was created from beside its snapshot.
|
|
574
|
+
stateDir: dir, runCwd, configPath: configPath ?? resolveConfigPath(dir), temp,
|
|
575
|
+
prep: await adapter.prepareSession(role, { stateDir: dir, runCwd }), log: deps.log,
|
|
576
|
+
});
|
|
506
577
|
const sessionBackend = role.session ?? 'acp';
|
|
507
578
|
const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
|
|
508
579
|
let launch = adapter.agentSession.prepareLaunch(role, prep);
|
|
580
|
+
// Resolve the fleet-wide active subscription profile once, now: a later switch
|
|
581
|
+
// never changes this session (the operator restarts agents to move them).
|
|
582
|
+
const subscription = pinSubscriptionForLaunch(role, dir);
|
|
583
|
+
if (subscription?.pin.home)
|
|
584
|
+
deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
|
|
509
585
|
// Preserve the role's existing isolation policy.
|
|
510
586
|
let wrappedArgv = launch.argv;
|
|
511
587
|
if (role.isolation) {
|
|
@@ -516,6 +592,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
516
592
|
launch = { ...launch, argv: runtime.argv };
|
|
517
593
|
const ctx = {
|
|
518
594
|
...isolationContextFor(role), stateDir: dir, runCwd,
|
|
595
|
+
subscriptionHome: subscription?.writablePath,
|
|
519
596
|
runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
|
|
520
597
|
};
|
|
521
598
|
const policy = resolveIsolation(role.isolation, ctx);
|
|
@@ -538,9 +615,15 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
538
615
|
if (rprefix.length)
|
|
539
616
|
wrappedArgv = [...rprefix, ...wrappedArgv];
|
|
540
617
|
}
|
|
618
|
+
startupTelemetry.mark('identity_prepare_started');
|
|
541
619
|
const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
|
|
620
|
+
startupTelemetry.mark('identity_ready');
|
|
621
|
+
let layoutRetiring = false;
|
|
622
|
+
// Self-reminders live and die with this attempt's runtime binding: every exit
|
|
623
|
+
// path below stops the dispatcher before that binding is released.
|
|
624
|
+
let reminderManager;
|
|
542
625
|
try {
|
|
543
|
-
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
|
|
626
|
+
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
|
|
544
627
|
// Start-stagger: space this launch at least start_stagger_ms after the previous
|
|
545
628
|
// agent launch across the whole host, so a burst of boots (systemd starts every
|
|
546
629
|
// user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
|
|
@@ -556,10 +639,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
556
639
|
}
|
|
557
640
|
}
|
|
558
641
|
// 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).
|
|
642
|
+
// stream tip BEFORE the session launches so no arrival is missed during boot.
|
|
643
|
+
// Monitor.run checks the body-free initial unread summary after readiness;
|
|
644
|
+
// managed native hooks do not own backlog delivery.
|
|
563
645
|
let sessionHandle;
|
|
564
646
|
let modelRecovery;
|
|
565
647
|
const resolvedMonitorDeps = monitorDeps(deps, role.env);
|
|
@@ -599,6 +681,20 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
599
681
|
let unsubscribeRecovery;
|
|
600
682
|
let monitorLoop;
|
|
601
683
|
let sessionStartupComplete = false;
|
|
684
|
+
let startupRecoveryAllowed = false;
|
|
685
|
+
let successfulTurnObserved = false;
|
|
686
|
+
let monitorMayStart = false;
|
|
687
|
+
let monitorStarted = false;
|
|
688
|
+
const startMonitorAfterStartup = () => {
|
|
689
|
+
if (!monitorStarted && monitorMayStart && sessionStartupComplete && !deps.shouldStop?.()) {
|
|
690
|
+
monitorStarted = true;
|
|
691
|
+
monitorLoop = monitor?.run(pid);
|
|
692
|
+
}
|
|
693
|
+
// Reminder wakes follow the same gate as mail wakes: never into a role
|
|
694
|
+
// whose protected startup turn has not succeeded.
|
|
695
|
+
if (monitorMayStart && sessionStartupComplete && !deps.shouldStop?.())
|
|
696
|
+
reminderManager?.start();
|
|
697
|
+
};
|
|
602
698
|
let sessionClosed = false;
|
|
603
699
|
let ownerChannel;
|
|
604
700
|
let ownerBinder;
|
|
@@ -617,23 +713,66 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
617
713
|
if (perms.unattended === 'deny')
|
|
618
714
|
deps.log(`[${name}] permission policy: unattended=deny — with no console attached, ` +
|
|
619
715
|
`permission requests are automatically denied once each (reject_once) and the turn continues`);
|
|
716
|
+
startupTelemetry.mark('harness_starting');
|
|
620
717
|
agentSession = await managedService.runtime.startHarness(() => deps.startAgentSession(adapter.agentSession, {
|
|
621
718
|
managedOurs: managedHarness.ours,
|
|
622
719
|
role, prep,
|
|
623
720
|
launch: { ...launch, argv: wrappedArgv, env: managedHarness.env },
|
|
624
|
-
cwd: runCwd, stateDir: dir, mode, permissions: perms,
|
|
721
|
+
cwd: runCwd, stateDir: dir, mode, requireResume, deferRecoveredPrompts: true, permissions: perms,
|
|
625
722
|
permissionMode: effectivePermissionMode(role), log: deps.log,
|
|
626
723
|
}));
|
|
724
|
+
managedService.setFileDelivery?.(agentSession.sendFileToChat?.bind(agentSession));
|
|
725
|
+
startupTelemetry.mark('harness_ready');
|
|
726
|
+
saveRecovery({ session: 'established' });
|
|
627
727
|
pid = agentSession.pid;
|
|
628
728
|
arbiter = new RoleTurnArbiter(agentSession);
|
|
629
729
|
sessionHandle = arbiter;
|
|
630
|
-
|
|
730
|
+
reminderManager = bindRoleReminders({
|
|
731
|
+
role: name, stateDir: dir, runtime: managedService.setReminders ? managedService.runtime.snapshot : {},
|
|
732
|
+
promptInput: sessionBackendCapabilities(sessionBackend, role.harness).promptInput,
|
|
733
|
+
admitter: arbiter, service: managedService,
|
|
734
|
+
deps: {
|
|
735
|
+
now: deps.now, log: deps.log,
|
|
736
|
+
setTimer: (callback, ms) => setTimeout(callback, ms),
|
|
737
|
+
clearTimer: timer => clearTimeout(timer),
|
|
738
|
+
},
|
|
739
|
+
});
|
|
740
|
+
const unsubscribeFailure = agentSession.subscribe(event => {
|
|
631
741
|
if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
|
|
632
742
|
return;
|
|
633
743
|
const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
|
|
634
744
|
if (evidence)
|
|
635
745
|
resolvedMonitorDeps.onFailureEvidence?.(evidence);
|
|
636
746
|
});
|
|
747
|
+
const unsubscribeTelemetry = agentSession.subscribeConversation?.(event => startupTelemetry.observe(event));
|
|
748
|
+
const unsubscribeStartup = agentSession.subscribeConversation?.(event => {
|
|
749
|
+
if (event.kind !== 'turn.completed' || !('outcome' in event.payload) || event.payload.outcome !== 'completed')
|
|
750
|
+
return;
|
|
751
|
+
successfulTurnObserved = true;
|
|
752
|
+
if (startupRecoveryAllowed) {
|
|
753
|
+
sessionStartupComplete = true;
|
|
754
|
+
startMonitorAfterStartup();
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
const activePrompts = new Set();
|
|
758
|
+
const unsubscribeContinuity = agentSession.subscribeConversation?.(event => {
|
|
759
|
+
if (!recovery?.readiness)
|
|
760
|
+
return;
|
|
761
|
+
if (event.kind === 'prompt.started') {
|
|
762
|
+
if (event.promptId)
|
|
763
|
+
activePrompts.add(event.promptId);
|
|
764
|
+
saveRecovery({ active: true });
|
|
765
|
+
}
|
|
766
|
+
else if (event.kind === 'turn.completed' && 'outcome' in event.payload) {
|
|
767
|
+
if (event.promptId)
|
|
768
|
+
activePrompts.delete(event.promptId);
|
|
769
|
+
// A shutdown/failed turn needs continuation; a successful terminal turn
|
|
770
|
+
// proves it may stay idle. Never store prompt bodies in this cursor.
|
|
771
|
+
if (event.payload.outcome === 'completed')
|
|
772
|
+
saveRecovery({ active: activePrompts.size > 0 });
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); unsubscribeTelemetry?.(); unsubscribeContinuity?.(); };
|
|
637
776
|
if (role.owner_channel) {
|
|
638
777
|
try {
|
|
639
778
|
ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
|
|
@@ -670,11 +809,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
670
809
|
...(configPath ? { configPath } : {}),
|
|
671
810
|
});
|
|
672
811
|
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
812
|
+
control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
|
|
673
813
|
control.setFleetAuditor(ownerChannel ? {
|
|
674
814
|
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
675
815
|
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
676
816
|
present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
|
|
677
|
-
} : localFleetAuditor(dir, name, deps.log));
|
|
817
|
+
} : localFleetAuditor(dir, name, deps.log, agentSession));
|
|
818
|
+
control.setLayoutControl?.(createLayoutControl({
|
|
819
|
+
agent: name, temporary: temp, standalone: !role.roomMemberStartup,
|
|
820
|
+
runtime: managedService.runtime, session: arbiter,
|
|
821
|
+
stopping: () => Boolean(deps.shouldStop?.()),
|
|
822
|
+
retire: async () => { layoutRetiring = true; await arbiter.close(); },
|
|
823
|
+
}));
|
|
678
824
|
await control.start();
|
|
679
825
|
}
|
|
680
826
|
catch (error) {
|
|
@@ -700,15 +846,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
700
846
|
}
|
|
701
847
|
return event;
|
|
702
848
|
});
|
|
849
|
+
// Recovered prompts may run the managed Fleet CLI immediately. Start them
|
|
850
|
+
// only now that the control socket, audit and spawn handlers are live, and
|
|
851
|
+
// never for a role being stopped: they stay durable for its next start.
|
|
852
|
+
if (!deps.shouldStop?.())
|
|
853
|
+
agentSession.activateRecoveredPrompts?.();
|
|
703
854
|
resolvedMonitorDeps.delivery = {
|
|
704
855
|
// A wake is only delivered when its turn TERMINATES successfully. A
|
|
705
856
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
706
857
|
// the monitor must keep its cursor and try again.
|
|
707
858
|
submit: async (text, options) => {
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
// steer into the live turn instead; after it completes, honor the
|
|
711
|
-
// configured interrupt policy normally.
|
|
859
|
+
// Delivery starts after the protected startup turn succeeds. Honor the
|
|
860
|
+
// configured interrupt policy for subsequent turns.
|
|
712
861
|
const policy = options?.interrupt;
|
|
713
862
|
const interrupt = policy === true && sessionStartupComplete;
|
|
714
863
|
const promptOptions = {
|
|
@@ -716,8 +865,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
716
865
|
...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
|
|
717
866
|
origin: { kind: 'fleet-monitor' },
|
|
718
867
|
};
|
|
719
|
-
//
|
|
720
|
-
// steer rather than waiting on/cancelling the runner-owned first turn.
|
|
868
|
+
// after_tool waits for a safe boundary in subsequent active turns.
|
|
721
869
|
const result = policy === 'after_tool' && sessionStartupComplete
|
|
722
870
|
? await arbiter.submitPromptAfterTool(text, promptOptions)
|
|
723
871
|
: await arbiter.submitPrompt(text, promptOptions);
|
|
@@ -746,17 +894,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
746
894
|
};
|
|
747
895
|
},
|
|
748
896
|
};
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
897
|
+
// The worklog is continuity input only when it records work. It outlives `.booted`,
|
|
898
|
+
// which a rotation to a fresh conversation clears, so the log itself is the evidence.
|
|
899
|
+
const priorWork = worklogRecordsPriorWork(dir);
|
|
900
|
+
const firstPrompt = managedStartupPrompt(dir, mode, priorWork);
|
|
752
901
|
// Wait for the first turn's TERMINAL result. An agent that accepts the
|
|
753
902
|
// 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
|
-
|
|
903
|
+
// up would hide a role that never completed its readiness turn.
|
|
904
|
+
const retainedResume = retainedManaged && mode === 'resume' && recoveryBefore?.readiness === true;
|
|
905
|
+
if (!retainedResume)
|
|
906
|
+
startupTelemetry.mark('startup_submitted');
|
|
907
|
+
// The backend already verified the saved conversation. Resuming a managed
|
|
908
|
+
// member admits new wakes/console input without replaying startup or work.
|
|
909
|
+
const starting = retainedResume
|
|
910
|
+
? Promise.resolve({ accepted: false, succeeded: true, outcome: 'completed', detail: 'retained backend resumed; no readiness prompt submitted' })
|
|
911
|
+
: arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
|
|
912
|
+
// Keep the early cursor baseline; do not deliver wakes into the protected
|
|
913
|
+
// first turn. Steering can pre-empt startup just like cancellation.
|
|
760
914
|
// Owner traffic and agent replies must be observed during a long first
|
|
761
915
|
// turn. Ordinary owner input steers until startup has proved successful.
|
|
762
916
|
if (ownerChannel) {
|
|
@@ -778,6 +932,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
778
932
|
}
|
|
779
933
|
}
|
|
780
934
|
const started = await starting;
|
|
935
|
+
startupTelemetry.finish();
|
|
781
936
|
// A temporary role's first turn can be the active turn when an ours wake
|
|
782
937
|
// needs immediate attention. A typed console/monitor cancellation ends
|
|
783
938
|
// only that turn: the already-live agent session and any queued wake remain
|
|
@@ -816,7 +971,38 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
816
971
|
else if (interruptedForWake)
|
|
817
972
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
818
973
|
+ 'keeping temporary supervisor alive');
|
|
819
|
-
|
|
974
|
+
startupRecoveryAllowed = interruptedForWake;
|
|
975
|
+
sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
|
|
976
|
+
if (started.succeeded && !retainedResume)
|
|
977
|
+
startupTelemetry.mark('readiness_turn_completed');
|
|
978
|
+
if (started.succeeded)
|
|
979
|
+
saveRecovery({ readiness: true });
|
|
980
|
+
// Readiness is now complete. Configured work (or retained continuity) is
|
|
981
|
+
// admitted as a distinct turn; its terminal result must not block console
|
|
982
|
+
// readiness, mail delivery, or supervisor liveness. Empty fresh agents wait
|
|
983
|
+
// for authenticated console input or the monitor's unread/arrival wake.
|
|
984
|
+
const configuredWork = Boolean(role.mission?.trim() || role.roomMemberStartup?.task?.trim() || role.briefing_file?.trim());
|
|
985
|
+
const initialPending = retainedManaged && recoveryBefore?.initial === 'pending';
|
|
986
|
+
const interruptedWork = retainedManaged && (recoveryBefore?.active || recoveryBefore?.initial === 'dispatching');
|
|
987
|
+
const workPrompt = retainedManaged
|
|
988
|
+
? initialPending && configuredWork ? managedTaskPrompt(dir, 'fresh', false)
|
|
989
|
+
: interruptedWork ? 'Fleet supervisor restarted. Continue pending work in the restored conversation; do not redo completed steps. '
|
|
990
|
+
+ (recoveryBefore?.initial === 'dispatching' ? `If you have not yet received your assignment, read ${join(dir, 'briefing.md')} once before acting. ` : '')
|
|
991
|
+
+ '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
|
|
992
|
+
: configuredWork || booted || priorWork ? managedTaskPrompt(dir, mode, priorWork) : undefined;
|
|
993
|
+
if (started.succeeded && !deps.shouldStop?.() && workPrompt) {
|
|
994
|
+
saveRecovery({ initial: 'dispatching', active: true });
|
|
995
|
+
const queued = await arbiter.queuePrompt(workPrompt, {
|
|
996
|
+
origin: { kind: 'startup' },
|
|
997
|
+
});
|
|
998
|
+
void queued.completion.then(result => {
|
|
999
|
+
if (result.succeeded)
|
|
1000
|
+
saveRecovery({ initial: 'completed', active: activePrompts.size > 0 });
|
|
1001
|
+
deps.log(`[${name}] configured task turn ${result.outcome}`);
|
|
1002
|
+
}, () => deps.log(`[${name}] configured task turn failed`));
|
|
1003
|
+
}
|
|
1004
|
+
else if (started.succeeded && initialPending && !configuredWork)
|
|
1005
|
+
saveRecovery({ initial: 'completed' });
|
|
820
1006
|
reloadLoopConfig = async () => {
|
|
821
1007
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
822
1008
|
const definitions = nextRole.loops ?? [];
|
|
@@ -873,9 +1059,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
873
1059
|
}
|
|
874
1060
|
}
|
|
875
1061
|
deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1062
|
+
const healthyAt = deps.now();
|
|
1063
|
+
opts.onHealthy?.();
|
|
1064
|
+
// Keep arrivals behind the early prime() cursor; never re-prime at startup.
|
|
1065
|
+
// Recoverable cancellation preserves the supervisor, not monitor readiness.
|
|
1066
|
+
monitorMayStart = true;
|
|
1067
|
+
startMonitorAfterStartup();
|
|
879
1068
|
recoveryController = new RoleRecoveryController({
|
|
880
1069
|
role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
|
|
881
1070
|
log: deps.log,
|
|
@@ -902,6 +1091,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
902
1091
|
}
|
|
903
1092
|
},
|
|
904
1093
|
});
|
|
1094
|
+
let chatIdle;
|
|
1095
|
+
try {
|
|
1096
|
+
if (temp && !role.roomMemberStartup && !readTempSupervisor(dir)?.taskOwner)
|
|
1097
|
+
chatIdle = new TemporaryChatIdle(dir, deps.now());
|
|
1098
|
+
}
|
|
1099
|
+
catch {
|
|
1100
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
|
|
1101
|
+
}
|
|
905
1102
|
const start = deps.now();
|
|
906
1103
|
let nextLoopReloadAt = deps.now() + 30_000;
|
|
907
1104
|
let nextIdentityPollAt = deps.now();
|
|
@@ -925,7 +1122,21 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
925
1122
|
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
926
1123
|
}
|
|
927
1124
|
}
|
|
1125
|
+
let idleExpired = false;
|
|
1126
|
+
try {
|
|
1127
|
+
idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
|
|
1128
|
+
}
|
|
1129
|
+
catch {
|
|
1130
|
+
chatIdle = undefined;
|
|
1131
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
|
|
1132
|
+
}
|
|
1133
|
+
if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
|
|
1134
|
+
retirementReason = 'idle-timeout';
|
|
1135
|
+
sessionClosed = true;
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
928
1138
|
if (deps.shouldStop?.()) {
|
|
1139
|
+
reminderManager?.stop();
|
|
929
1140
|
if (temp) {
|
|
930
1141
|
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
931
1142
|
deps.log(`[${name}] temporary supervisor retirement requested (${retirementReason})`);
|
|
@@ -936,7 +1147,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
936
1147
|
}
|
|
937
1148
|
if (temp && now >= nextIdentityPollAt) {
|
|
938
1149
|
nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
|
|
939
|
-
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env);
|
|
1150
|
+
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
|
|
940
1151
|
if (presence.state === 'present') {
|
|
941
1152
|
identityObserved = true;
|
|
942
1153
|
identityAbsentSince = undefined;
|
|
@@ -951,6 +1162,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
951
1162
|
if (now - identityAbsentSince >= TEMP_IDENTITY_CLOSE_DEBOUNCE_MS) {
|
|
952
1163
|
retirementReason = 'identity-closed';
|
|
953
1164
|
deps.log(`[${name}] temporary identity '${role.identity}' closed; retiring session and supervisor`);
|
|
1165
|
+
reminderManager?.stop();
|
|
954
1166
|
await sessionHandle.close();
|
|
955
1167
|
sessionClosed = true;
|
|
956
1168
|
break;
|
|
@@ -973,6 +1185,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
973
1185
|
}
|
|
974
1186
|
}
|
|
975
1187
|
}
|
|
1188
|
+
monitorMayStart = false;
|
|
1189
|
+
reminderManager?.stop();
|
|
976
1190
|
if (loopManager) {
|
|
977
1191
|
control?.setLoopManager(undefined);
|
|
978
1192
|
await loopManager.stop();
|
|
@@ -1018,11 +1232,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1018
1232
|
const rotate = (why) => {
|
|
1019
1233
|
writeFileSync(sidFile, randomUUID() + '\n');
|
|
1020
1234
|
rmSync(bootedFile, { force: true });
|
|
1235
|
+
rmSync(managedRecoveryPath(dir), { force: true });
|
|
1021
1236
|
rotated = true;
|
|
1022
1237
|
deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
|
|
1023
1238
|
};
|
|
1024
|
-
if (!temp && deps.shouldStop?.())
|
|
1025
|
-
deps.log(`[${name}] supervisor
|
|
1239
|
+
if (readTempSupervisor(dir)?.taskOwner || ((!temp || deps.suspendOnStop) && deps.shouldStop?.()))
|
|
1240
|
+
deps.log(`[${name}] supervisor/context retained -> next start RESUMES context`);
|
|
1026
1241
|
else if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
1027
1242
|
|| exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
|
|
1028
1243
|
// This is a deliberate adapter reclamation, not evidence that resume state
|
|
@@ -1053,11 +1268,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1053
1268
|
throw new SupervisorRecycleRequiredError();
|
|
1054
1269
|
return {
|
|
1055
1270
|
elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
|
|
1056
|
-
|
|
1271
|
+
healthySecs: (deps.now() - healthyAt) / 1000,
|
|
1272
|
+
...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
|
|
1057
1273
|
};
|
|
1058
1274
|
}
|
|
1059
1275
|
finally {
|
|
1060
|
-
|
|
1276
|
+
reminderManager?.stop();
|
|
1277
|
+
managedService.setReminders?.(undefined);
|
|
1278
|
+
await managedService.close(layoutRetiring);
|
|
1061
1279
|
}
|
|
1062
1280
|
}
|
|
1063
1281
|
/**
|
|
@@ -1072,11 +1290,13 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1072
1290
|
*/
|
|
1073
1291
|
export async function runSupervised(name, opts = {}, partialDeps = {}, attempt = runOnce) {
|
|
1074
1292
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
1075
|
-
const
|
|
1293
|
+
const taskMember = opts.temp === true;
|
|
1294
|
+
const dir = agentDir(name, taskMember);
|
|
1076
1295
|
mkdirSync(dir, { recursive: true });
|
|
1077
1296
|
let stopping = false;
|
|
1078
1297
|
const requestStop = () => { stopping = true; };
|
|
1079
|
-
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1298
|
+
const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
|
|
1299
|
+
|| (taskMember && requestedTempStopReason(dir) === 'operator-stop');
|
|
1080
1300
|
deps.shouldStop = shouldStop;
|
|
1081
1301
|
const stamp = () => new Date(deps.now()).toISOString();
|
|
1082
1302
|
// Record how the PREVIOUS supervisor process ended before doing anything
|
|
@@ -1104,10 +1324,12 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1104
1324
|
process.on('SIGINT', requestStop);
|
|
1105
1325
|
try {
|
|
1106
1326
|
while (!shouldStop()) {
|
|
1327
|
+
if (taskMember && !taskSupervisorMayRun(name))
|
|
1328
|
+
break;
|
|
1107
1329
|
let ledger = readRestartLedger(dir);
|
|
1108
1330
|
try {
|
|
1109
1331
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1110
|
-
const role = findRole(loadConfig(configPath), name);
|
|
1332
|
+
const role = taskMember ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1111
1333
|
reconcileModelRecovery(dir, role, stamp());
|
|
1112
1334
|
if (modelRecoveryHeld(dir)) {
|
|
1113
1335
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
@@ -1123,20 +1345,74 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1123
1345
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1124
1346
|
continue;
|
|
1125
1347
|
}
|
|
1348
|
+
if (attempt === runOnce && taskMember) {
|
|
1349
|
+
// Boot-time daemon unavailability must not consume the harness crash budget.
|
|
1350
|
+
const { waitForRoleDaemon } = await import('./startup-readiness.js');
|
|
1351
|
+
await waitForRoleDaemon(name, undefined, {}, true);
|
|
1352
|
+
}
|
|
1353
|
+
if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
|
|
1354
|
+
break;
|
|
1355
|
+
if (attempt === runOnce && taskMember) {
|
|
1356
|
+
// Cowork can come up after the daemon on boot. Wait read-only so a
|
|
1357
|
+
// gateway still starting its upstream does not spend the failure budget.
|
|
1358
|
+
const { waitForTaskRoomManagement } = await import('./startup-readiness.js');
|
|
1359
|
+
await waitForTaskRoomManagement(name, { shouldStop });
|
|
1360
|
+
}
|
|
1361
|
+
if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
|
|
1362
|
+
break;
|
|
1363
|
+
const fastFailSecs = taskMember ? tempFastFailSecs(name) : fastFailSecsFor(name, opts.configPath);
|
|
1364
|
+
// The fast-fail boundary starts a recovery episode; it must not also be
|
|
1365
|
+
// the boundary that declares recovery successful. Otherwise alternating
|
|
1366
|
+
// 19s and 20s deaths erase one another forever. Require the configured
|
|
1367
|
+
// number of fast-fail windows to survive before closing an active streak.
|
|
1368
|
+
// This hysteresis stays adapter-relative (100s for the current 20s/5-attempt
|
|
1369
|
+
// policy) and still lets a genuinely sustained session reset the breaker.
|
|
1370
|
+
const stableRecoverySecs = fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1371
|
+
// A session that stays up that long has recovered, even if it never
|
|
1372
|
+
// returns: a host reset or OOM-kill ends it without reaching the
|
|
1373
|
+
// post-attempt reset below, and a stale streak would then hold the role
|
|
1374
|
+
// down after a few transient boot failures spread over days.
|
|
1375
|
+
const streakAtStart = ledger;
|
|
1376
|
+
const clearSustainedStreak = () => {
|
|
1377
|
+
if (streakAtStart.consecutiveImmediateFailures === 0)
|
|
1378
|
+
return;
|
|
1379
|
+
const current = readRestartLedger(dir);
|
|
1380
|
+
// Never overwrite an operator reset, an open circuit or a successor's state.
|
|
1381
|
+
if (current.circuit === 'open' || current.updatedAt !== streakAtStart.updatedAt
|
|
1382
|
+
|| current.consecutiveImmediateFailures !== streakAtStart.consecutiveImmediateFailures
|
|
1383
|
+
|| readRunMarker(dir)?.startedAt !== startedAt)
|
|
1384
|
+
return;
|
|
1385
|
+
writeRestartLedger(dir, {
|
|
1386
|
+
...emptyLedger(),
|
|
1387
|
+
...carriedForward(current),
|
|
1388
|
+
lastReason: `sustained healthy session (${stableRecoverySecs}s)`,
|
|
1389
|
+
updatedAt: stamp(),
|
|
1390
|
+
});
|
|
1391
|
+
deps.log(`[${name}] healthy for ${stableRecoverySecs}s; cleared `
|
|
1392
|
+
+ `${current.consecutiveImmediateFailures} earlier immediate failure(s)`);
|
|
1393
|
+
};
|
|
1394
|
+
let cancelStabilityTimer;
|
|
1395
|
+
const onHealthy = () => {
|
|
1396
|
+
if (cancelStabilityTimer || streakAtStart.consecutiveImmediateFailures === 0)
|
|
1397
|
+
return;
|
|
1398
|
+
cancelStabilityTimer = (deps.setTimer ?? unrefTimer)(clearSustainedStreak, stableRecoverySecs * 1000);
|
|
1399
|
+
};
|
|
1126
1400
|
let result;
|
|
1127
1401
|
try {
|
|
1128
1402
|
// Service-manager boot and automatic retry bypass the operator-facing
|
|
1129
1403
|
// up/restart commands. Reconcile again immediately before every real
|
|
1130
1404
|
// permanent harness attempt; the operation is idempotent and releases its
|
|
1131
1405
|
// provisioning lease before the agent or owner channel binds.
|
|
1132
|
-
if (attempt === runOnce) {
|
|
1406
|
+
if (attempt === runOnce && !taskMember) {
|
|
1133
1407
|
const configPath = resolveConfigPath(dir, opts.configPath);
|
|
1134
1408
|
const role = findRole(loadConfig(configPath), name);
|
|
1135
1409
|
await reconcilePermanentRoleIdentities(role, undefined, deps.log);
|
|
1136
1410
|
}
|
|
1137
|
-
result = await attempt(name, { configPath: opts.configPath,
|
|
1411
|
+
result = await attempt(name, { configPath: opts.configPath, ...(taskMember ? { temp: true } : {}),
|
|
1412
|
+
allowResumeRotation: taskMember ? false : !ledger.resumeDiscarded, onHealthy }, deps);
|
|
1138
1413
|
}
|
|
1139
1414
|
catch (e) {
|
|
1415
|
+
cancelStabilityTimer?.();
|
|
1140
1416
|
if (e instanceof SupervisorRecycleRequiredError) {
|
|
1141
1417
|
writeRestartLedger(dir, {
|
|
1142
1418
|
...ledger,
|
|
@@ -1156,8 +1432,14 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1156
1432
|
mode: 'fresh',
|
|
1157
1433
|
};
|
|
1158
1434
|
}
|
|
1159
|
-
|
|
1435
|
+
cancelStabilityTimer?.();
|
|
1436
|
+
if (stopping || (taskMember && (shouldStop() || result.retirementReason))) {
|
|
1437
|
+
// An orderly stop of a session that was healthy long enough is a
|
|
1438
|
+
// recovery too; only the timer's race with the stop is decided here.
|
|
1439
|
+
if ((result.healthySecs ?? 0) >= stableRecoverySecs)
|
|
1440
|
+
clearSustainedStreak();
|
|
1160
1441
|
break;
|
|
1442
|
+
}
|
|
1161
1443
|
// Re-read: the attempt itself may have taken minutes, and an operator may
|
|
1162
1444
|
// have reset the ledger meanwhile.
|
|
1163
1445
|
ledger = readRestartLedger(dir);
|
|
@@ -1174,14 +1456,6 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1174
1456
|
await deps.sleep(HELD_DOWN_POLL_MS);
|
|
1175
1457
|
continue;
|
|
1176
1458
|
}
|
|
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
1459
|
const recoveryFailed = result.elapsedSecs < fastFailSecs
|
|
1186
1460
|
|| (ledger.consecutiveImmediateFailures > 0 && result.elapsedSecs < stableRecoverySecs);
|
|
1187
1461
|
if (!recoveryFailed) {
|
|
@@ -1211,7 +1485,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1211
1485
|
next.nextDelayMs = 0;
|
|
1212
1486
|
writeRestartLedger(dir, next);
|
|
1213
1487
|
deps.log(`[${name}] HELD DOWN after ${failures} immediate failures at ${next.openedAt} — ` +
|
|
1214
|
-
`${reason};
|
|
1488
|
+
`${reason}; recover with: ${taskMember ? `ours-fleet task recover-members ${readTempSupervisor(dir).taskOwner.taskId}` : `ours-fleet restart ${name}`}`);
|
|
1215
1489
|
continue;
|
|
1216
1490
|
}
|
|
1217
1491
|
writeRestartLedger(dir, next);
|
|
@@ -1226,7 +1500,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
1226
1500
|
process.off('SIGINT', requestStop);
|
|
1227
1501
|
// An orderly shutdown clears the marker; an unhandled signal or OOM-kill
|
|
1228
1502
|
// leaves it so the successor can identify an abrupt termination.
|
|
1229
|
-
if (shouldStop())
|
|
1503
|
+
if (!taskMember && shouldStop())
|
|
1230
1504
|
await deps.releaseAgentOurs(findRole(loadConfig(opts.configPath), name));
|
|
1231
1505
|
releaseSupervisorRun(dir);
|
|
1232
1506
|
}
|
|
@@ -1245,9 +1519,67 @@ function fastFailSecsFor(name, configPath) {
|
|
|
1245
1519
|
return 20;
|
|
1246
1520
|
}
|
|
1247
1521
|
}
|
|
1522
|
+
function tempFastFailSecs(name) {
|
|
1523
|
+
try {
|
|
1524
|
+
return getAdapter(loadTempRole(name).harness).exitPolicy.fastFailSecs;
|
|
1525
|
+
}
|
|
1526
|
+
catch {
|
|
1527
|
+
return 20;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1248
1530
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
1531
|
+
/** The parent uses the same adapter-relative sustained-uptime reset boundary. */
|
|
1532
|
+
export function stableSupervisorWindow(name, temp, configPath) {
|
|
1533
|
+
try {
|
|
1534
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
1535
|
+
return getAdapter(role.harness).exitPolicy.fastFailSecs * RESTART_FAIL_THRESHOLD;
|
|
1536
|
+
}
|
|
1537
|
+
catch {
|
|
1538
|
+
return 20 * RESTART_FAIL_THRESHOLD;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1249
1541
|
export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
1250
1542
|
const dir = agentDir(name, true);
|
|
1543
|
+
const metadata = readTempSupervisor(dir);
|
|
1544
|
+
// Non-resumable standalone temporaries retain their prior finite lifecycle:
|
|
1545
|
+
// a parent stop retires them rather than promising retained conversation.
|
|
1546
|
+
if (!metadata?.taskOwner && deps.suspendOnStop && !getAdapter(loadTempRole(name).harness).supportsResume)
|
|
1547
|
+
deps = { ...deps, suspendOnStop: false };
|
|
1548
|
+
if (!metadata?.taskOwner) {
|
|
1549
|
+
const role = loadTempRole(name);
|
|
1550
|
+
const provenance = readProvenance(dir);
|
|
1551
|
+
if (role.roomMemberStartup?.task_id
|
|
1552
|
+
|| (name.startsWith('layout-') && provenance?.creationActionId?.startsWith('task-')))
|
|
1553
|
+
throw Error('TASK_SUPERVISOR_OWNER_MISSING');
|
|
1554
|
+
}
|
|
1555
|
+
if (metadata?.taskOwner) {
|
|
1556
|
+
// Serialize the entire process lifetime before metadata/session writes.
|
|
1557
|
+
const lease = await acquireOwnerBinderLease(join(dir, '.task-supervisor'), 'task-supervisor', name);
|
|
1558
|
+
let ownershipVerified = false;
|
|
1559
|
+
try {
|
|
1560
|
+
const mayRun = taskSupervisorMayRun(name);
|
|
1561
|
+
ownershipVerified = true;
|
|
1562
|
+
if (!mayRun)
|
|
1563
|
+
return;
|
|
1564
|
+
await markTempSupervisorActive(dir);
|
|
1565
|
+
await runSupervised(name, { temp: true }, deps, attempt);
|
|
1566
|
+
}
|
|
1567
|
+
finally {
|
|
1568
|
+
try {
|
|
1569
|
+
if (ownershipVerified && requestedTempStopReason(dir)) {
|
|
1570
|
+
// Parent shutdown suspends; an explicit stop retires the exact saved
|
|
1571
|
+
// owner even when no harness attempt ran (for example held circuits).
|
|
1572
|
+
await (deps.releaseAgentOurs ?? releaseManagedAgent)(loadTempRole(name), taskMemberRetirementProof(name));
|
|
1573
|
+
const archived = archiveTempState(name, 'operator-stop', 'retired', 'explicit task member retirement');
|
|
1574
|
+
deps.log?.(`[${name}] task member retired: operator-stop${archived ? `; evidence archived at ${archived}` : ''}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
finally {
|
|
1578
|
+
lease.release();
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1251
1583
|
await markTempSupervisorActive(dir);
|
|
1252
1584
|
let signal;
|
|
1253
1585
|
const onTerm = () => { signal = 'SIGTERM'; };
|
|
@@ -1274,7 +1606,8 @@ export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
|
1274
1606
|
process.off('SIGTERM', onTerm);
|
|
1275
1607
|
process.off('SIGINT', onInt);
|
|
1276
1608
|
// A recycle replaces the supervisor process, not the logical temporary agent.
|
|
1277
|
-
if (!(
|
|
1609
|
+
if (!((signal || deps.shouldStop?.()) && deps.suspendOnStop && !requestedTempStopReason(dir))
|
|
1610
|
+
&& (!(failure instanceof SupervisorRecycleRequiredError) || signal || deps.shouldStop?.())) {
|
|
1278
1611
|
const requested = requestedTempStopReason(dir);
|
|
1279
1612
|
const reason = requested
|
|
1280
1613
|
?? result?.retirementReason
|