@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -68
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +26 -9
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/file-rpc.d.ts +12 -0
- package/dist/agent-ours/file-rpc.js +18 -4
- package/dist/agent-ours/harness.js +9 -1
- package/dist/agent-ours/mcp-endpoint.d.ts +19 -0
- package/dist/agent-ours/mcp-endpoint.js +98 -25
- package/dist/agent-ours/runtime.d.ts +9 -1
- package/dist/agent-ours/runtime.js +50 -2
- package/dist/agent-ours/service.d.ts +24 -1
- package/dist/agent-ours/service.js +97 -13
- package/dist/application/codex-models.d.ts +15 -0
- package/dist/application/codex-models.js +56 -0
- package/dist/application/conversation-history.d.ts +34 -0
- package/dist/application/conversation-history.js +280 -0
- package/dist/application/conversation-role-observer.d.ts +62 -0
- package/dist/application/conversation-role-observer.js +206 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +3 -3
- package/dist/application/log-service.js +8 -8
- package/dist/application/model-catalog.d.ts +1 -1
- package/dist/application/model-catalog.js +5 -2
- package/dist/application/preset-provenance.d.ts +19 -0
- package/dist/application/preset-provenance.js +59 -0
- package/dist/application/resource-observer.d.ts +40 -0
- package/dist/application/resource-observer.js +278 -0
- package/dist/application/role-creation-service.js +3 -2
- package/dist/application/role-removal-service.js +17 -3
- package/dist/application/role-repository.d.ts +1 -1
- package/dist/application/role-repository.js +30 -7
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +61 -17
- package/dist/application/session-control.js +207 -23
- package/dist/application/supervisor-ours-tools.js +5 -11
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +261 -0
- package/dist/application/task-room-service.d.ts +101 -3
- package/dist/application/task-room-service.js +286 -33
- package/dist/application/types.d.ts +2 -0
- package/dist/atomic-file.d.ts +34 -1
- package/dist/atomic-file.js +161 -18
- package/dist/briefing.d.ts +3 -1
- package/dist/briefing.js +97 -31
- package/dist/build-info.json +9 -4
- package/dist/capabilities.d.ts +9 -1
- package/dist/capabilities.js +13 -0
- package/dist/cli.js +400 -37
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +45 -0
- package/dist/config.js +57 -6
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +4 -4
- package/dist/daemon-recovery.d.ts +14 -9
- package/dist/daemon-recovery.js +124 -115
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +207 -19
- package/dist/doctor.d.ts +4 -1
- package/dist/doctor.js +75 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/file-delivery/reader.d.ts +15 -0
- package/dist/file-delivery/reader.js +63 -0
- package/dist/file-delivery/store.d.ts +15 -0
- package/dist/file-delivery/store.js +124 -0
- package/dist/file-delivery/types.d.ts +19 -0
- package/dist/file-delivery/types.js +1 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +24 -8
- package/dist/harness/agent-session.d.ts +4 -0
- package/dist/harness/claude-code-session.js +1 -1
- package/dist/harness/claude-code.js +21 -11
- package/dist/harness/claude-runtime.d.ts +12 -0
- package/dist/harness/claude-runtime.js +43 -0
- package/dist/harness/codex-app-server-proxy.d.ts +4 -1
- package/dist/harness/codex-app-server-proxy.js +41 -5
- package/dist/harness/codex-reasoning.d.ts +5 -0
- package/dist/harness/codex-reasoning.js +12 -0
- package/dist/harness/codex-runtime.d.ts +11 -1
- package/dist/harness/codex-runtime.js +31 -4
- package/dist/harness/codex-session.js +2 -2
- package/dist/harness/codex.js +24 -12
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +6 -1
- package/dist/isolation/types.d.ts +5 -0
- package/dist/managed-cli.d.ts +253 -0
- package/dist/managed-cli.js +923 -0
- package/dist/managed-recovery.d.ts +13 -0
- package/dist/managed-recovery.js +31 -0
- package/dist/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +9 -34
- package/dist/monitor.js +90 -153
- package/dist/notifications/fleet-producer.d.ts +22 -0
- package/dist/notifications/fleet-producer.js +237 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +11 -0
- package/dist/notifications/target-cleanup.js +63 -0
- package/dist/notifications/task-producer.d.ts +20 -0
- package/dist/notifications/task-producer.js +100 -0
- package/dist/ops.d.ts +2 -0
- package/dist/ops.js +20 -3
- package/dist/owner-channel/channel.js +17 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +43 -0
- package/dist/process-incarnation.d.ts +23 -0
- package/dist/process-incarnation.js +42 -0
- package/dist/reminders/manager.d.ts +75 -0
- package/dist/reminders/manager.js +278 -0
- package/dist/reminders/runtime.d.ts +27 -0
- package/dist/reminders/runtime.js +32 -0
- package/dist/reminders/schedule.d.ts +37 -0
- package/dist/reminders/schedule.js +104 -0
- package/dist/reminders/store.d.ts +341 -0
- package/dist/reminders/store.js +762 -0
- package/dist/reminders/tools.d.ts +20 -0
- package/dist/reminders/tools.js +114 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +84 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +65 -5
- package/dist/rooms-tasks/close.d.ts +6 -2
- package/dist/rooms-tasks/close.js +246 -37
- package/dist/rooms-tasks/config.d.ts +1 -1
- package/dist/rooms-tasks/config.js +11 -4
- package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
- package/dist/rooms-tasks/cowork-adapter.js +15 -121
- package/dist/rooms-tasks/cowork-http.js +56 -6
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +84 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +173 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -1
- package/dist/rooms-tasks/external-worker.js +3 -4
- package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
- package/dist/rooms-tasks/layout-binding-client.js +56 -0
- package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
- package/dist/rooms-tasks/layout-binding-grants.js +128 -0
- package/dist/rooms-tasks/layout-cli.d.ts +4 -0
- package/dist/rooms-tasks/layout-cli.js +107 -0
- package/dist/rooms-tasks/layout-config.d.ts +11 -0
- package/dist/rooms-tasks/layout-config.js +89 -0
- package/dist/rooms-tasks/layout-control.d.ts +21 -0
- package/dist/rooms-tasks/layout-control.js +52 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
- package/dist/rooms-tasks/layout-member-retirement.js +90 -0
- package/dist/rooms-tasks/layout-service.d.ts +43 -0
- package/dist/rooms-tasks/layout-service.js +203 -0
- package/dist/rooms-tasks/layout.d.ts +121 -0
- package/dist/rooms-tasks/layout.js +286 -0
- package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
- package/dist/rooms-tasks/legacy-task-member.js +116 -0
- package/dist/rooms-tasks/live-readiness.d.ts +18 -0
- package/dist/rooms-tasks/live-readiness.js +85 -0
- package/dist/rooms-tasks/member-ownership.d.ts +5 -0
- package/dist/rooms-tasks/member-ownership.js +40 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +3 -0
- package/dist/rooms-tasks/provision.js +101 -49
- package/dist/rooms-tasks/recovery.d.ts +19 -0
- package/dist/rooms-tasks/recovery.js +110 -0
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +47 -8
- package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
- package/dist/rooms-tasks/task-service-retirement.js +21 -0
- package/dist/rooms-tasks/task-state.d.ts +52 -8
- package/dist/rooms-tasks/task-state.js +251 -26
- package/dist/rooms-tasks/types.d.ts +40 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +35 -0
- package/dist/rooms-tasks/workspace.js +325 -0
- package/dist/runner.d.ts +33 -4
- package/dist/runner.js +410 -62
- package/dist/session/acp.d.ts +75 -2
- package/dist/session/acp.js +345 -82
- package/dist/session/arbiter.d.ts +98 -0
- package/dist/session/arbiter.js +272 -3
- package/dist/session/codex-app-server.d.ts +45 -1
- package/dist/session/codex-app-server.js +225 -32
- package/dist/session/control.d.ts +17 -3
- package/dist/session/control.js +213 -23
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-observation.d.ts +25 -0
- package/dist/session/conversation-observation.js +100 -0
- package/dist/session/conversation-replay.d.ts +50 -0
- package/dist/session/conversation-replay.js +203 -0
- package/dist/session/conversation-store.d.ts +15 -1
- package/dist/session/conversation-store.js +206 -26
- package/dist/session/conversation-types.d.ts +57 -4
- package/dist/session/prompt-batch.d.ts +79 -0
- package/dist/session/prompt-batch.js +120 -0
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +42 -1
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.d.ts +5 -1
- package/dist/spawn.js +40 -4
- package/dist/startup-prompt.d.ts +15 -0
- package/dist/startup-prompt.js +71 -0
- package/dist/startup-readiness.d.ts +32 -0
- package/dist/startup-readiness.js +101 -0
- package/dist/startup-telemetry.d.ts +16 -0
- package/dist/startup-telemetry.js +46 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +77 -0
- package/dist/subscriptions/login.js +215 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +143 -0
- package/dist/supervisor/adoption.d.ts +8 -0
- package/dist/supervisor/adoption.js +78 -0
- package/dist/supervisor/catalog.d.ts +62 -0
- package/dist/supervisor/catalog.js +254 -0
- package/dist/supervisor/fleet.d.ts +8 -0
- package/dist/supervisor/fleet.js +89 -0
- package/dist/supervisor/index.js +3 -6
- package/dist/supervisor/launchd.d.ts +1 -1
- package/dist/supervisor/launchd.js +26 -13
- package/dist/supervisor/legacy.d.ts +35 -0
- package/dist/supervisor/legacy.js +524 -0
- package/dist/supervisor/manager.d.ts +15 -0
- package/dist/supervisor/manager.js +270 -0
- package/dist/supervisor/registration-refresh.d.ts +45 -0
- package/dist/supervisor/registration-refresh.js +266 -0
- package/dist/supervisor/scope.d.ts +4 -0
- package/dist/supervisor/scope.js +10 -0
- package/dist/supervisor/systemd.d.ts +2 -2
- package/dist/supervisor/systemd.js +19 -33
- package/dist/task-supervision.d.ts +9 -0
- package/dist/task-supervision.js +145 -0
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +23 -8
- package/dist/temp-lifecycle.js +123 -66
- package/dist/temp-supervisor-recovery.d.ts +1 -1
- package/dist/temp-supervisor-recovery.js +2 -2
- package/dist/tunnel-setup-completion.d.ts +7 -0
- package/dist/tunnel-setup-completion.js +13 -0
- package/dist/tunnel-setup-input.d.ts +19 -0
- package/dist/tunnel-setup-input.js +124 -0
- package/dist/watchdog/query.d.ts +3 -1
- package/dist/watchdog/query.js +2 -1
- package/dist/web/agent-attachments.d.ts +29 -0
- package/dist/web/agent-attachments.js +162 -0
- package/dist/web/auth.d.ts +65 -2
- package/dist/web/auth.js +201 -11
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/conversation-stream-codec.d.ts +23 -0
- package/dist/web/conversation-stream-codec.js +110 -0
- package/dist/web/conversation-stream.d.ts +25 -0
- package/dist/web/conversation-stream.js +231 -0
- package/dist/web/events.d.ts +2 -0
- package/dist/web/events.js +9 -0
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +248 -0
- package/dist/web/resource-views.d.ts +124 -0
- package/dist/web/resource-views.js +424 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +132 -34
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.js +802 -67
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +107 -0
- package/dist/web/workspace-devices.js +277 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +83 -0
- package/dist/workspace-enrollment.js +580 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/acp-files.md +146 -0
- package/docs/conversation-stream.md +196 -0
- package/docs/fleet-migration.md +54 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/self-reminders.md +374 -0
- package/docs/task-workspaces.md +225 -0
- package/docs/validation/managed-cli-permissions.md +421 -0
- package/examples/room-layouts/fresh.yaml +27 -0
- package/examples/room-layouts/scoped.yaml +21 -0
- package/examples/room-layouts/shared.yaml +19 -0
- package/package.json +24 -20
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +12 -11
- package/presets/fleet/roles/Critic.yaml +13 -0
- package/presets/fleet/roles/Developer.yaml +13 -0
- package/presets/fleet/roles/Engineer.yaml +53 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
package/dist/session/acp.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ConversationObservation } from './conversation-observation.js';
|
|
2
|
+
import { appendTaskCreated } from './task-notice.js';
|
|
1
3
|
import { spawn } from 'node:child_process';
|
|
2
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
5
|
import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
|
|
@@ -7,6 +9,8 @@ import * as acp from '@agentclientprotocol/sdk';
|
|
|
7
9
|
import { normalizeSessionUpdate } from './conversation-normalizer.js';
|
|
8
10
|
import { ConversationEventStore } from './conversation-store.js';
|
|
9
11
|
import { SessionEvents } from './events.js';
|
|
12
|
+
import { commandName, memberAuthority, memberBindings, pendingInventory, takeBatch, refuseAtRelease, RELEASE_GUARD_REFUSED, } from './prompt-batch.js';
|
|
13
|
+
import { recordClaudeRateLimit } from '../subscriptions/observed.js';
|
|
10
14
|
import { DEFAULT_STALL_TIMEOUT_MS, STALL_RECOVERY_PROMPT, StallWatchdog, StallToolHistory, hasStallRecoveryClaim } from './stall-watchdog.js';
|
|
11
15
|
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, sessionBackendCapabilities, turnResult, } from './types.js';
|
|
12
16
|
const CANCEL_SETTLE_GRACE_MS = 15_000;
|
|
@@ -36,7 +40,6 @@ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
|
|
|
36
40
|
/** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
|
|
37
41
|
export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
|
|
38
42
|
const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
|
|
39
|
-
const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
|
|
40
43
|
const MAX_CANONICAL_SYMLINK_DEPTH = 40;
|
|
41
44
|
const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
|
|
42
45
|
/**
|
|
@@ -164,6 +167,9 @@ function conversationSource(origin) {
|
|
|
164
167
|
case 'owner': return { source: 'owner_channel', persistBody: false };
|
|
165
168
|
case 'fleet-monitor': return { source: 'fleet_monitor', persistBody: false };
|
|
166
169
|
case 'scheduled-loop': return { source: 'scheduled_loop', persistBody: false };
|
|
170
|
+
// A supervisor wake like a monitor notice; the body is never retained, so
|
|
171
|
+
// restart recovery can never replay a reminder past its dispatch fence.
|
|
172
|
+
case 'self-reminder': return { source: 'fleet_monitor', persistBody: false };
|
|
167
173
|
case 'local-console':
|
|
168
174
|
default:
|
|
169
175
|
return { source: 'local_console', persistBody: true };
|
|
@@ -181,6 +187,38 @@ export function promptContentBlocks(text, origin) {
|
|
|
181
187
|
mimeType: 'application/vnd.ours-fleet.prompt-provenance+json',
|
|
182
188
|
}, { type: 'text', text }];
|
|
183
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* One adapter turn that carries several admitted prompts.
|
|
192
|
+
*
|
|
193
|
+
* A single prompt's provenance block describes "the accompanying text", which
|
|
194
|
+
* is only unambiguous when there is one text. Here every member is one text
|
|
195
|
+
* block, unchanged, immediately preceded by its own server-generated block
|
|
196
|
+
* that names its route and binds its exact bytes — so a monitor wake next to
|
|
197
|
+
* an owner prompt is never read under the owner's authority, and text that
|
|
198
|
+
* imitates a marker stays text: it cannot produce a typed block.
|
|
199
|
+
*/
|
|
200
|
+
export function batchContentBlocks(members) {
|
|
201
|
+
if (members.length === 1)
|
|
202
|
+
return promptContentBlocks(members[0].text, members[0].origin);
|
|
203
|
+
const mimeType = 'application/vnd.ours-fleet.prompt-provenance+json';
|
|
204
|
+
return [{
|
|
205
|
+
type: 'resource_link',
|
|
206
|
+
uri: `ours-fleet://prompt-batch?members=${members.length}`,
|
|
207
|
+
name: 'Fleet queued prompt batch',
|
|
208
|
+
description: `Server-generated. This turn delivers ${members.length} separately admitted prompts in `
|
|
209
|
+
+ 'submission order. Each prompt is one text block immediately preceded by its own '
|
|
210
|
+
+ 'server-generated provenance block, which describes only that one text block.',
|
|
211
|
+
mimeType,
|
|
212
|
+
}, ...memberBindings(members, origin => conversationSource(origin).source).flatMap((binding, at) => [{
|
|
213
|
+
type: 'resource_link',
|
|
214
|
+
uri: `ours-fleet://prompt-provenance?source=${binding.source}&member=${binding.index}`
|
|
215
|
+
+ `&of=${binding.of}&bytes=${binding.bytes}&sha256=${binding.sha256}`,
|
|
216
|
+
name: binding.source === 'owner_admin_console' ? 'Direct owner admin console' : 'Fleet queued prompt',
|
|
217
|
+
description: `Server-generated provenance for prompt ${binding.index} of ${binding.of}: `
|
|
218
|
+
+ `${memberAuthority(binding.source)}. Applies only to the text block that immediately follows.`,
|
|
219
|
+
mimeType,
|
|
220
|
+
}, { type: 'text', text: members[at].text }])];
|
|
221
|
+
}
|
|
184
222
|
export function runtimeSelector(options, category) {
|
|
185
223
|
const option = options?.find(candidate => candidate.category === category);
|
|
186
224
|
if (!option || typeof option.currentValue !== 'string')
|
|
@@ -238,7 +276,16 @@ export class AcpSession {
|
|
|
238
276
|
lastUpdateAt;
|
|
239
277
|
lastError;
|
|
240
278
|
promptTail = Promise.resolve();
|
|
279
|
+
/** Admitted prompts not yet handed to the adapter, in admission order. */
|
|
280
|
+
pendingPrompts = [];
|
|
281
|
+
/** Admitted prompt → the adapter turn that carries it, while that turn is open. */
|
|
282
|
+
sharedTurns = new Map();
|
|
283
|
+
/** Commands the adapter currently advertises; only these are control operations. */
|
|
284
|
+
advertisedCommands = new Set();
|
|
285
|
+
presentedCommands = [];
|
|
241
286
|
queueDepth = 0;
|
|
287
|
+
deferredRecovered = [];
|
|
288
|
+
observation = new ConversationObservation(() => this.captureObservation());
|
|
242
289
|
exit = null;
|
|
243
290
|
steeringSupported = false;
|
|
244
291
|
agentCapabilities;
|
|
@@ -277,6 +324,7 @@ export class AcpSession {
|
|
|
277
324
|
stallTimer;
|
|
278
325
|
stallAttempt;
|
|
279
326
|
toolBoundaryWaiters = new Set();
|
|
327
|
+
fileExportBusy = false;
|
|
280
328
|
activeTurn;
|
|
281
329
|
constructor(options, child, connection) {
|
|
282
330
|
this.options = options;
|
|
@@ -289,6 +337,9 @@ export class AcpSession {
|
|
|
289
337
|
roleId: options.name, log: line => options.log(`[${options.name}] ${line}`),
|
|
290
338
|
});
|
|
291
339
|
this.conversationStartCursor = this.conversation.lastCursor();
|
|
340
|
+
// A durable append can precede its inventory mutation. Capture only after
|
|
341
|
+
// the synchronous owner checkpoint, never the state at append notification.
|
|
342
|
+
this.conversation.subscribe(() => this.observation.scheduleCapture());
|
|
292
343
|
this.sessionFile = join(options.stateDir, '.acp-session-id');
|
|
293
344
|
this.terminated = new Promise((_resolve, reject) => { this.terminate = reject; });
|
|
294
345
|
// Nothing awaits this promise until a request races it; an unobserved
|
|
@@ -322,6 +373,7 @@ export class AcpSession {
|
|
|
322
373
|
acpSessionId: this.sessionId,
|
|
323
374
|
payload: { status: 'failed', detail: this.lastError },
|
|
324
375
|
});
|
|
376
|
+
this.observation.publish();
|
|
325
377
|
});
|
|
326
378
|
}
|
|
327
379
|
static async start(options) {
|
|
@@ -406,14 +458,29 @@ export class AcpSession {
|
|
|
406
458
|
});
|
|
407
459
|
continue;
|
|
408
460
|
}
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
void run.finally(() => { this.queueDepth = Math.max(0, this.queueDepth - 1); });
|
|
461
|
+
const recovered = { promptId: open.promptId, text: open.text };
|
|
462
|
+
if (this.options.deferRecoveredPrompts)
|
|
463
|
+
this.deferredRecovered.push(recovered);
|
|
464
|
+
else
|
|
465
|
+
this.dispatchRecovered(recovered);
|
|
415
466
|
}
|
|
416
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Dispatch recovered prompts held by deferRecoveredPrompts. A recovered turn
|
|
470
|
+
* can run tools at once, including the managed Fleet CLI, so it must not
|
|
471
|
+
* start before the supervisor's control socket accepts connections. Prompts
|
|
472
|
+
* never activated stay admitted-unstarted and recover again on next start.
|
|
473
|
+
*/
|
|
474
|
+
activateRecoveredPrompts() {
|
|
475
|
+
for (const recovered of this.deferredRecovered.splice(0))
|
|
476
|
+
this.dispatchRecovered(recovered);
|
|
477
|
+
}
|
|
478
|
+
dispatchRecovered({ promptId, text }) {
|
|
479
|
+
this.queueDepth++;
|
|
480
|
+
const run = this.enqueue(promptId, text, undefined);
|
|
481
|
+
this.observation.publish();
|
|
482
|
+
void run.finally(() => { this.queueDepth = Math.max(0, this.queueDepth - 1); this.observation.publish(); });
|
|
483
|
+
}
|
|
417
484
|
isAlive() {
|
|
418
485
|
// child.killed only means kill() successfully SENT a signal. A process may
|
|
419
486
|
// ignore SIGTERM and remain alive. exitCode or signalCode is the actual
|
|
@@ -430,6 +497,7 @@ export class AcpSession {
|
|
|
430
497
|
return;
|
|
431
498
|
this.steeringOccupied = true;
|
|
432
499
|
this.refreshSteeringOccupancy();
|
|
500
|
+
this.observation.publish();
|
|
433
501
|
}
|
|
434
502
|
refreshSteeringOccupancy() {
|
|
435
503
|
if (!this.steeringOccupied)
|
|
@@ -452,6 +520,7 @@ export class AcpSession {
|
|
|
452
520
|
if (!this.steeringOccupied)
|
|
453
521
|
return;
|
|
454
522
|
this.steeringOccupied = false;
|
|
523
|
+
this.observation.publish();
|
|
455
524
|
this.options.log(`[${this.options.name}] steering-started turn no longer holds the adapter (${reason})`);
|
|
456
525
|
}
|
|
457
526
|
snapshot() {
|
|
@@ -465,6 +534,9 @@ export class AcpSession {
|
|
|
465
534
|
readiness: this.readiness === 'idle' && this.steeringOccupied
|
|
466
535
|
? 'running' : this.readiness,
|
|
467
536
|
sessionId: this.sessionId,
|
|
537
|
+
// A shared turn is a human console turn if any member is: inbox traffic must not cut it short.
|
|
538
|
+
...(this.activeTurn?.origin ? { activePromptSource: this.activeTurn.console
|
|
539
|
+
? 'owner-admin-console' : this.activeTurn.origin.kind } : {}),
|
|
468
540
|
lastError: this.lastError,
|
|
469
541
|
pendingPermissionId: this.pendingPermissions.keys().next().value,
|
|
470
542
|
runtimeModel: this.runtimeModel,
|
|
@@ -579,6 +651,11 @@ export class AcpSession {
|
|
|
579
651
|
* steering response from becoming a tight replay loop.
|
|
580
652
|
*/
|
|
581
653
|
async steerOrQueueWake(text, options) {
|
|
654
|
+
// A tool-boundary wait may outlive its original turn. Recheck the current
|
|
655
|
+
// typed source at delivery, since an Owner turn can begin during the wait.
|
|
656
|
+
// `console` covers a shared turn whose first member is not the console prompt.
|
|
657
|
+
if (options.origin?.kind === 'fleet-monitor' && this.activeTurn?.console)
|
|
658
|
+
return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
582
659
|
if (this.stallRecoveryClaimed)
|
|
583
660
|
return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
584
661
|
const steered = await this.steerPrompt(text);
|
|
@@ -644,6 +721,9 @@ export class AcpSession {
|
|
|
644
721
|
* for it is what turned a busy agent into a timeout and then into "dead".
|
|
645
722
|
*/
|
|
646
723
|
async queuePrompt(text, options = {}) {
|
|
724
|
+
// A guarded prompt must reach the model only through the release fence.
|
|
725
|
+
if (options.releaseGuard)
|
|
726
|
+
options = { ...options, interrupt: false, steer: false };
|
|
647
727
|
if (this.stallRecoveryClaimed && options.origin?.kind === 'fleet-monitor')
|
|
648
728
|
options = { ...options, interrupt: false, steer: false };
|
|
649
729
|
if (this.cancelRecoveryReason)
|
|
@@ -666,12 +746,18 @@ export class AcpSession {
|
|
|
666
746
|
}
|
|
667
747
|
const promptId = randomUUID();
|
|
668
748
|
const queuedBehind = this.queueDepth;
|
|
669
|
-
this.
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
749
|
+
const run = this.observation.checkpoint(() => {
|
|
750
|
+
this.admitToLedger(promptId, text, queuedBehind, options);
|
|
751
|
+
this.queueDepth++;
|
|
752
|
+
return this.enqueue(promptId, text, options.origin, options.releaseGuard);
|
|
753
|
+
});
|
|
754
|
+
const completion = run.then(result => {
|
|
674
755
|
this.queueDepth = Math.max(0, this.queueDepth - 1);
|
|
756
|
+
this.observation.publish();
|
|
757
|
+
return result;
|
|
758
|
+
}, error => {
|
|
759
|
+
this.queueDepth = Math.max(0, this.queueDepth - 1);
|
|
760
|
+
this.observation.publish();
|
|
675
761
|
return turnResult(false, 'failed', error?.message ?? String(error));
|
|
676
762
|
});
|
|
677
763
|
return {
|
|
@@ -679,6 +765,57 @@ export class AcpSession {
|
|
|
679
765
|
delivery: delivery ?? (queuedBehind > 0 ? 'queued' : 'started'),
|
|
680
766
|
};
|
|
681
767
|
}
|
|
768
|
+
/** The turn whose events and result answer this admitted prompt; itself unless a release shared it. */
|
|
769
|
+
sharedTurnId(promptId) {
|
|
770
|
+
return this.sharedTurns.get(promptId);
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Queue an admitted prompt behind the current work. Each prompt keeps its
|
|
774
|
+
* own step in the chain, so its place in line is unchanged; what the step
|
|
775
|
+
* does when it is reached is release a whole snapshot, not one prompt.
|
|
776
|
+
*/
|
|
777
|
+
enqueue(promptId, text, origin, releaseGuard) {
|
|
778
|
+
const entry = {
|
|
779
|
+
promptId, text, origin, ...conversationSource(origin), admittedAt: new Date().toISOString(),
|
|
780
|
+
command: this.advertisedCommands.has(commandName(text) ?? ''),
|
|
781
|
+
...(releaseGuard ? { releaseGuard } : {}),
|
|
782
|
+
};
|
|
783
|
+
this.pendingPrompts.push(entry);
|
|
784
|
+
const run = this.promptTail.then(() => this.release(entry));
|
|
785
|
+
this.promptTail = run.then(() => undefined, () => undefined);
|
|
786
|
+
return run;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* The turn boundary. The first waiting prompt to get here takes every
|
|
790
|
+
* pending prompt delivered with it, synchronously, and they share one adapter
|
|
791
|
+
* turn. A prompt admitted after that — while the turn runs, or while a
|
|
792
|
+
* cancellation settles — stays pending for the next boundary. A step whose
|
|
793
|
+
* prompt an earlier boundary already took just reports that turn's result.
|
|
794
|
+
*/
|
|
795
|
+
release(entry) {
|
|
796
|
+
if (entry.released)
|
|
797
|
+
return entry.released;
|
|
798
|
+
const refused = refuseAtRelease(this.pendingPrompts, entry, Boolean(this.sessionId && this.isAlive()));
|
|
799
|
+
if (refused) {
|
|
800
|
+
this.conversation.appendSafe({
|
|
801
|
+
kind: 'turn.completed', sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId,
|
|
802
|
+
promptId: entry.promptId, turnId: entry.promptId,
|
|
803
|
+
payload: { outcome: 'cancelled', stopReason: RELEASE_GUARD_REFUSED },
|
|
804
|
+
});
|
|
805
|
+
entry.released = Promise.resolve(refused);
|
|
806
|
+
return entry.released;
|
|
807
|
+
}
|
|
808
|
+
const members = takeBatch(this.pendingPrompts, entry);
|
|
809
|
+
for (const member of members)
|
|
810
|
+
this.sharedTurns.set(member.promptId, members[0].promptId);
|
|
811
|
+
const run = this.runPrompt(members).finally(() => {
|
|
812
|
+
for (const member of members)
|
|
813
|
+
this.sharedTurns.delete(member.promptId);
|
|
814
|
+
});
|
|
815
|
+
for (const member of members)
|
|
816
|
+
member.released = run;
|
|
817
|
+
return run;
|
|
818
|
+
}
|
|
682
819
|
/**
|
|
683
820
|
* Prepare the session for a prompt that asked to pre-empt current work.
|
|
684
821
|
*
|
|
@@ -760,11 +897,16 @@ export class AcpSession {
|
|
|
760
897
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
761
898
|
if (existing)
|
|
762
899
|
return existing;
|
|
900
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
901
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
902
|
+
if (command.requireIdle && (this.queueDepth > 0 || this.conversationSnapshot().readiness !== 'idle'))
|
|
903
|
+
throw new Error('voice_busy: Agent is already working. Wait for the current turn.');
|
|
763
904
|
const queued = await this.queuePrompt(command.text, {
|
|
764
905
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
765
906
|
actor: { browserSession: command.actorBrowserSession },
|
|
766
907
|
});
|
|
767
908
|
const receipt = {
|
|
909
|
+
sessionGeneration: this.sessionGeneration,
|
|
768
910
|
commandId: command.commandId,
|
|
769
911
|
promptId: queued.promptId,
|
|
770
912
|
state: queued.queuedBehind > 0 ? 'queued' : 'starting',
|
|
@@ -792,6 +934,14 @@ export class AcpSession {
|
|
|
792
934
|
* (`queuePrompt({ interrupt: true })`) needs the typed error, because only it
|
|
793
935
|
* still owes an undelivered message a replay.
|
|
794
936
|
*/
|
|
937
|
+
liveVoiceSupported = true;
|
|
938
|
+
async interruptPrompt(generation, promptId) {
|
|
939
|
+
if (generation !== this.sessionGeneration)
|
|
940
|
+
throw new Error('session_changed: Agent session changed.');
|
|
941
|
+
if (!this.activeTurn?.promptIds.includes(promptId))
|
|
942
|
+
return { state: 'settled' };
|
|
943
|
+
return this.interrupt('local-console');
|
|
944
|
+
}
|
|
795
945
|
async interrupt(source = 'local-console') {
|
|
796
946
|
try {
|
|
797
947
|
await this.cancelActive(source);
|
|
@@ -810,6 +960,8 @@ export class AcpSession {
|
|
|
810
960
|
if (!this.sessionId)
|
|
811
961
|
return;
|
|
812
962
|
const active = this.activeTurn;
|
|
963
|
+
if (active)
|
|
964
|
+
active.fileDeliveryRevoked = true;
|
|
813
965
|
const previousSource = active?.cancellationSource;
|
|
814
966
|
if (active && (source === 'owner' || source === 'local-console' || !previousSource))
|
|
815
967
|
active.cancellationSource = source;
|
|
@@ -902,6 +1054,7 @@ export class AcpSession {
|
|
|
902
1054
|
this.allowPermission(pending.toolCallId, permissionId);
|
|
903
1055
|
else
|
|
904
1056
|
this.releasePermission(pending.toolCallId, permissionId);
|
|
1057
|
+
this.readiness = this.pendingPermissions.size > 0 ? 'awaiting_permission' : 'running';
|
|
905
1058
|
this.events.emit('permission', {
|
|
906
1059
|
turnId: this.activeTurn?.id,
|
|
907
1060
|
origin: this.activeTurn?.origin,
|
|
@@ -918,7 +1071,7 @@ export class AcpSession {
|
|
|
918
1071
|
promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
|
|
919
1072
|
payload: { decision, decisionSource: 'manual', optionId },
|
|
920
1073
|
});
|
|
921
|
-
this.
|
|
1074
|
+
this.observation.publish();
|
|
922
1075
|
return true;
|
|
923
1076
|
}
|
|
924
1077
|
/**
|
|
@@ -984,6 +1137,8 @@ export class AcpSession {
|
|
|
984
1137
|
: { outcome: { outcome: 'cancelled' } });
|
|
985
1138
|
const settled = decision === 'denied' && !rejectOption ? 'cancelled' : decision;
|
|
986
1139
|
this.releasePermission(pending.toolCallId, permissionId);
|
|
1140
|
+
if (this.pendingPermissions.size === 0 && this.readiness === 'awaiting_permission')
|
|
1141
|
+
this.readiness = 'running';
|
|
987
1142
|
this.events.emit('permission', {
|
|
988
1143
|
turnId: this.activeTurn?.id,
|
|
989
1144
|
origin: this.activeTurn?.origin,
|
|
@@ -1007,12 +1162,59 @@ export class AcpSession {
|
|
|
1007
1162
|
...(rejectOption ? { optionId: rejectOption.optionId } : {}),
|
|
1008
1163
|
},
|
|
1009
1164
|
});
|
|
1010
|
-
|
|
1011
|
-
this.readiness = 'running';
|
|
1165
|
+
this.observation.publish();
|
|
1012
1166
|
}
|
|
1013
1167
|
exitResult() {
|
|
1014
1168
|
return this.exit;
|
|
1015
1169
|
}
|
|
1170
|
+
/** One attempt, tied to the managed live turn captured before any approval/read. */
|
|
1171
|
+
async sendFileToChat(input, signal, copy, policy) {
|
|
1172
|
+
const active = this.activeTurn, sessionId = this.sessionId;
|
|
1173
|
+
const check = () => {
|
|
1174
|
+
signal.throwIfAborted();
|
|
1175
|
+
if (!active || !sessionId || this.activeTurn !== active || this.sessionId !== sessionId || active.fileDeliveryRevoked
|
|
1176
|
+
|| active.cancellationSource || this.closing || !this.isAlive() || this.replaying || this.steeringOccupied
|
|
1177
|
+
|| active.origin?.kind === 'scheduled-loop' || policy.approval === 'deny')
|
|
1178
|
+
throw Error('CURRENT_CHAT_DELIVERY_UNAVAILABLE');
|
|
1179
|
+
};
|
|
1180
|
+
check();
|
|
1181
|
+
if (this.fileExportBusy)
|
|
1182
|
+
throw Error('FILE_DELIVERY_BUSY');
|
|
1183
|
+
this.fileExportBusy = true;
|
|
1184
|
+
const toolCallId = 'fleet-file-export-' + randomUUID();
|
|
1185
|
+
try {
|
|
1186
|
+
const onAbort = () => {
|
|
1187
|
+
for (const [id, pending] of this.pendingPermissions)
|
|
1188
|
+
if (pending.toolCallId === toolCallId)
|
|
1189
|
+
this.settlePendingAutomatically(id, pending, 'cancelled', undefined, 'File send cancelled');
|
|
1190
|
+
};
|
|
1191
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
1192
|
+
let answer;
|
|
1193
|
+
try {
|
|
1194
|
+
answer = await this.requestPermission({ sessionId: sessionId, toolCall: {
|
|
1195
|
+
toolCallId, title: 'Send file to this ACP chat: ' + (input.filename ?? input.path), kind: 'read', status: 'pending',
|
|
1196
|
+
locations: [{ path: resolve(this.options.cwd, input.path) }],
|
|
1197
|
+
}, options: [{ optionId: 'send', name: 'Send file', kind: 'allow_once' }, { optionId: 'deny', name: 'Do not send', kind: 'reject_once' }] }, policy);
|
|
1198
|
+
}
|
|
1199
|
+
finally {
|
|
1200
|
+
signal.removeEventListener('abort', onAbort);
|
|
1201
|
+
}
|
|
1202
|
+
check();
|
|
1203
|
+
if (answer.outcome.outcome !== 'selected' || answer.outcome.optionId !== 'send')
|
|
1204
|
+
throw Error('FILE_SEND_DENIED');
|
|
1205
|
+
const attachment = await copy({ sessionGeneration: this.sessionGeneration, acpSessionId: sessionId, turnId: active.id });
|
|
1206
|
+
// Synchronous check + durable append establishes order with cancellation/stop.
|
|
1207
|
+
// No await, pending-send journal, replay or recovery exists after this point.
|
|
1208
|
+
check();
|
|
1209
|
+
this.conversation.append({ kind: 'file.attached', source: 'agent', sessionGeneration: this.sessionGeneration,
|
|
1210
|
+
acpSessionId: sessionId, promptId: active.id, turnId: active.id, payload: { attachment } });
|
|
1211
|
+
return attachment;
|
|
1212
|
+
}
|
|
1213
|
+
finally {
|
|
1214
|
+
this.releaseTool(toolCallId);
|
|
1215
|
+
this.fileExportBusy = false;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1016
1218
|
async close() {
|
|
1017
1219
|
this.closing = true;
|
|
1018
1220
|
if (this.stallTimer)
|
|
@@ -1066,6 +1268,8 @@ export class AcpSession {
|
|
|
1066
1268
|
const persisted = this.options.mode === 'resume' && existsSync(this.sessionFile)
|
|
1067
1269
|
? readFileSync(this.sessionFile, 'utf8').trim()
|
|
1068
1270
|
: '';
|
|
1271
|
+
if (this.options.requireResume && (!persisted || !(this.agentCapabilities?.sessionCapabilities?.resume != null || this.agentCapabilities?.loadSession)))
|
|
1272
|
+
throw new Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
|
|
1069
1273
|
let advertisedConfigOptions;
|
|
1070
1274
|
let advertisedModes;
|
|
1071
1275
|
let advertisedModelId;
|
|
@@ -1171,6 +1375,7 @@ export class AcpSession {
|
|
|
1171
1375
|
kind: 'session.state', sessionGeneration: this.sessionGeneration,
|
|
1172
1376
|
acpSessionId: this.sessionId, payload: { status: 'idle' },
|
|
1173
1377
|
});
|
|
1378
|
+
this.observation.publish();
|
|
1174
1379
|
}
|
|
1175
1380
|
captureRuntimeMetadata(options, modelId) {
|
|
1176
1381
|
this.runtimeModel = runtimeSelector(options, 'model');
|
|
@@ -1283,8 +1488,9 @@ export class AcpSession {
|
|
|
1283
1488
|
}
|
|
1284
1489
|
}
|
|
1285
1490
|
/** Keep the original queue slot (including startup) until recovery finishes. */
|
|
1286
|
-
async runPrompt(
|
|
1287
|
-
const
|
|
1491
|
+
async runPrompt(members) {
|
|
1492
|
+
const { text, promptId: turnId, origin } = members[0];
|
|
1493
|
+
const result = await this.runSinglePrompt(text, turnId, origin, members);
|
|
1288
1494
|
const attempt = this.stallAttempt;
|
|
1289
1495
|
if (!attempt || attempt.turnId !== turnId)
|
|
1290
1496
|
return result;
|
|
@@ -1308,8 +1514,11 @@ export class AcpSession {
|
|
|
1308
1514
|
attempt.recoveryStartedAt = Date.now();
|
|
1309
1515
|
const recoveryOrigin = origin?.kind === 'scheduled-loop'
|
|
1310
1516
|
? origin : { kind: 'stall-watchdog' };
|
|
1311
|
-
this.
|
|
1312
|
-
|
|
1517
|
+
const recovery = this.observation.checkpoint(() => {
|
|
1518
|
+
this.admitToLedger(attempt.recoveryId, STALL_RECOVERY_PROMPT, 0, { origin: recoveryOrigin });
|
|
1519
|
+
return this.runSinglePrompt(STALL_RECOVERY_PROMPT, attempt.recoveryId, recoveryOrigin);
|
|
1520
|
+
});
|
|
1521
|
+
const recovered = await recovery;
|
|
1313
1522
|
attempt.report(attempt.superseded ? 'superseded'
|
|
1314
1523
|
: recovered.succeeded && attempt.resumed ? 'recovery_completed' : 'blocked_recovery');
|
|
1315
1524
|
// A failed diagnostic turn must not make startup tear down the session.
|
|
@@ -1319,7 +1528,22 @@ export class AcpSession {
|
|
|
1319
1528
|
return turnResult(true, 'cancelled', 'diagnostic recovery requires operator attention', undefined, 'stall-watchdog');
|
|
1320
1529
|
}
|
|
1321
1530
|
}
|
|
1322
|
-
|
|
1531
|
+
/**
|
|
1532
|
+
* One adapter turn. `members` are the admitted prompts it carries, the first
|
|
1533
|
+
* being the turn's own id; each keeps its own started and completed records
|
|
1534
|
+
* so none is left open and none is reported delivered that was not sent.
|
|
1535
|
+
*/
|
|
1536
|
+
async runSinglePrompt(text, turnId = randomUUID(), origin, members = [{ promptId: turnId, text, origin }]) {
|
|
1537
|
+
let completionPayload;
|
|
1538
|
+
const closeMembers = (payload) => {
|
|
1539
|
+
for (const member of members)
|
|
1540
|
+
this.conversation.appendSafe({
|
|
1541
|
+
kind: 'turn.completed', sessionGeneration: this.sessionGeneration,
|
|
1542
|
+
acpSessionId: this.sessionId, promptId: member.promptId, turnId, payload,
|
|
1543
|
+
});
|
|
1544
|
+
};
|
|
1545
|
+
// Not handed to the adapter and not closed: the members stay admitted, and
|
|
1546
|
+
// the next session generation dispatches them again.
|
|
1323
1547
|
if (!this.sessionId || !this.isAlive())
|
|
1324
1548
|
return turnResult(false, 'failed', this.lastError ?? 'ACP session is offline');
|
|
1325
1549
|
this.readiness = 'running';
|
|
@@ -1327,18 +1551,20 @@ export class AcpSession {
|
|
|
1327
1551
|
this.retryNativeTurnId = undefined;
|
|
1328
1552
|
let settle;
|
|
1329
1553
|
const settled = new Promise(resolve => { settle = resolve; });
|
|
1330
|
-
this.activeTurn = { toolEvidence: new Map(), startedAt: Date.now(), toolIds: new Set(), lastProgressAt: 0, progressCount: 0, transportFailures: 0, boundaryUnknown: false, id: turnId, output: '', origin, settled, settle };
|
|
1554
|
+
this.activeTurn = { toolEvidence: new Map(), startedAt: Date.now(), toolIds: new Set(), lastProgressAt: 0, progressCount: 0, transportFailures: 0, boundaryUnknown: false, id: turnId, output: '', origin, settled, settle, promptIds: members.map(member => member.promptId), console: members.some(member => member.origin?.kind === 'owner-admin-console') };
|
|
1555
|
+
this.observation.publish();
|
|
1331
1556
|
this.events.emit('state', { turnId, status: 'running', origin });
|
|
1332
|
-
this.conversation.appendSafe({
|
|
1557
|
+
members.forEach((member, index) => this.conversation.appendSafe({
|
|
1333
1558
|
kind: 'prompt.started', sessionGeneration: this.sessionGeneration,
|
|
1334
|
-
acpSessionId: this.sessionId, promptId:
|
|
1335
|
-
source: conversationSource(origin).source,
|
|
1336
|
-
|
|
1559
|
+
acpSessionId: this.sessionId, promptId: member.promptId, turnId,
|
|
1560
|
+
source: conversationSource(member.origin).source,
|
|
1561
|
+
payload: members.length > 1 ? { batch: { turnId, index, size: members.length } } : {},
|
|
1562
|
+
}));
|
|
1337
1563
|
try {
|
|
1338
1564
|
const response = await Promise.race([
|
|
1339
1565
|
this.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
1340
1566
|
sessionId: this.sessionId,
|
|
1341
|
-
prompt:
|
|
1567
|
+
prompt: batchContentBlocks(members),
|
|
1342
1568
|
}),
|
|
1343
1569
|
this.terminated,
|
|
1344
1570
|
]);
|
|
@@ -1349,15 +1575,11 @@ export class AcpSession {
|
|
|
1349
1575
|
turnId, stopReason: response.stopReason, origin, cancellationSource,
|
|
1350
1576
|
});
|
|
1351
1577
|
this.events.emit('state', { status: 'idle' });
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
stopReason: response.stopReason,
|
|
1358
|
-
...(cancellationSource ? { cancellationSource } : {}),
|
|
1359
|
-
},
|
|
1360
|
-
});
|
|
1578
|
+
completionPayload = {
|
|
1579
|
+
outcome: classifyStopReason(response.stopReason),
|
|
1580
|
+
stopReason: response.stopReason,
|
|
1581
|
+
...(cancellationSource ? { cancellationSource } : {}),
|
|
1582
|
+
};
|
|
1361
1583
|
// The prompt was accepted either way — the agent answered. Whether the
|
|
1362
1584
|
// turn SUCCEEDED is a separate question, and only `stopReason` answers it.
|
|
1363
1585
|
return turnResult(true, classifyStopReason(response.stopReason), response.stopReason, this.activeTurn?.id === turnId ? this.activeTurn.output : undefined, this.activeTurn?.id === turnId ? this.activeTurn.cancellationSource : undefined);
|
|
@@ -1377,11 +1599,7 @@ export class AcpSession {
|
|
|
1377
1599
|
});
|
|
1378
1600
|
if (this.isAlive())
|
|
1379
1601
|
this.events.emit('state', { status: 'idle' });
|
|
1380
|
-
this.
|
|
1381
|
-
kind: 'turn.completed', sessionGeneration: this.sessionGeneration,
|
|
1382
|
-
acpSessionId: this.sessionId, promptId: turnId, turnId,
|
|
1383
|
-
payload: { outcome: 'failed', stopReason: this.lastError },
|
|
1384
|
-
});
|
|
1602
|
+
completionPayload = { outcome: 'failed', stopReason: this.lastError };
|
|
1385
1603
|
return turnResult(false, 'failed', this.lastError, this.activeTurn?.id === turnId ? this.activeTurn.output : undefined);
|
|
1386
1604
|
}
|
|
1387
1605
|
finally {
|
|
@@ -1397,10 +1615,15 @@ export class AcpSession {
|
|
|
1397
1615
|
this.cancelEscalation = undefined;
|
|
1398
1616
|
this.activeTurn = undefined;
|
|
1399
1617
|
}
|
|
1618
|
+
if (completionPayload)
|
|
1619
|
+
closeMembers(completionPayload);
|
|
1620
|
+
this.observation.publish();
|
|
1400
1621
|
}
|
|
1401
1622
|
}
|
|
1402
1623
|
async steerPrompt(text) {
|
|
1403
1624
|
this.steeringWasUsed = true;
|
|
1625
|
+
if (this.activeTurn)
|
|
1626
|
+
this.activeTurn.fileDeliveryRevoked = true;
|
|
1404
1627
|
if (!this.sessionId || !this.isAlive())
|
|
1405
1628
|
return turnResult(false, 'failed', this.lastError ?? 'ACP session is offline');
|
|
1406
1629
|
this.steeringRequests++;
|
|
@@ -1432,7 +1655,7 @@ export class AcpSession {
|
|
|
1432
1655
|
this.steeringRequests--;
|
|
1433
1656
|
}
|
|
1434
1657
|
}
|
|
1435
|
-
requestPermission(params) {
|
|
1658
|
+
requestPermission(params, neutralPolicy) {
|
|
1436
1659
|
// `kinds` is a PRIORITY order. Scanning the agent's option array instead
|
|
1437
1660
|
// (`options.find(o => kinds.includes(o.kind))`) hands the choice to whatever
|
|
1438
1661
|
// order the agent happened to list, which is exactly how an automatic denial
|
|
@@ -1450,7 +1673,7 @@ export class AcpSession {
|
|
|
1450
1673
|
// Permission is part of the tool lifecycle. Reserve before any policy or
|
|
1451
1674
|
// human decision so a monitor wake cannot slip between request and answer.
|
|
1452
1675
|
this.reservePermission(toolCallId, permissionId);
|
|
1453
|
-
if (this.isEffectiveCodexProtectedMcpApproval(params)) {
|
|
1676
|
+
if (!neutralPolicy && this.isEffectiveCodexProtectedMcpApproval(params)) {
|
|
1454
1677
|
// Protected MCP approval is already the tool's narrow gate. Never turn
|
|
1455
1678
|
// this one decision into an adapter-wide standing grant.
|
|
1456
1679
|
const option = choose(['allow_once']);
|
|
@@ -1461,9 +1684,9 @@ export class AcpSession {
|
|
|
1461
1684
|
this.releasePermission(toolCallId, permissionId);
|
|
1462
1685
|
return Promise.resolve(response);
|
|
1463
1686
|
}
|
|
1464
|
-
if (this.options.permissions.approval === 'allow' && this.withinAutomaticBoundary(params)) {
|
|
1687
|
+
if ((neutralPolicy ?? this.options.permissions).approval === 'allow' && this.withinAutomaticBoundary(params, neutralPolicy)) {
|
|
1465
1688
|
const option = choose(['allow_always', 'allow_once']);
|
|
1466
|
-
const response = this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${this.options.permissions.filesystem} boundary`);
|
|
1689
|
+
const response = this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${(neutralPolicy ?? this.options.permissions).filesystem} boundary`);
|
|
1467
1690
|
if (option)
|
|
1468
1691
|
this.allowPermission(toolCallId, permissionId);
|
|
1469
1692
|
else
|
|
@@ -1473,8 +1696,8 @@ export class AcpSession {
|
|
|
1473
1696
|
// A live grace window still counts as attended: the controller may be
|
|
1474
1697
|
// mid-reconnect, and denying instantly is exactly what the grace prevents.
|
|
1475
1698
|
const unattended = this.controllerCount === 0 && !this.controllerGrace
|
|
1476
|
-
&& this.options.permissions.unattended === 'deny';
|
|
1477
|
-
if (this.options.permissions.approval === 'deny' || unattended) {
|
|
1699
|
+
&& (neutralPolicy ?? this.options.permissions).unattended === 'deny';
|
|
1700
|
+
if ((neutralPolicy ?? this.options.permissions).approval === 'deny' || unattended) {
|
|
1478
1701
|
// reject_once FIRST: `reject_always` teaches the agent a standing rule from
|
|
1479
1702
|
// a decision no human made, so one unattended denial would silently disable
|
|
1480
1703
|
// the tool for the rest of the session.
|
|
@@ -1487,42 +1710,20 @@ export class AcpSession {
|
|
|
1487
1710
|
}
|
|
1488
1711
|
const timeoutMs = this.options.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS;
|
|
1489
1712
|
const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
origin: this.activeTurn?.origin,
|
|
1494
|
-
permissionId,
|
|
1495
|
-
toolCallId: this.activeTurn?.origin?.kind === 'scheduled-loop'
|
|
1496
|
-
? 'scheduled-loop-tool' : params.toolCall.toolCallId,
|
|
1497
|
-
title: this.activeTurn?.origin?.kind === 'scheduled-loop'
|
|
1713
|
+
const presented = {
|
|
1714
|
+
toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : toolCallId,
|
|
1715
|
+
title: scheduledTurn(this.activeTurn)
|
|
1498
1716
|
? 'Scheduled-loop permission requested' : params.toolCall.title ?? 'Permission requested',
|
|
1499
|
-
status: 'pending',
|
|
1500
1717
|
options: params.options.map(option => ({
|
|
1501
1718
|
optionId: option.optionId,
|
|
1502
|
-
name: this.activeTurn
|
|
1719
|
+
name: scheduledTurn(this.activeTurn) ? option.kind : option.name,
|
|
1503
1720
|
kind: option.kind,
|
|
1504
1721
|
})),
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
kind: 'permission.requested', sessionGeneration: this.sessionGeneration,
|
|
1508
|
-
acpSessionId: this.sessionId, permissionId,
|
|
1509
|
-
promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
|
|
1510
|
-
toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
|
|
1511
|
-
payload: {
|
|
1512
|
-
toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
|
|
1513
|
-
title: scheduledTurn(this.activeTurn)
|
|
1514
|
-
? 'Scheduled-loop permission requested' : params.toolCall.title ?? 'Permission requested',
|
|
1515
|
-
options: params.options.map(option => ({
|
|
1516
|
-
optionId: option.optionId,
|
|
1517
|
-
name: scheduledTurn(this.activeTurn) ? option.kind : option.name,
|
|
1518
|
-
kind: option.kind,
|
|
1519
|
-
})),
|
|
1520
|
-
expiresAt,
|
|
1521
|
-
},
|
|
1522
|
-
});
|
|
1722
|
+
expiresAt,
|
|
1723
|
+
};
|
|
1523
1724
|
return new Promise(resolve => {
|
|
1524
1725
|
const pending = {
|
|
1525
|
-
options: params.options, resolve,
|
|
1726
|
+
options: params.options, resolve, presented,
|
|
1526
1727
|
toolCallId,
|
|
1527
1728
|
eventToolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : toolCallId,
|
|
1528
1729
|
};
|
|
@@ -1531,6 +1732,18 @@ export class AcpSession {
|
|
|
1531
1732
|
}, timeoutMs);
|
|
1532
1733
|
pending.expiry.unref?.();
|
|
1533
1734
|
this.pendingPermissions.set(permissionId, pending);
|
|
1735
|
+
this.readiness = 'awaiting_permission';
|
|
1736
|
+
this.events.emit('permission', {
|
|
1737
|
+
turnId: this.activeTurn?.id, origin: this.activeTurn?.origin,
|
|
1738
|
+
permissionId, ...presented, status: 'pending',
|
|
1739
|
+
});
|
|
1740
|
+
this.conversation.appendSafe({
|
|
1741
|
+
kind: 'permission.requested', sessionGeneration: this.sessionGeneration,
|
|
1742
|
+
acpSessionId: this.sessionId, permissionId,
|
|
1743
|
+
promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
|
|
1744
|
+
toolCallId: presented.toolCallId, payload: presented,
|
|
1745
|
+
});
|
|
1746
|
+
this.observation.publish();
|
|
1534
1747
|
});
|
|
1535
1748
|
}
|
|
1536
1749
|
/**
|
|
@@ -1575,8 +1788,8 @@ export class AcpSession {
|
|
|
1575
1788
|
? { outcome: { outcome: 'selected', optionId: option.optionId } }
|
|
1576
1789
|
: { outcome: { outcome: 'cancelled' } };
|
|
1577
1790
|
}
|
|
1578
|
-
withinAutomaticBoundary(params) {
|
|
1579
|
-
const filesystem = this.options.permissions.filesystem;
|
|
1791
|
+
withinAutomaticBoundary(params, policy) {
|
|
1792
|
+
const filesystem = (policy ?? this.options.permissions).filesystem;
|
|
1580
1793
|
if (filesystem === 'unrestricted')
|
|
1581
1794
|
return true;
|
|
1582
1795
|
if (filesystem === 'read-only' && params.toolCall.kind !== 'read')
|
|
@@ -1707,9 +1920,19 @@ export class AcpSession {
|
|
|
1707
1920
|
const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
|
|
1708
1921
|
const messagePhase = update.sessionUpdate === 'agent_message_chunk'
|
|
1709
1922
|
? this.codexMessagePhase(update) : undefined;
|
|
1710
|
-
|
|
1923
|
+
// Install live capability state before the durable append can synchronously
|
|
1924
|
+
// notify a subscriber that reenters observation at the new ledger barrier.
|
|
1711
1925
|
if (update.sessionUpdate === 'config_option_update')
|
|
1712
1926
|
this.captureRuntimeMetadata(update.configOptions);
|
|
1927
|
+
if (update.sessionUpdate === 'available_commands_update')
|
|
1928
|
+
this.advertisedCommands = new Set((update.availableCommands ?? []).map(command => command.name));
|
|
1929
|
+
this.recordConversationUpdate(update, scheduled);
|
|
1930
|
+
if (update.sessionUpdate === 'usage_update') {
|
|
1931
|
+
// Claude subscription rate-limit snapshot (claude-agent-acp forwards the SDK rate_limit_event).
|
|
1932
|
+
const rateLimit = update._meta?.['_claude/rateLimit'];
|
|
1933
|
+
if (rateLimit)
|
|
1934
|
+
recordClaudeRateLimit(this.options.stateDir, rateLimit);
|
|
1935
|
+
}
|
|
1713
1936
|
switch (update.sessionUpdate) {
|
|
1714
1937
|
case 'agent_message_chunk': {
|
|
1715
1938
|
const phase = messagePhase;
|
|
@@ -1768,6 +1991,7 @@ export class AcpSession {
|
|
|
1768
1991
|
default:
|
|
1769
1992
|
break;
|
|
1770
1993
|
}
|
|
1994
|
+
this.observation.publish();
|
|
1771
1995
|
}
|
|
1772
1996
|
/**
|
|
1773
1997
|
* Codex ACP's phase extension is the only currently supported visibility
|
|
@@ -1785,13 +2009,17 @@ export class AcpSession {
|
|
|
1785
2009
|
return undefined;
|
|
1786
2010
|
return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
|
|
1787
2011
|
}
|
|
1788
|
-
/**
|
|
1789
|
-
|
|
2012
|
+
/**
|
|
2013
|
+
* Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
|
|
2014
|
+
* owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
|
|
2015
|
+
*/
|
|
2016
|
+
recordConversationUpdate(update, scheduled) {
|
|
1790
2017
|
const normalized = normalizeSessionUpdate(update, scheduled ? {
|
|
1791
2018
|
redactText: SCHEDULED_LOOP_REDACTION,
|
|
1792
2019
|
redactToolCallId: 'scheduled-loop-tool',
|
|
1793
|
-
}
|
|
1794
|
-
|
|
2020
|
+
} : {});
|
|
2021
|
+
if (normalized.kind === 'capabilities.updated' && 'commands' in normalized.payload)
|
|
2022
|
+
this.presentedCommands = normalized.payload.commands ?? [];
|
|
1795
2023
|
this.conversation.appendSafe({
|
|
1796
2024
|
kind: normalized.kind,
|
|
1797
2025
|
sessionGeneration: this.sessionGeneration,
|
|
@@ -1805,6 +2033,10 @@ export class AcpSession {
|
|
|
1805
2033
|
});
|
|
1806
2034
|
}
|
|
1807
2035
|
// ── conversation ledger access (AgentSession) ─────────────────────────────
|
|
2036
|
+
taskNoticeBinding() {
|
|
2037
|
+
return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
|
|
2038
|
+
}
|
|
2039
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
1808
2040
|
conversationPage(request = {}) {
|
|
1809
2041
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
1810
2042
|
const requested = Number(request.after ?? 0);
|
|
@@ -1826,12 +2058,43 @@ export class AcpSession {
|
|
|
1826
2058
|
snapshot: this.conversationSnapshot(),
|
|
1827
2059
|
};
|
|
1828
2060
|
}
|
|
2061
|
+
subscribeConversationFailure(listener) {
|
|
2062
|
+
return this.conversation.subscribeFailure(listener);
|
|
2063
|
+
}
|
|
2064
|
+
conversationReplayAvailable(expectedGeneration) {
|
|
2065
|
+
return expectedGeneration === this.sessionGeneration && this.conversation.generationDurable(expectedGeneration);
|
|
2066
|
+
}
|
|
2067
|
+
replayConversation(request) {
|
|
2068
|
+
if (request.expectedGeneration !== this.sessionGeneration)
|
|
2069
|
+
throw new Error('session generation changed');
|
|
2070
|
+
return this.conversation.replay(request);
|
|
2071
|
+
}
|
|
2072
|
+
observeConversation(listener) {
|
|
2073
|
+
return this.observation.observe(listener);
|
|
2074
|
+
}
|
|
2075
|
+
captureObservation() {
|
|
2076
|
+
const snapshot = this.snapshot();
|
|
2077
|
+
return {
|
|
2078
|
+
...this.conversationSnapshot(),
|
|
2079
|
+
...pendingInventory(this.pendingPrompts),
|
|
2080
|
+
ledgerBarrier: Number(this.conversation.lastCursor() ?? 0),
|
|
2081
|
+
alive: snapshot.alive,
|
|
2082
|
+
readiness: snapshot.alive ? snapshot.readiness : 'offline',
|
|
2083
|
+
activePromptIds: [...(this.activeTurn?.promptIds ?? [])],
|
|
2084
|
+
pendingPermissions: [...this.pendingPermissions].flatMap(([permissionId, pending]) => pending.presented ? [{ permissionId, ...pending.presented }] : []),
|
|
2085
|
+
commands: this.presentedCommands,
|
|
2086
|
+
runtimeModel: snapshot.runtimeModel,
|
|
2087
|
+
reasoningEffort: snapshot.reasoningEffort,
|
|
2088
|
+
permissionMode: snapshot.permissionMode,
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
1829
2091
|
conversationSnapshot() {
|
|
1830
2092
|
return {
|
|
1831
2093
|
sessionGeneration: this.sessionGeneration,
|
|
1832
2094
|
readiness: this.isAlive() ? this.readiness : 'offline',
|
|
1833
2095
|
queueDepth: this.queueDepth,
|
|
1834
2096
|
pendingPermissionIds: [...this.pendingPermissions.keys()],
|
|
2097
|
+
...pendingInventory(this.pendingPrompts),
|
|
1835
2098
|
...(this.conversation.degraded ? { historyDegraded: true } : {}),
|
|
1836
2099
|
};
|
|
1837
2100
|
}
|