@ours.network/fleet 1.2.0-nightly.5 → 1.2.0-nightly.50
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 +263 -53
- package/dist/account-origin.d.ts +2 -0
- package/dist/account-origin.js +6 -0
- package/dist/agent-ours/bridge.js +9 -0
- package/dist/agent-ours/correspondence.d.ts +10 -0
- package/dist/agent-ours/correspondence.js +1 -0
- package/dist/agent-ours/harness.js +8 -0
- package/dist/agent-ours/runtime.d.ts +6 -0
- package/dist/agent-ours/runtime.js +36 -0
- package/dist/agent-ours/service.js +33 -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 +25 -0
- package/dist/application/conversation-history.js +189 -0
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +2 -2
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/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 +1 -1
- package/dist/application/role-removal-service.js +2 -0
- package/dist/application/role-repository.js +2 -2
- package/dist/application/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +30 -0
- package/dist/application/session-control.js +72 -3
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +113 -0
- package/dist/application/task-layouts.d.ts +91 -0
- package/dist/application/task-layouts.js +249 -0
- package/dist/application/task-room-service.d.ts +99 -3
- package/dist/application/task-room-service.js +296 -31
- package/dist/atomic-file.d.ts +5 -0
- package/dist/atomic-file.js +62 -1
- package/dist/briefing.d.ts +2 -0
- package/dist/briefing.js +8 -1
- package/dist/build-info.json +7 -4
- package/dist/capabilities.d.ts +6 -1
- package/dist/capabilities.js +8 -0
- package/dist/cli.js +376 -34
- package/dist/client-profile.d.ts +5 -17
- package/dist/client-profile.js +4 -127
- package/dist/config.d.ts +28 -0
- package/dist/config.js +41 -6
- package/dist/creation.js +2 -2
- package/dist/daemon-recovery.d.ts +1 -8
- package/dist/daemon-recovery.js +8 -113
- package/dist/docs.d.ts +3 -3
- package/dist/docs.js +126 -22
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +55 -60
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +7 -0
- package/dist/fleet-command-audit.js +26 -9
- package/dist/harness/claude-code.js +9 -1
- 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.d.ts +3 -2
- package/dist/harness/codex.js +20 -11
- package/dist/init-wizard.d.ts +2 -0
- package/dist/init-wizard.js +38 -19
- package/dist/isolation/policy.js +2 -0
- 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/minimal-setup.d.ts +2 -0
- package/dist/minimal-setup.js +44 -0
- package/dist/monitor.d.ts +1 -34
- package/dist/monitor.js +24 -151
- package/dist/notifications/fleet-producer.d.ts +19 -0
- package/dist/notifications/fleet-producer.js +208 -0
- package/dist/notifications/outbox.d.ts +46 -0
- package/dist/notifications/outbox.js +158 -0
- package/dist/notifications/target-cleanup.d.ts +8 -0
- package/dist/notifications/target-cleanup.js +41 -0
- package/dist/ops.js +5 -1
- package/dist/owner-channel/channel.d.ts +3 -0
- package/dist/owner-channel/channel.js +34 -6
- package/dist/owner-channel/commands.d.ts +4 -0
- package/dist/owner-channel/commands.js +8 -0
- package/dist/owner-channel/lifecycle-outbox.js +4 -3
- package/dist/owner-channel/ours-client.js +2 -17
- package/dist/paths.d.ts +2 -0
- package/dist/paths.js +2 -0
- package/dist/preset-migration.js +25 -0
- package/dist/resolved-plan.js +1 -0
- package/dist/rooms-tasks/add-member.d.ts +48 -0
- package/dist/rooms-tasks/add-member.js +83 -0
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +34 -5
- package/dist/rooms-tasks/close.d.ts +5 -2
- package/dist/rooms-tasks/close.js +208 -28
- 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 +6 -4
- package/dist/rooms-tasks/cowork-adapter.js +8 -121
- package/dist/rooms-tasks/deletion.d.ts +1 -1
- package/dist/rooms-tasks/deletion.js +74 -5
- package/dist/rooms-tasks/erasure.d.ts +6 -0
- package/dist/rooms-tasks/erasure.js +169 -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-service.d.ts +42 -0
- package/dist/rooms-tasks/layout-service.js +185 -0
- package/dist/rooms-tasks/layout.d.ts +114 -0
- package/dist/rooms-tasks/layout.js +250 -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-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +14 -0
- package/dist/rooms-tasks/provision.js +173 -46
- package/dist/rooms-tasks/room-state.d.ts +4 -2
- package/dist/rooms-tasks/room-state.js +53 -8
- package/dist/rooms-tasks/task-state.d.ts +42 -7
- package/dist/rooms-tasks/task-state.js +153 -23
- package/dist/rooms-tasks/types.d.ts +28 -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 +26 -0
- package/dist/rooms-tasks/workspace.js +195 -0
- package/dist/runner.d.ts +19 -3
- package/dist/runner.js +128 -45
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +31 -6
- package/dist/session/arbiter.d.ts +5 -0
- package/dist/session/arbiter.js +28 -2
- package/dist/session/codex-app-server.d.ts +5 -0
- package/dist/session/codex-app-server.js +36 -3
- package/dist/session/control.d.ts +12 -1
- package/dist/session/control.js +68 -3
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-store.d.ts +1 -1
- package/dist/session/conversation-store.js +7 -11
- package/dist/session/conversation-types.d.ts +7 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +6 -0
- package/dist/setup-tunnel-preflight.d.ts +34 -0
- package/dist/setup-tunnel-preflight.js +62 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +8 -1
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/subscriptions/cli.d.ts +91 -0
- package/dist/subscriptions/cli.js +223 -0
- package/dist/subscriptions/launch.d.ts +37 -0
- package/dist/subscriptions/launch.js +93 -0
- package/dist/subscriptions/login.d.ts +75 -0
- package/dist/subscriptions/login.js +182 -0
- package/dist/subscriptions/observed.d.ts +7 -0
- package/dist/subscriptions/observed.js +44 -0
- package/dist/subscriptions/probe.d.ts +10 -0
- package/dist/subscriptions/probe.js +73 -0
- package/dist/subscriptions/service.d.ts +102 -0
- package/dist/subscriptions/service.js +364 -0
- package/dist/subscriptions/store.d.ts +71 -0
- package/dist/subscriptions/store.js +140 -0
- package/dist/supervisor/launchd.js +2 -1
- package/dist/supervisor/systemd.js +9 -23
- package/dist/temp-idle.d.ts +17 -0
- package/dist/temp-idle.js +42 -0
- package/dist/temp-lifecycle.d.ts +6 -1
- package/dist/temp-lifecycle.js +29 -13
- 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 +29 -1
- package/dist/web/auth.js +82 -7
- package/dist/web/available-port.d.ts +2 -0
- package/dist/web/available-port.js +23 -0
- package/dist/web/control.d.ts +3 -3
- package/dist/web/control.js +10 -8
- package/dist/web/fleet-config-service.d.ts +4 -1
- package/dist/web/fleet-config-service.js +46 -15
- package/dist/web/prefix-gateway.d.ts +32 -0
- package/dist/web/prefix-gateway.js +234 -0
- package/dist/web/runtime.d.ts +3 -1
- package/dist/web/runtime.js +116 -31
- package/dist/web/server.d.ts +16 -0
- package/dist/web/server.js +588 -53
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +36 -0
- package/dist/web/workspace-devices.js +101 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +34 -0
- package/dist/workspace-enrollment.d.ts +82 -0
- package/dist/workspace-enrollment.js +566 -0
- package/dist/workspace-replacement.d.ts +66 -0
- package/dist/workspace-replacement.js +220 -0
- package/docs/live-voice.md +13 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -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 +15 -18
- package/presets/README.md +78 -0
- package/presets/brain-catalog.json +8 -2
- package/presets/fleet/agent_templates/Critic.yaml +1 -1
- package/presets/fleet/agent_templates/Developer.yaml +1 -1
- package/presets/fleet/agent_templates/Engineer.yaml +5 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
- package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +34 -11
- package/presets/fleet/roles/Engineer.yaml +40 -0
- package/presets/fleet/room_layouts/engineering.yaml +25 -0
- package/presets/fleet/room_layouts/pair.yaml +24 -0
- package/presets/fleet/room_layouts/single.yaml +17 -0
- package/presets/fleet/room_layouts/team.yaml +30 -0
- package/presets/fleet/room_templates/engineering.yaml +19 -0
- package/presets/manifest.json +2 -2
- package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
- package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
- package/dist/web-app/icons/ours-fleet.svg +0 -4
- package/dist/web-app/index.html +0 -17
- package/dist/web-app/manifest.webmanifest +0 -15
- package/dist/web-app/offline.html +0 -18
- package/dist/web-app/sw.js +0 -51
package/dist/runner.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
|
|
2
2
|
import { type ResolvedRole } from './config.js';
|
|
3
|
-
import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
|
|
3
|
+
import { probeIdentityPresence, type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
|
|
4
4
|
import { type DaemonGenerationProbe } from './daemon-recovery.js';
|
|
5
5
|
import { type Exec } from './exec.js';
|
|
6
6
|
import { RoleControlServer } from './session/control.js';
|
|
@@ -20,6 +20,7 @@ export interface RunnerDeps {
|
|
|
20
20
|
log(line: string): void;
|
|
21
21
|
/** HTTP transport for the monitor's daemon long-poll (injectable for tests). */
|
|
22
22
|
fetch: FetchLike;
|
|
23
|
+
identityProbeDeps?: Parameters<typeof probeIdentityPresence>[3];
|
|
23
24
|
probeGeneration(env: NodeJS.ProcessEnv): Promise<DaemonGenerationProbe>;
|
|
24
25
|
/** Construct the supervisor mail monitor (injectable so tests stub it out). */
|
|
25
26
|
createMonitor(opts: MonitorOpts): MonitorHandle;
|
|
@@ -29,7 +30,7 @@ export interface RunnerDeps {
|
|
|
29
30
|
/** Neutral construction seam for deterministic runner lifecycle tests. */
|
|
30
31
|
startAgentSession(adapter: AgentSessionAdapter, options: AgentSessionStartOptions): Promise<AgentSession>;
|
|
31
32
|
/** Construct the authenticated role control route (injectable where sockets are unavailable). */
|
|
32
|
-
createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'
|
|
33
|
+
createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'> & Partial<Pick<RoleControlServer, 'setLayoutControl' | 'setCorrespondence'>>;
|
|
33
34
|
/** Construct scheduled-loop execution (injectable for fail-closed startup tests). */
|
|
34
35
|
createLoopManager(...args: ConstructorParameters<typeof ScheduledLoopManager>): ScheduledLoopManagerHandle;
|
|
35
36
|
/** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
|
|
@@ -44,6 +45,11 @@ export declare class SupervisorRecycleRequiredError extends Error {
|
|
|
44
45
|
readonly code = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
|
|
45
46
|
constructor();
|
|
46
47
|
}
|
|
48
|
+
export declare function localFleetAuditor(stateDir: string, caller: string, log: (line: string) => void, session: AgentSession): {
|
|
49
|
+
begin(requestId: string, argv: string[]): Promise<import("./fleet-command-audit.js").FleetAuditAttempt>;
|
|
50
|
+
finish(input: Parameters<NonNullable<OwnerChannelHandle["finishFleetCommandAudit"]>>[0]): Promise<import("./fleet-command-audit.js").FleetAuditAttempt>;
|
|
51
|
+
present(): Promise<void>;
|
|
52
|
+
};
|
|
47
53
|
/** Environment injected only into the managed harness process. */
|
|
48
54
|
export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
|
|
49
55
|
/**
|
|
@@ -149,6 +155,16 @@ export declare const START_STAGGER_FILE = ".start-stagger-ms";
|
|
|
149
155
|
export declare function reserveLaunchSlot(root: string, staggerMs: number, deps: Pick<RunnerDeps, 'now' | 'sleep' | 'log'>): Promise<number>;
|
|
150
156
|
/** Read a temp role's config snapshot written by spawnTemp. */
|
|
151
157
|
export declare function loadTempRole(name: string): ResolvedRole;
|
|
158
|
+
/**
|
|
159
|
+
* Fall back to the config path applyRole() recorded at the last up/restart/spawn
|
|
160
|
+
* for this role. systemd's shared unit template (`ours-fleet-agent@.service`)
|
|
161
|
+
* execs `_run <name>` with no -c on every restart, so a role brought up from a
|
|
162
|
+
* non-default config (`ours-fleet up -c custom.yaml`) would otherwise silently
|
|
163
|
+
* resolve against the default ~/fleet.yaml on its very first restart and fail
|
|
164
|
+
* with "no such role". An empty/missing marker means "use the default", same as
|
|
165
|
+
* no -c was ever given.
|
|
166
|
+
*/
|
|
167
|
+
export declare function resolveConfigPath(dir: string, explicit?: string): string | undefined;
|
|
152
168
|
/** What one child session did, so the supervising loop can decide what follows. */
|
|
153
169
|
export interface AttemptResult {
|
|
154
170
|
elapsedSecs: number;
|
|
@@ -158,7 +174,7 @@ export interface AttemptResult {
|
|
|
158
174
|
mode: 'fresh' | 'resume';
|
|
159
175
|
modelRecovery?: 'advance' | 'hold';
|
|
160
176
|
/** Present only when a temporary-role lifecycle signal ended the session. */
|
|
161
|
-
retirementReason?: 'identity-closed' | 'operator-stop' | 'supervisor-signal';
|
|
177
|
+
retirementReason?: 'identity-closed' | 'operator-stop' | 'supervisor-signal' | 'idle-timeout';
|
|
162
178
|
}
|
|
163
179
|
/** Continuous authoritative absence required after an identity was observed. */
|
|
164
180
|
export declare const TEMP_IDENTITY_CLOSE_DEBOUNCE_MS = 5000;
|
package/dist/runner.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { createLayoutControl } from './rooms-tasks/layout-control.js';
|
|
2
|
+
import { TemporaryChatIdle } from './temp-idle.js';
|
|
1
3
|
import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
|
|
2
4
|
import { prepareManagedHarness } from './agent-ours/harness.js';
|
|
5
|
+
import { prepareManagedCliLaunch } from './managed-cli.js';
|
|
3
6
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, realpathSync } from 'node:fs';
|
|
4
7
|
import { join } from 'node:path';
|
|
5
8
|
import { fileURLToPath } from 'node:url';
|
|
6
9
|
import { randomUUID } from 'node:crypto';
|
|
7
10
|
import { parse } from 'yaml';
|
|
8
11
|
import { agentDir, stateRoot } from './paths.js';
|
|
9
|
-
import { loadConfig, findRole, isolationContextFor,
|
|
12
|
+
import { loadConfig, findRole, isolationContextFor, resolveSnapshotMonitor, resolvePermissions, } from './config.js';
|
|
10
13
|
import { getAdapter } from './harness/registry.js';
|
|
11
14
|
import { createMonitor, probeIdentityPresence, } from './monitor.js';
|
|
12
15
|
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
|
|
13
16
|
import { realExec } from './exec.js';
|
|
14
17
|
import { resolveIsolation } from './isolation/policy.js';
|
|
18
|
+
import { pinSubscriptionForLaunch } from './subscriptions/launch.js';
|
|
15
19
|
import { selectIsolationBackend } from './isolation/registry.js';
|
|
16
20
|
import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
|
|
17
21
|
import { resolveLaunchRuntime } from './isolation/runtime.js';
|
|
@@ -75,12 +79,16 @@ const defaultDeps = () => ({
|
|
|
75
79
|
});
|
|
76
80
|
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
77
81
|
const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
|
|
78
|
-
function localFleetAuditor(stateDir, caller, log) {
|
|
82
|
+
export function localFleetAuditor(stateDir, caller, log, session) {
|
|
83
|
+
const bindings = new Map();
|
|
79
84
|
const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
|
|
80
85
|
return {
|
|
81
86
|
async begin(requestId, argv) {
|
|
82
87
|
let attempt = store.begin(requestId, caller, argv);
|
|
83
88
|
if (attempt.invocation === 'sending') {
|
|
89
|
+
const binding = session.taskNoticeBinding?.();
|
|
90
|
+
if (binding)
|
|
91
|
+
bindings.set(attempt.correlationId, binding);
|
|
84
92
|
log(`[${caller}] fleet proxy command ${attempt.correlationId} `
|
|
85
93
|
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
86
94
|
attempt = store.invocation(attempt.correlationId, caller, 'delivered');
|
|
@@ -94,6 +102,19 @@ function localFleetAuditor(stateDir, caller, log) {
|
|
|
94
102
|
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
95
103
|
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
96
104
|
});
|
|
105
|
+
const binding = bindings.get(attempt.correlationId);
|
|
106
|
+
if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
|
|
107
|
+
for (const p of attempt.outcome.presentations ?? [])
|
|
108
|
+
if (p.kind === 'task' && p.operation === 'create') {
|
|
109
|
+
try {
|
|
110
|
+
session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
log(`[${caller}] task-created conversation notice could not be persisted`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
bindings.delete(attempt.correlationId);
|
|
117
|
+
}
|
|
97
118
|
if (attempt.outcome?.delivery === 'sending')
|
|
98
119
|
attempt = store.outcome(attempt.correlationId, caller, 'delivered');
|
|
99
120
|
return attempt;
|
|
@@ -414,7 +435,7 @@ export function loadTempRole(name) {
|
|
|
414
435
|
// Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
|
|
415
436
|
// with no monitor block keeps the historical native/no-supervisor behavior.
|
|
416
437
|
if (role.monitor)
|
|
417
|
-
role.monitor =
|
|
438
|
+
role.monitor = resolveSnapshotMonitor(role.monitor);
|
|
418
439
|
role.__temp = true;
|
|
419
440
|
return role;
|
|
420
441
|
}
|
|
@@ -427,7 +448,7 @@ export function loadTempRole(name) {
|
|
|
427
448
|
* with "no such role". An empty/missing marker means "use the default", same as
|
|
428
449
|
* no -c was ever given.
|
|
429
450
|
*/
|
|
430
|
-
function resolveConfigPath(dir, explicit) {
|
|
451
|
+
export function resolveConfigPath(dir, explicit) {
|
|
431
452
|
if (explicit)
|
|
432
453
|
return explicit;
|
|
433
454
|
const marker = join(dir, '.config-path');
|
|
@@ -502,10 +523,22 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
502
523
|
writeFileSync(bootedFile, `${new Date(deps.now()).toISOString()} ${mode}\n`);
|
|
503
524
|
const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
|
|
504
525
|
role = { ...role, monitor: { ...role.monitor, mode: 'fleet' } };
|
|
505
|
-
|
|
526
|
+
// Native policy for Fleet commands run from the harness command sandbox. This
|
|
527
|
+
// is the same step for configured, spawned and task-room agents alike.
|
|
528
|
+
const prep = prepareManagedCliLaunch(role, {
|
|
529
|
+
// A temporary agent has no config path threaded through its detached
|
|
530
|
+
// supervisor; spawn records the one it was created from beside its snapshot.
|
|
531
|
+
stateDir: dir, runCwd, configPath: configPath ?? resolveConfigPath(dir), temp,
|
|
532
|
+
prep: await adapter.prepareSession(role, { stateDir: dir, runCwd }), log: deps.log,
|
|
533
|
+
});
|
|
506
534
|
const sessionBackend = role.session ?? 'acp';
|
|
507
535
|
const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
|
|
508
536
|
let launch = adapter.agentSession.prepareLaunch(role, prep);
|
|
537
|
+
// Resolve the fleet-wide active subscription profile once, now: a later switch
|
|
538
|
+
// never changes this session (the operator restarts agents to move them).
|
|
539
|
+
const subscription = pinSubscriptionForLaunch(role, dir);
|
|
540
|
+
if (subscription?.pin.home)
|
|
541
|
+
deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
|
|
509
542
|
// Preserve the role's existing isolation policy.
|
|
510
543
|
let wrappedArgv = launch.argv;
|
|
511
544
|
if (role.isolation) {
|
|
@@ -516,6 +549,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
516
549
|
launch = { ...launch, argv: runtime.argv };
|
|
517
550
|
const ctx = {
|
|
518
551
|
...isolationContextFor(role), stateDir: dir, runCwd,
|
|
552
|
+
subscriptionHome: subscription?.writablePath,
|
|
519
553
|
runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
|
|
520
554
|
};
|
|
521
555
|
const policy = resolveIsolation(role.isolation, ctx);
|
|
@@ -539,8 +573,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
539
573
|
wrappedArgv = [...rprefix, ...wrappedArgv];
|
|
540
574
|
}
|
|
541
575
|
const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
|
|
576
|
+
let layoutRetiring = false;
|
|
542
577
|
try {
|
|
543
|
-
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
|
|
578
|
+
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
|
|
544
579
|
// Start-stagger: space this launch at least start_stagger_ms after the previous
|
|
545
580
|
// agent launch across the whole host, so a burst of boots (systemd starts every
|
|
546
581
|
// user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
|
|
@@ -599,6 +634,16 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
599
634
|
let unsubscribeRecovery;
|
|
600
635
|
let monitorLoop;
|
|
601
636
|
let sessionStartupComplete = false;
|
|
637
|
+
let startupRecoveryAllowed = false;
|
|
638
|
+
let successfulTurnObserved = false;
|
|
639
|
+
let monitorMayStart = false;
|
|
640
|
+
let monitorStarted = false;
|
|
641
|
+
const startMonitorAfterStartup = () => {
|
|
642
|
+
if (!monitorStarted && monitorMayStart && sessionStartupComplete && !deps.shouldStop?.()) {
|
|
643
|
+
monitorStarted = true;
|
|
644
|
+
monitorLoop = monitor?.run(pid);
|
|
645
|
+
}
|
|
646
|
+
};
|
|
602
647
|
let sessionClosed = false;
|
|
603
648
|
let ownerChannel;
|
|
604
649
|
let ownerBinder;
|
|
@@ -627,13 +672,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
627
672
|
pid = agentSession.pid;
|
|
628
673
|
arbiter = new RoleTurnArbiter(agentSession);
|
|
629
674
|
sessionHandle = arbiter;
|
|
630
|
-
|
|
675
|
+
const unsubscribeFailure = agentSession.subscribe(event => {
|
|
631
676
|
if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
|
|
632
677
|
return;
|
|
633
678
|
const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
|
|
634
679
|
if (evidence)
|
|
635
680
|
resolvedMonitorDeps.onFailureEvidence?.(evidence);
|
|
636
681
|
});
|
|
682
|
+
const unsubscribeStartup = agentSession.subscribeConversation?.(event => {
|
|
683
|
+
if (event.kind !== 'turn.completed' || !('outcome' in event.payload) || event.payload.outcome !== 'completed')
|
|
684
|
+
return;
|
|
685
|
+
successfulTurnObserved = true;
|
|
686
|
+
if (startupRecoveryAllowed) {
|
|
687
|
+
sessionStartupComplete = true;
|
|
688
|
+
startMonitorAfterStartup();
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); };
|
|
637
692
|
if (role.owner_channel) {
|
|
638
693
|
try {
|
|
639
694
|
ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
|
|
@@ -662,6 +717,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
662
717
|
harness: role.harness,
|
|
663
718
|
config: role.owner_channel,
|
|
664
719
|
session: arbiter,
|
|
720
|
+
startupPending: () => !sessionStartupComplete,
|
|
665
721
|
stateDir: dir,
|
|
666
722
|
env: role.env,
|
|
667
723
|
log: deps.log,
|
|
@@ -669,11 +725,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
669
725
|
...(configPath ? { configPath } : {}),
|
|
670
726
|
});
|
|
671
727
|
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
728
|
+
control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
|
|
672
729
|
control.setFleetAuditor(ownerChannel ? {
|
|
673
730
|
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
674
731
|
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
675
732
|
present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
|
|
676
|
-
} : localFleetAuditor(dir, name, deps.log));
|
|
733
|
+
} : localFleetAuditor(dir, name, deps.log, agentSession));
|
|
734
|
+
control.setLayoutControl?.(createLayoutControl({
|
|
735
|
+
agent: name, temporary: temp, standalone: !role.roomMemberStartup,
|
|
736
|
+
runtime: managedService.runtime, session: arbiter,
|
|
737
|
+
stopping: () => Boolean(deps.shouldStop?.()),
|
|
738
|
+
retire: async () => { layoutRetiring = true; await arbiter.close(); },
|
|
739
|
+
}));
|
|
677
740
|
await control.start();
|
|
678
741
|
}
|
|
679
742
|
catch (error) {
|
|
@@ -704,10 +767,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
704
767
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
705
768
|
// the monitor must keep its cursor and try again.
|
|
706
769
|
submit: async (text, options) => {
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
// steer into the live turn instead; after it completes, honor the
|
|
710
|
-
// configured interrupt policy normally.
|
|
770
|
+
// Delivery starts after the protected startup turn succeeds. Honor the
|
|
771
|
+
// configured interrupt policy for subsequent turns.
|
|
711
772
|
const policy = options?.interrupt;
|
|
712
773
|
const interrupt = policy === true && sessionStartupComplete;
|
|
713
774
|
const promptOptions = {
|
|
@@ -715,8 +776,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
715
776
|
...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
|
|
716
777
|
origin: { kind: 'fleet-monitor' },
|
|
717
778
|
};
|
|
718
|
-
//
|
|
719
|
-
// steer rather than waiting on/cancelling the runner-owned first turn.
|
|
779
|
+
// after_tool waits for a safe boundary in subsequent active turns.
|
|
720
780
|
const result = policy === 'after_tool' && sessionStartupComplete
|
|
721
781
|
? await arbiter.submitPromptAfterTool(text, promptOptions)
|
|
722
782
|
: await arbiter.submitPrompt(text, promptOptions);
|
|
@@ -752,10 +812,28 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
752
812
|
// startup prompt and then refuses it has not started; logging the role as
|
|
753
813
|
// up would hide a role that never read its briefing.
|
|
754
814
|
const starting = arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
|
|
755
|
-
//
|
|
756
|
-
//
|
|
757
|
-
//
|
|
758
|
-
|
|
815
|
+
// Keep the early cursor baseline; do not deliver wakes into the protected
|
|
816
|
+
// first turn. Steering can pre-empt startup just like cancellation.
|
|
817
|
+
// Owner traffic and agent replies must be observed during a long first
|
|
818
|
+
// turn. Ordinary owner input steers until startup has proved successful.
|
|
819
|
+
if (ownerChannel) {
|
|
820
|
+
try {
|
|
821
|
+
await ownerChannel.start();
|
|
822
|
+
control.setOwnerChannel(ownerChannel);
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
monitor?.stop();
|
|
826
|
+
if (monitorLoop)
|
|
827
|
+
await monitorLoop;
|
|
828
|
+
await control.close();
|
|
829
|
+
await ownerChannel.close().catch(() => undefined);
|
|
830
|
+
ownerBinder?.release();
|
|
831
|
+
await agentSession.close();
|
|
832
|
+
unsubscribeRecovery?.();
|
|
833
|
+
throw new Error(`[${name}] owner channel failed to start: `
|
|
834
|
+
+ `${error?.message ?? String(error)}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
759
837
|
const started = await starting;
|
|
760
838
|
// A temporary role's first turn can be the active turn when an ours wake
|
|
761
839
|
// needs immediate attention. A typed console/monitor cancellation ends
|
|
@@ -768,6 +846,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
768
846
|
if (!started.succeeded && !interruptedForWake) {
|
|
769
847
|
monitor?.stop();
|
|
770
848
|
await control.close();
|
|
849
|
+
await ownerChannel?.close().catch(() => undefined);
|
|
771
850
|
ownerBinder?.release();
|
|
772
851
|
await agentSession.close();
|
|
773
852
|
unsubscribeRecovery?.();
|
|
@@ -794,27 +873,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
794
873
|
else if (interruptedForWake)
|
|
795
874
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
796
875
|
+ 'keeping temporary supervisor alive');
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
try {
|
|
800
|
-
await ownerChannel.start();
|
|
801
|
-
}
|
|
802
|
-
catch (error) {
|
|
803
|
-
monitor?.stop();
|
|
804
|
-
if (monitorLoop)
|
|
805
|
-
await monitorLoop;
|
|
806
|
-
await ownerChannel.close().catch(() => undefined);
|
|
807
|
-
await control.close();
|
|
808
|
-
ownerBinder?.release();
|
|
809
|
-
await agentSession.close();
|
|
810
|
-
unsubscribeRecovery?.();
|
|
811
|
-
throw new Error(`[${name}] owner channel failed to start: `
|
|
812
|
-
+ `${error?.message ?? String(error)}`);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if (ownerChannel) {
|
|
816
|
-
control.setOwnerChannel(ownerChannel);
|
|
817
|
-
}
|
|
876
|
+
startupRecoveryAllowed = interruptedForWake;
|
|
877
|
+
sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
|
|
818
878
|
reloadLoopConfig = async () => {
|
|
819
879
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
820
880
|
const definitions = nextRole.loops ?? [];
|
|
@@ -871,9 +931,10 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
871
931
|
}
|
|
872
932
|
}
|
|
873
933
|
deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
|
|
874
|
-
//
|
|
875
|
-
//
|
|
876
|
-
|
|
934
|
+
// Keep arrivals behind the early prime() cursor; never re-prime at startup.
|
|
935
|
+
// Recoverable cancellation preserves the supervisor, not monitor readiness.
|
|
936
|
+
monitorMayStart = true;
|
|
937
|
+
startMonitorAfterStartup();
|
|
877
938
|
recoveryController = new RoleRecoveryController({
|
|
878
939
|
role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
|
|
879
940
|
log: deps.log,
|
|
@@ -900,6 +961,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
900
961
|
}
|
|
901
962
|
},
|
|
902
963
|
});
|
|
964
|
+
let chatIdle;
|
|
965
|
+
try {
|
|
966
|
+
if (temp && !role.roomMemberStartup)
|
|
967
|
+
chatIdle = new TemporaryChatIdle(dir, deps.now());
|
|
968
|
+
}
|
|
969
|
+
catch {
|
|
970
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
|
|
971
|
+
}
|
|
903
972
|
const start = deps.now();
|
|
904
973
|
let nextLoopReloadAt = deps.now() + 30_000;
|
|
905
974
|
let nextIdentityPollAt = deps.now();
|
|
@@ -923,6 +992,19 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
923
992
|
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
924
993
|
}
|
|
925
994
|
}
|
|
995
|
+
let idleExpired = false;
|
|
996
|
+
try {
|
|
997
|
+
idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
|
|
998
|
+
}
|
|
999
|
+
catch {
|
|
1000
|
+
chatIdle = undefined;
|
|
1001
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
|
|
1002
|
+
}
|
|
1003
|
+
if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
|
|
1004
|
+
retirementReason = 'idle-timeout';
|
|
1005
|
+
sessionClosed = true;
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
926
1008
|
if (deps.shouldStop?.()) {
|
|
927
1009
|
if (temp) {
|
|
928
1010
|
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
@@ -934,7 +1016,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
934
1016
|
}
|
|
935
1017
|
if (temp && now >= nextIdentityPollAt) {
|
|
936
1018
|
nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
|
|
937
|
-
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env);
|
|
1019
|
+
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
|
|
938
1020
|
if (presence.state === 'present') {
|
|
939
1021
|
identityObserved = true;
|
|
940
1022
|
identityAbsentSince = undefined;
|
|
@@ -971,6 +1053,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
971
1053
|
}
|
|
972
1054
|
}
|
|
973
1055
|
}
|
|
1056
|
+
monitorMayStart = false;
|
|
974
1057
|
if (loopManager) {
|
|
975
1058
|
control?.setLoopManager(undefined);
|
|
976
1059
|
await loopManager.stop();
|
|
@@ -1051,11 +1134,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1051
1134
|
throw new SupervisorRecycleRequiredError();
|
|
1052
1135
|
return {
|
|
1053
1136
|
elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
|
|
1054
|
-
...(retirementReason ? { retirementReason } : {}),
|
|
1137
|
+
...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
|
|
1055
1138
|
};
|
|
1056
1139
|
}
|
|
1057
1140
|
finally {
|
|
1058
|
-
await managedService.close(
|
|
1141
|
+
await managedService.close(layoutRetiring);
|
|
1059
1142
|
}
|
|
1060
1143
|
}
|
|
1061
1144
|
/**
|
package/dist/session/acp.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
|
|
1
2
|
import * as acp from '@agentclientprotocol/sdk';
|
|
2
3
|
import type { CommonPermissions } from '../config.js';
|
|
3
4
|
import type { AcpMcpServer } from '../harness/types.js';
|
|
@@ -271,6 +272,8 @@ export declare class AcpSession implements AgentSession {
|
|
|
271
272
|
* (`queuePrompt({ interrupt: true })`) needs the typed error, because only it
|
|
272
273
|
* still owes an undelivered message a replay.
|
|
273
274
|
*/
|
|
275
|
+
readonly liveVoiceSupported = true;
|
|
276
|
+
interruptPrompt(generation: string, promptId: string): Promise<InterruptOutcome>;
|
|
274
277
|
interrupt(source?: TurnCancellationSource): Promise<InterruptOutcome>;
|
|
275
278
|
private cancelActive;
|
|
276
279
|
/**
|
|
@@ -338,8 +341,13 @@ export declare class AcpSession implements AgentSession {
|
|
|
338
341
|
* signal. Never infer commentary from text, message order, or unknown meta.
|
|
339
342
|
*/
|
|
340
343
|
private codexMessagePhase;
|
|
341
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
|
|
346
|
+
* owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
|
|
347
|
+
*/
|
|
342
348
|
private recordConversationUpdate;
|
|
349
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
350
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
343
351
|
conversationPage(request?: {
|
|
344
352
|
after?: string;
|
|
345
353
|
limit?: number;
|
package/dist/session/acp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appendTaskCreated } from './task-notice.js';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
3
4
|
import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
|
|
@@ -7,6 +8,7 @@ import * as acp from '@agentclientprotocol/sdk';
|
|
|
7
8
|
import { normalizeSessionUpdate } from './conversation-normalizer.js';
|
|
8
9
|
import { ConversationEventStore } from './conversation-store.js';
|
|
9
10
|
import { SessionEvents } from './events.js';
|
|
11
|
+
import { recordClaudeRateLimit } from '../subscriptions/observed.js';
|
|
10
12
|
import { DEFAULT_STALL_TIMEOUT_MS, STALL_RECOVERY_PROMPT, StallWatchdog, StallToolHistory, hasStallRecoveryClaim } from './stall-watchdog.js';
|
|
11
13
|
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, sessionBackendCapabilities, turnResult, } from './types.js';
|
|
12
14
|
const CANCEL_SETTLE_GRACE_MS = 15_000;
|
|
@@ -36,7 +38,6 @@ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
|
|
|
36
38
|
/** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
|
|
37
39
|
export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
|
|
38
40
|
const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
|
|
39
|
-
const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
|
|
40
41
|
const MAX_CANONICAL_SYMLINK_DEPTH = 40;
|
|
41
42
|
const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
|
|
42
43
|
/**
|
|
@@ -760,6 +761,10 @@ export class AcpSession {
|
|
|
760
761
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
761
762
|
if (existing)
|
|
762
763
|
return existing;
|
|
764
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
765
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
766
|
+
if (command.requireIdle && (this.queueDepth > 0 || this.conversationSnapshot().readiness !== 'idle'))
|
|
767
|
+
throw new Error('voice_busy: Agent is already working. Wait for the current turn.');
|
|
763
768
|
const queued = await this.queuePrompt(command.text, {
|
|
764
769
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
765
770
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -792,6 +797,14 @@ export class AcpSession {
|
|
|
792
797
|
* (`queuePrompt({ interrupt: true })`) needs the typed error, because only it
|
|
793
798
|
* still owes an undelivered message a replay.
|
|
794
799
|
*/
|
|
800
|
+
liveVoiceSupported = true;
|
|
801
|
+
async interruptPrompt(generation, promptId) {
|
|
802
|
+
if (generation !== this.sessionGeneration)
|
|
803
|
+
throw new Error('session_changed: Agent session changed.');
|
|
804
|
+
if (this.activeTurn?.id !== promptId)
|
|
805
|
+
return { state: 'settled' };
|
|
806
|
+
return this.interrupt('local-console');
|
|
807
|
+
}
|
|
795
808
|
async interrupt(source = 'local-console') {
|
|
796
809
|
try {
|
|
797
810
|
await this.cancelActive(source);
|
|
@@ -1707,9 +1720,15 @@ export class AcpSession {
|
|
|
1707
1720
|
const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
|
|
1708
1721
|
const messagePhase = update.sessionUpdate === 'agent_message_chunk'
|
|
1709
1722
|
? this.codexMessagePhase(update) : undefined;
|
|
1710
|
-
this.recordConversationUpdate(update, scheduled
|
|
1723
|
+
this.recordConversationUpdate(update, scheduled);
|
|
1711
1724
|
if (update.sessionUpdate === 'config_option_update')
|
|
1712
1725
|
this.captureRuntimeMetadata(update.configOptions);
|
|
1726
|
+
if (update.sessionUpdate === 'usage_update') {
|
|
1727
|
+
// Claude subscription rate-limit snapshot (claude-agent-acp forwards the SDK rate_limit_event).
|
|
1728
|
+
const rateLimit = update._meta?.['_claude/rateLimit'];
|
|
1729
|
+
if (rateLimit)
|
|
1730
|
+
recordClaudeRateLimit(this.options.stateDir, rateLimit);
|
|
1731
|
+
}
|
|
1713
1732
|
switch (update.sessionUpdate) {
|
|
1714
1733
|
case 'agent_message_chunk': {
|
|
1715
1734
|
const phase = messagePhase;
|
|
@@ -1785,13 +1804,15 @@ export class AcpSession {
|
|
|
1785
1804
|
return undefined;
|
|
1786
1805
|
return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
|
|
1787
1806
|
}
|
|
1788
|
-
/**
|
|
1789
|
-
|
|
1807
|
+
/**
|
|
1808
|
+
* Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
|
|
1809
|
+
* owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
|
|
1810
|
+
*/
|
|
1811
|
+
recordConversationUpdate(update, scheduled) {
|
|
1790
1812
|
const normalized = normalizeSessionUpdate(update, scheduled ? {
|
|
1791
1813
|
redactText: SCHEDULED_LOOP_REDACTION,
|
|
1792
1814
|
redactToolCallId: 'scheduled-loop-tool',
|
|
1793
|
-
}
|
|
1794
|
-
: commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
|
|
1815
|
+
} : {});
|
|
1795
1816
|
this.conversation.appendSafe({
|
|
1796
1817
|
kind: normalized.kind,
|
|
1797
1818
|
sessionGeneration: this.sessionGeneration,
|
|
@@ -1805,6 +1826,10 @@ export class AcpSession {
|
|
|
1805
1826
|
});
|
|
1806
1827
|
}
|
|
1807
1828
|
// ── conversation ledger access (AgentSession) ─────────────────────────────
|
|
1829
|
+
taskNoticeBinding() {
|
|
1830
|
+
return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
|
|
1831
|
+
}
|
|
1832
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
1808
1833
|
conversationPage(request = {}) {
|
|
1809
1834
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
1810
1835
|
const requested = Number(request.after ?? 0);
|
|
@@ -23,6 +23,7 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
23
23
|
private tail;
|
|
24
24
|
private unsettled;
|
|
25
25
|
private stopping;
|
|
26
|
+
private retiring;
|
|
26
27
|
/** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
|
|
27
28
|
private generation;
|
|
28
29
|
/** Resolves the instant the CURRENT generation is retired. */
|
|
@@ -62,6 +63,8 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
62
63
|
tryScheduled(text: string, origin: Extract<PromptOrigin, {
|
|
63
64
|
kind: 'scheduled-loop';
|
|
64
65
|
}>, beforeQueue?: () => void | Promise<void>): Promise<ScheduledAttempt>;
|
|
66
|
+
/** Retire only at the same admission boundary used by all prompt producers. */
|
|
67
|
+
retireIfIdle(expired: (snapshot: SessionSnapshot, queueDepth: number) => boolean): Promise<boolean>;
|
|
65
68
|
stopScheduledAdmission(): void;
|
|
66
69
|
isAlive(): boolean;
|
|
67
70
|
snapshot(): SessionSnapshot;
|
|
@@ -72,6 +75,8 @@ export declare class RoleTurnArbiter implements AgentSession {
|
|
|
72
75
|
conversationSnapshot(): ConversationSnapshot;
|
|
73
76
|
subscribeConversation(listener: (event: ConversationEventV1) => void): () => void;
|
|
74
77
|
submitPromptBrowser(command: SubmitPromptCommand): Promise<PromptReceipt>;
|
|
78
|
+
get liveVoiceSupported(): boolean;
|
|
79
|
+
interruptPrompt(generation: string, promptId: string): Promise<InterruptOutcome>;
|
|
75
80
|
interrupt(source?: TurnCancellationSource): Promise<InterruptOutcome>;
|
|
76
81
|
respondPermission(permissionId: string, optionId: string): boolean;
|
|
77
82
|
respondPermissionV2(permissionId: string, optionId: string, sessionGeneration: string): 'accepted' | 'stale';
|
package/dist/session/arbiter.js
CHANGED
|
@@ -29,6 +29,7 @@ export class RoleTurnArbiter {
|
|
|
29
29
|
tail = Promise.resolve();
|
|
30
30
|
unsettled = 0;
|
|
31
31
|
stopping = false;
|
|
32
|
+
retiring = false;
|
|
32
33
|
/** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
|
|
33
34
|
generation = 0;
|
|
34
35
|
/** Resolves the instant the CURRENT generation is retired. */
|
|
@@ -89,7 +90,8 @@ export class RoleTurnArbiter {
|
|
|
89
90
|
retired.retire();
|
|
90
91
|
}
|
|
91
92
|
queuePrompt(text, options = {}) {
|
|
92
|
-
return this.exclusive(async () =>
|
|
93
|
+
return this.exclusive(async () => { if (this.retiring)
|
|
94
|
+
throw new Error('Chat is closing'); return this.track(await this.session.queuePrompt(text, options)); });
|
|
93
95
|
}
|
|
94
96
|
async submitPrompt(text, options = {}) {
|
|
95
97
|
return (await this.queuePrompt(text, options)).completion;
|
|
@@ -99,6 +101,8 @@ export class RoleTurnArbiter {
|
|
|
99
101
|
* answers and explicit interrupts must remain able to pass immediately.
|
|
100
102
|
*/
|
|
101
103
|
submitPromptAfterTool(text, options = {}) {
|
|
104
|
+
if (this.retiring)
|
|
105
|
+
return Promise.reject(new Error('Chat is closing'));
|
|
102
106
|
return this.session.submitPromptAfterTool?.(text, options)
|
|
103
107
|
?? this.session.submitPrompt(text, { ...options, interrupt: false, steer: false });
|
|
104
108
|
}
|
|
@@ -136,6 +140,19 @@ export class RoleTurnArbiter {
|
|
|
136
140
|
throw error;
|
|
137
141
|
});
|
|
138
142
|
}
|
|
143
|
+
/** Retire only at the same admission boundary used by all prompt producers. */
|
|
144
|
+
retireIfIdle(expired) {
|
|
145
|
+
return this.exclusive(async () => {
|
|
146
|
+
const snapshot = this.session.snapshot();
|
|
147
|
+
const queue = this.session.conversationSnapshot?.().queueDepth ?? -1;
|
|
148
|
+
if (this.retiring || this.unsettled > 0 || snapshot.readiness !== 'idle' || !expired(snapshot, queue))
|
|
149
|
+
return false;
|
|
150
|
+
this.retiring = true;
|
|
151
|
+
this.stopping = true;
|
|
152
|
+
await this.session.close();
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
139
156
|
stopScheduledAdmission() { this.stopping = true; }
|
|
140
157
|
isAlive() { return this.session.isAlive(); }
|
|
141
158
|
snapshot() { return this.session.snapshot(); }
|
|
@@ -157,7 +174,16 @@ export class RoleTurnArbiter {
|
|
|
157
174
|
submitPromptBrowser(command) {
|
|
158
175
|
if (!this.session.submitPromptBrowser)
|
|
159
176
|
return Promise.reject(new Error('browser prompt admission is unavailable'));
|
|
160
|
-
return this.exclusive(() => this.
|
|
177
|
+
return this.exclusive(() => { if (this.retiring)
|
|
178
|
+
throw new Error('Chat is closing'); return this.session.submitPromptBrowser(command); });
|
|
179
|
+
}
|
|
180
|
+
get liveVoiceSupported() { return this.session.liveVoiceSupported === true; }
|
|
181
|
+
interruptPrompt(generation, promptId) {
|
|
182
|
+
return this.exclusive(async () => {
|
|
183
|
+
if (!this.session.interruptPrompt)
|
|
184
|
+
throw new Error('targeted voice interrupt is unavailable');
|
|
185
|
+
return interruptOutcome(await this.session.interruptPrompt(generation, promptId));
|
|
186
|
+
});
|
|
161
187
|
}
|
|
162
188
|
interrupt(source = 'local-console') {
|
|
163
189
|
return this.exclusive(async () => interruptOutcome(await this.session.interrupt(source)));
|