@ours.network/fleet 1.2.0-nightly.4 → 1.2.0-nightly.40
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 +165 -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/room-layout-definitions.d.ts +45 -0
- package/dist/application/room-layout-definitions.js +130 -0
- package/dist/application/session-control.d.ts +18 -0
- package/dist/application/session-control.js +54 -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/build-info.json +6 -4
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +4 -0
- package/dist/cli.js +186 -34
- package/dist/client-profile.d.ts +5 -16
- package/dist/client-profile.js +4 -112
- package/dist/config.d.ts +13 -0
- package/dist/config.js +19 -4
- 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 +44 -22
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +46 -59
- package/dist/erased-resources.d.ts +9 -0
- package/dist/erased-resources.js +87 -0
- package/dist/fleet-command-audit.d.ts +1 -0
- package/dist/fleet-command-audit.js +11 -6
- 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-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 +8 -5
- 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/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 +18 -0
- package/dist/notifications/fleet-producer.js +129 -0
- package/dist/notifications/outbox.d.ts +42 -0
- package/dist/notifications/outbox.js +149 -0
- 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/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 +11 -117
- package/dist/rooms-tasks/cowork-http.d.ts +3 -0
- package/dist/rooms-tasks/cowork-http.js +70 -0
- 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 +89 -29
- package/dist/session/acp.d.ts +7 -1
- package/dist/session/acp.js +21 -6
- package/dist/session/arbiter.d.ts +3 -0
- package/dist/session/arbiter.js +20 -2
- package/dist/session/codex-app-server.d.ts +3 -0
- package/dist/session/codex-app-server.js +26 -3
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +37 -2
- package/dist/session/conversation-normalizer.d.ts +2 -1
- package/dist/session/conversation-normalizer.js +1 -1
- package/dist/session/conversation-types.d.ts +5 -3
- package/dist/session/task-notice.d.ts +14 -0
- package/dist/session/task-notice.js +13 -0
- package/dist/session/types.d.ts +3 -0
- package/dist/setup-tunnel-preflight.d.ts +32 -0
- package/dist/setup-tunnel-preflight.js +58 -0
- package/dist/socket-path.d.ts +2 -0
- package/dist/socket-path.js +22 -0
- package/dist/spawn.js +7 -0
- 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/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 +81 -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 +29 -0
- package/dist/web/prefix-gateway.js +206 -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 +501 -53
- package/dist/web/service.d.ts +11 -2
- package/dist/web/service.js +30 -4
- package/dist/web/workspace-devices.d.ts +29 -0
- package/dist/web/workspace-devices.js +84 -0
- package/dist/web/workspace-tunnel.d.ts +1 -0
- package/dist/web/workspace-tunnel.js +32 -0
- package/dist/workspace-enrollment.d.ts +53 -0
- package/dist/workspace-enrollment.js +370 -0
- package/docs/room-layouts.md +227 -0
- package/docs/task-workspaces.md +130 -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 +13 -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,3 +1,5 @@
|
|
|
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';
|
|
3
5
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, realpathSync } from 'node:fs';
|
|
@@ -12,6 +14,7 @@ import { createMonitor, probeIdentityPresence, } from './monitor.js';
|
|
|
12
14
|
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
|
|
13
15
|
import { realExec } from './exec.js';
|
|
14
16
|
import { resolveIsolation } from './isolation/policy.js';
|
|
17
|
+
import { pinSubscriptionForLaunch } from './subscriptions/launch.js';
|
|
15
18
|
import { selectIsolationBackend } from './isolation/registry.js';
|
|
16
19
|
import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
|
|
17
20
|
import { resolveLaunchRuntime } from './isolation/runtime.js';
|
|
@@ -75,12 +78,16 @@ const defaultDeps = () => ({
|
|
|
75
78
|
});
|
|
76
79
|
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
77
80
|
const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
|
|
78
|
-
function localFleetAuditor(stateDir, caller, log) {
|
|
81
|
+
export function localFleetAuditor(stateDir, caller, log, session) {
|
|
82
|
+
const bindings = new Map();
|
|
79
83
|
const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
|
|
80
84
|
return {
|
|
81
85
|
async begin(requestId, argv) {
|
|
82
86
|
let attempt = store.begin(requestId, caller, argv);
|
|
83
87
|
if (attempt.invocation === 'sending') {
|
|
88
|
+
const binding = session.taskNoticeBinding?.();
|
|
89
|
+
if (binding)
|
|
90
|
+
bindings.set(attempt.correlationId, binding);
|
|
84
91
|
log(`[${caller}] fleet proxy command ${attempt.correlationId} `
|
|
85
92
|
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
86
93
|
attempt = store.invocation(attempt.correlationId, caller, 'delivered');
|
|
@@ -94,6 +101,19 @@ function localFleetAuditor(stateDir, caller, log) {
|
|
|
94
101
|
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
95
102
|
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
96
103
|
});
|
|
104
|
+
const binding = bindings.get(attempt.correlationId);
|
|
105
|
+
if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
|
|
106
|
+
for (const p of attempt.outcome.presentations ?? [])
|
|
107
|
+
if (p.kind === 'task' && p.operation === 'create') {
|
|
108
|
+
try {
|
|
109
|
+
session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
log(`[${caller}] task-created conversation notice could not be persisted`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
bindings.delete(attempt.correlationId);
|
|
116
|
+
}
|
|
97
117
|
if (attempt.outcome?.delivery === 'sending')
|
|
98
118
|
attempt = store.outcome(attempt.correlationId, caller, 'delivered');
|
|
99
119
|
return attempt;
|
|
@@ -412,9 +432,12 @@ export function loadTempRole(name) {
|
|
|
412
432
|
throw new Error(`temp role '${name}' has no snapshot at ${p}`);
|
|
413
433
|
const role = parse(readFileSync(p, 'utf8'));
|
|
414
434
|
// Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
|
|
415
|
-
// with no monitor block keeps the historical native/no-supervisor behavior
|
|
435
|
+
// with no monitor block keeps the historical native/no-supervisor behavior,
|
|
436
|
+
// and one without interrupt keeps the non-interrupting delivery it ran with.
|
|
416
437
|
if (role.monitor)
|
|
417
|
-
role.monitor = resolveMonitorConfig(undefined,
|
|
438
|
+
role.monitor = resolveMonitorConfig(undefined, {
|
|
439
|
+
interrupt: false, ...role.monitor,
|
|
440
|
+
});
|
|
418
441
|
role.__temp = true;
|
|
419
442
|
return role;
|
|
420
443
|
}
|
|
@@ -427,7 +450,7 @@ export function loadTempRole(name) {
|
|
|
427
450
|
* with "no such role". An empty/missing marker means "use the default", same as
|
|
428
451
|
* no -c was ever given.
|
|
429
452
|
*/
|
|
430
|
-
function resolveConfigPath(dir, explicit) {
|
|
453
|
+
export function resolveConfigPath(dir, explicit) {
|
|
431
454
|
if (explicit)
|
|
432
455
|
return explicit;
|
|
433
456
|
const marker = join(dir, '.config-path');
|
|
@@ -506,6 +529,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
506
529
|
const sessionBackend = role.session ?? 'acp';
|
|
507
530
|
const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
|
|
508
531
|
let launch = adapter.agentSession.prepareLaunch(role, prep);
|
|
532
|
+
// Resolve the fleet-wide active subscription profile once, now: a later switch
|
|
533
|
+
// never changes this session (the operator restarts agents to move them).
|
|
534
|
+
const subscription = pinSubscriptionForLaunch(role, dir);
|
|
535
|
+
if (subscription?.pin.home)
|
|
536
|
+
deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
|
|
509
537
|
// Preserve the role's existing isolation policy.
|
|
510
538
|
let wrappedArgv = launch.argv;
|
|
511
539
|
if (role.isolation) {
|
|
@@ -516,6 +544,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
516
544
|
launch = { ...launch, argv: runtime.argv };
|
|
517
545
|
const ctx = {
|
|
518
546
|
...isolationContextFor(role), stateDir: dir, runCwd,
|
|
547
|
+
subscriptionHome: subscription?.writablePath,
|
|
519
548
|
runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
|
|
520
549
|
};
|
|
521
550
|
const policy = resolveIsolation(role.isolation, ctx);
|
|
@@ -539,8 +568,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
539
568
|
wrappedArgv = [...rprefix, ...wrappedArgv];
|
|
540
569
|
}
|
|
541
570
|
const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
|
|
571
|
+
let layoutRetiring = false;
|
|
542
572
|
try {
|
|
543
|
-
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
|
|
573
|
+
const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
|
|
544
574
|
// Start-stagger: space this launch at least start_stagger_ms after the previous
|
|
545
575
|
// agent launch across the whole host, so a burst of boots (systemd starts every
|
|
546
576
|
// user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
|
|
@@ -662,6 +692,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
662
692
|
harness: role.harness,
|
|
663
693
|
config: role.owner_channel,
|
|
664
694
|
session: arbiter,
|
|
695
|
+
startupPending: () => !sessionStartupComplete,
|
|
665
696
|
stateDir: dir,
|
|
666
697
|
env: role.env,
|
|
667
698
|
log: deps.log,
|
|
@@ -669,11 +700,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
669
700
|
...(configPath ? { configPath } : {}),
|
|
670
701
|
});
|
|
671
702
|
control = deps.createControlServer(dir, arbiter, deps.log);
|
|
703
|
+
control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
|
|
672
704
|
control.setFleetAuditor(ownerChannel ? {
|
|
673
705
|
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
674
706
|
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
675
707
|
present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
|
|
676
|
-
} : localFleetAuditor(dir, name, deps.log));
|
|
708
|
+
} : localFleetAuditor(dir, name, deps.log, agentSession));
|
|
709
|
+
control.setLayoutControl?.(createLayoutControl({
|
|
710
|
+
agent: name, temporary: temp, standalone: !role.roomMemberStartup,
|
|
711
|
+
runtime: managedService.runtime, session: arbiter,
|
|
712
|
+
stopping: () => Boolean(deps.shouldStop?.()),
|
|
713
|
+
retire: async () => { layoutRetiring = true; await arbiter.close(); },
|
|
714
|
+
}));
|
|
677
715
|
await control.start();
|
|
678
716
|
}
|
|
679
717
|
catch (error) {
|
|
@@ -756,6 +794,26 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
756
794
|
// interruption to steering until this startup turn reaches a terminal
|
|
757
795
|
// success, so there is neither a deaf gap nor a boot-cancellation loop.
|
|
758
796
|
monitorLoop = monitor?.run(pid);
|
|
797
|
+
// Owner traffic and agent replies must be observed during a long first
|
|
798
|
+
// turn. Ordinary owner input steers until startup has proved successful.
|
|
799
|
+
if (ownerChannel) {
|
|
800
|
+
try {
|
|
801
|
+
await ownerChannel.start();
|
|
802
|
+
control.setOwnerChannel(ownerChannel);
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
monitor?.stop();
|
|
806
|
+
if (monitorLoop)
|
|
807
|
+
await monitorLoop;
|
|
808
|
+
await control.close();
|
|
809
|
+
await ownerChannel.close().catch(() => undefined);
|
|
810
|
+
ownerBinder?.release();
|
|
811
|
+
await agentSession.close();
|
|
812
|
+
unsubscribeRecovery?.();
|
|
813
|
+
throw new Error(`[${name}] owner channel failed to start: `
|
|
814
|
+
+ `${error?.message ?? String(error)}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
759
817
|
const started = await starting;
|
|
760
818
|
// A temporary role's first turn can be the active turn when an ours wake
|
|
761
819
|
// needs immediate attention. A typed console/monitor cancellation ends
|
|
@@ -768,6 +826,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
768
826
|
if (!started.succeeded && !interruptedForWake) {
|
|
769
827
|
monitor?.stop();
|
|
770
828
|
await control.close();
|
|
829
|
+
await ownerChannel?.close().catch(() => undefined);
|
|
771
830
|
ownerBinder?.release();
|
|
772
831
|
await agentSession.close();
|
|
773
832
|
unsubscribeRecovery?.();
|
|
@@ -795,26 +854,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
795
854
|
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
796
855
|
+ 'keeping temporary supervisor alive');
|
|
797
856
|
sessionStartupComplete = true;
|
|
798
|
-
if (ownerChannel) {
|
|
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
|
-
}
|
|
818
857
|
reloadLoopConfig = async () => {
|
|
819
858
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
820
859
|
const definitions = nextRole.loops ?? [];
|
|
@@ -900,6 +939,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
900
939
|
}
|
|
901
940
|
},
|
|
902
941
|
});
|
|
942
|
+
let chatIdle;
|
|
943
|
+
try {
|
|
944
|
+
if (temp && !role.roomMemberStartup)
|
|
945
|
+
chatIdle = new TemporaryChatIdle(dir, deps.now());
|
|
946
|
+
}
|
|
947
|
+
catch {
|
|
948
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
|
|
949
|
+
}
|
|
903
950
|
const start = deps.now();
|
|
904
951
|
let nextLoopReloadAt = deps.now() + 30_000;
|
|
905
952
|
let nextIdentityPollAt = deps.now();
|
|
@@ -923,6 +970,19 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
923
970
|
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
924
971
|
}
|
|
925
972
|
}
|
|
973
|
+
let idleExpired = false;
|
|
974
|
+
try {
|
|
975
|
+
idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
|
|
976
|
+
}
|
|
977
|
+
catch {
|
|
978
|
+
chatIdle = undefined;
|
|
979
|
+
deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
|
|
980
|
+
}
|
|
981
|
+
if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
|
|
982
|
+
retirementReason = 'idle-timeout';
|
|
983
|
+
sessionClosed = true;
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
926
986
|
if (deps.shouldStop?.()) {
|
|
927
987
|
if (temp) {
|
|
928
988
|
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
@@ -934,7 +994,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
934
994
|
}
|
|
935
995
|
if (temp && now >= nextIdentityPollAt) {
|
|
936
996
|
nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
|
|
937
|
-
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env);
|
|
997
|
+
const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
|
|
938
998
|
if (presence.state === 'present') {
|
|
939
999
|
identityObserved = true;
|
|
940
1000
|
identityAbsentSince = undefined;
|
|
@@ -1051,11 +1111,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
1051
1111
|
throw new SupervisorRecycleRequiredError();
|
|
1052
1112
|
return {
|
|
1053
1113
|
elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
|
|
1054
|
-
...(retirementReason ? { retirementReason } : {}),
|
|
1114
|
+
...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
|
|
1055
1115
|
};
|
|
1056
1116
|
}
|
|
1057
1117
|
finally {
|
|
1058
|
-
await managedService.close(
|
|
1118
|
+
await managedService.close(layoutRetiring);
|
|
1059
1119
|
}
|
|
1060
1120
|
}
|
|
1061
1121
|
/**
|
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';
|
|
@@ -338,8 +339,13 @@ export declare class AcpSession implements AgentSession {
|
|
|
338
339
|
* signal. Never infer commentary from text, message order, or unknown meta.
|
|
339
340
|
*/
|
|
340
341
|
private codexMessagePhase;
|
|
341
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
|
|
344
|
+
* owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
|
|
345
|
+
*/
|
|
342
346
|
private recordConversationUpdate;
|
|
347
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
348
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
343
349
|
conversationPage(request?: {
|
|
344
350
|
after?: string;
|
|
345
351
|
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,8 @@ 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.');
|
|
763
766
|
const queued = await this.queuePrompt(command.text, {
|
|
764
767
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
765
768
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -1707,9 +1710,15 @@ export class AcpSession {
|
|
|
1707
1710
|
const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
|
|
1708
1711
|
const messagePhase = update.sessionUpdate === 'agent_message_chunk'
|
|
1709
1712
|
? this.codexMessagePhase(update) : undefined;
|
|
1710
|
-
this.recordConversationUpdate(update, scheduled
|
|
1713
|
+
this.recordConversationUpdate(update, scheduled);
|
|
1711
1714
|
if (update.sessionUpdate === 'config_option_update')
|
|
1712
1715
|
this.captureRuntimeMetadata(update.configOptions);
|
|
1716
|
+
if (update.sessionUpdate === 'usage_update') {
|
|
1717
|
+
// Claude subscription rate-limit snapshot (claude-agent-acp forwards the SDK rate_limit_event).
|
|
1718
|
+
const rateLimit = update._meta?.['_claude/rateLimit'];
|
|
1719
|
+
if (rateLimit)
|
|
1720
|
+
recordClaudeRateLimit(this.options.stateDir, rateLimit);
|
|
1721
|
+
}
|
|
1713
1722
|
switch (update.sessionUpdate) {
|
|
1714
1723
|
case 'agent_message_chunk': {
|
|
1715
1724
|
const phase = messagePhase;
|
|
@@ -1785,13 +1794,15 @@ export class AcpSession {
|
|
|
1785
1794
|
return undefined;
|
|
1786
1795
|
return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
|
|
1787
1796
|
}
|
|
1788
|
-
/**
|
|
1789
|
-
|
|
1797
|
+
/**
|
|
1798
|
+
* Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
|
|
1799
|
+
* owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
|
|
1800
|
+
*/
|
|
1801
|
+
recordConversationUpdate(update, scheduled) {
|
|
1790
1802
|
const normalized = normalizeSessionUpdate(update, scheduled ? {
|
|
1791
1803
|
redactText: SCHEDULED_LOOP_REDACTION,
|
|
1792
1804
|
redactToolCallId: 'scheduled-loop-tool',
|
|
1793
|
-
}
|
|
1794
|
-
: commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
|
|
1805
|
+
} : {});
|
|
1795
1806
|
this.conversation.appendSafe({
|
|
1796
1807
|
kind: normalized.kind,
|
|
1797
1808
|
sessionGeneration: this.sessionGeneration,
|
|
@@ -1805,6 +1816,10 @@ export class AcpSession {
|
|
|
1805
1816
|
});
|
|
1806
1817
|
}
|
|
1807
1818
|
// ── conversation ledger access (AgentSession) ─────────────────────────────
|
|
1819
|
+
taskNoticeBinding() {
|
|
1820
|
+
return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
|
|
1821
|
+
}
|
|
1822
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
1808
1823
|
conversationPage(request = {}) {
|
|
1809
1824
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
1810
1825
|
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;
|
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,8 @@ 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); });
|
|
161
179
|
}
|
|
162
180
|
interrupt(source = 'local-console') {
|
|
163
181
|
return this.exclusive(async () => interruptOutcome(await this.session.interrupt(source)));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
|
|
1
2
|
import type { CommonPermissions } from '../config.js';
|
|
2
3
|
import type { ConversationEventV1, ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
|
|
3
4
|
import type { CodexAppServerTransportFactory } from './codex-app-server-transport.js';
|
|
@@ -76,6 +77,8 @@ export declare class CodexAppServerSession implements AgentSession {
|
|
|
76
77
|
setControllerAttached(attached: boolean): void;
|
|
77
78
|
exitResult(): ExitRecord | null;
|
|
78
79
|
close(): Promise<void>;
|
|
80
|
+
taskNoticeBinding(): TaskNoticeBinding | undefined;
|
|
81
|
+
recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
|
|
79
82
|
conversationPage(request?: {
|
|
80
83
|
after?: string;
|
|
81
84
|
limit?: number;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { appendTaskCreated } from './task-notice.js';
|
|
1
2
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { boundedJson } from './conversation-normalizer.js';
|
|
4
6
|
import { ConversationEventStore } from './conversation-store.js';
|
|
5
7
|
import { SessionEvents } from './events.js';
|
|
6
8
|
import { CodexAppServerTransport } from './codex-app-server-transport.js';
|
|
@@ -12,7 +14,6 @@ const INTERRUPT_TERMINATE_GRACE_MS = 5_000;
|
|
|
12
14
|
const TERMINAL_RECONCILE_QUIET_MS = 2_000;
|
|
13
15
|
const TERMINAL_RECONCILE_REQUEST_TIMEOUT_MS = 5_000;
|
|
14
16
|
const SCHEDULED_REDACTION = '[scheduled-loop content redacted]';
|
|
15
|
-
const COMMENTARY_REDACTION = '[assistant commentary redacted]';
|
|
16
17
|
const OWNER_ADMIN_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_admin_console';
|
|
17
18
|
const OWNER_CHANNEL_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_channel';
|
|
18
19
|
const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -228,8 +229,10 @@ export class CodexAppServerSession {
|
|
|
228
229
|
}
|
|
229
230
|
async interrupt(source = 'local-console') {
|
|
230
231
|
const active = this.activeTurn;
|
|
231
|
-
if (!active || !this.threadId
|
|
232
|
+
if (!active || !this.threadId)
|
|
232
233
|
return { state: 'settled' };
|
|
234
|
+
if (!active.nativeTurnId)
|
|
235
|
+
throw new SessionControlError('backend', 'The operation is still pending without a native turn ID. Cancellation is not confirmed; inspect or restart the agent if it does not progress.');
|
|
233
236
|
active.cancellationSource ??= source;
|
|
234
237
|
this.events.emit('state', { turnId: active.promptId, status: 'running', origin: active.origin });
|
|
235
238
|
this.conversation.appendSafe({
|
|
@@ -335,6 +338,10 @@ export class CodexAppServerSession {
|
|
|
335
338
|
this.conversation.close();
|
|
336
339
|
await this.transport.close();
|
|
337
340
|
}
|
|
341
|
+
taskNoticeBinding() {
|
|
342
|
+
return this.activeTurn && this.threadId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.threadId, promptId: this.activeTurn.promptId } : undefined;
|
|
343
|
+
}
|
|
344
|
+
recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
|
|
338
345
|
conversationPage(request = {}) {
|
|
339
346
|
const floor = Number(this.conversationStartCursor ?? 0);
|
|
340
347
|
const requested = Number(request.after ?? 0);
|
|
@@ -367,6 +374,8 @@ export class CodexAppServerSession {
|
|
|
367
374
|
const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
|
|
368
375
|
if (existing)
|
|
369
376
|
return existing;
|
|
377
|
+
if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
|
|
378
|
+
throw new Error('session_changed: Agent session changed. Record a new voice message.');
|
|
370
379
|
const queued = await this.queuePrompt(command.text, {
|
|
371
380
|
origin: { kind: 'owner-admin-console', commandId: command.commandId },
|
|
372
381
|
actor: { browserSession: command.actorBrowserSession },
|
|
@@ -415,6 +424,10 @@ export class CodexAppServerSession {
|
|
|
415
424
|
this.reasoningEffort = { value: effort };
|
|
416
425
|
this.readiness = 'idle';
|
|
417
426
|
this.events.emit('state', { status: 'idle', text: `Codex thread ${this.threadId}` });
|
|
427
|
+
this.conversation.appendSafe({
|
|
428
|
+
kind: 'capabilities.updated', sessionGeneration: this.sessionGeneration,
|
|
429
|
+
payload: { commands: [{ name: 'compact', description: textBlock('Compact session context') }] },
|
|
430
|
+
});
|
|
418
431
|
this.conversation.appendSafe({
|
|
419
432
|
kind: 'session.state', sessionGeneration: this.sessionGeneration,
|
|
420
433
|
payload: { status: 'idle' },
|
|
@@ -486,6 +499,12 @@ export class CodexAppServerSession {
|
|
|
486
499
|
promptId, turnId: promptId, source: sourceFor(origin).source, payload: {},
|
|
487
500
|
});
|
|
488
501
|
try {
|
|
502
|
+
if (text.trim() === '/compact') {
|
|
503
|
+
// The acknowledgement is not completion. Standard turn notifications
|
|
504
|
+
// settle the same queued prompt and durable browser receipt.
|
|
505
|
+
await this.transport.request('thread/compact/start', { threadId: this.threadId });
|
|
506
|
+
return await settled;
|
|
507
|
+
}
|
|
489
508
|
const response = await this.transport.request('turn/start', {
|
|
490
509
|
threadId: this.threadId,
|
|
491
510
|
input: [{ type: 'text', text, text_elements: [] }],
|
|
@@ -699,7 +718,7 @@ export class CodexAppServerSession {
|
|
|
699
718
|
promptId: this.activeTurn?.promptId, turnId: this.activeTurn?.promptId,
|
|
700
719
|
messageId: id, source: 'agent',
|
|
701
720
|
payload: {
|
|
702
|
-
role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION :
|
|
721
|
+
role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : undefined),
|
|
703
722
|
},
|
|
704
723
|
});
|
|
705
724
|
}
|
|
@@ -746,6 +765,10 @@ export class CodexAppServerSession {
|
|
|
746
765
|
toolCallId: scheduled ? 'scheduled-loop-tool' : id, snapshot,
|
|
747
766
|
title: scheduled ? 'scheduled-loop tool' : tool.title, status: tool.status,
|
|
748
767
|
kind: string(item.type),
|
|
768
|
+
...(!scheduled && item.arguments !== undefined ? { rawInput: boundedJson(item.arguments) } : {}),
|
|
769
|
+
...(!scheduled && item.type === 'commandExecution' ? { rawInput: boundedJson({ command: item.command, cwd: item.cwd }), ...(item.aggregatedOutput !== undefined ? { rawOutput: boundedJson({ output: item.aggregatedOutput, exitCode: item.exitCode }) } : {}) } : {}),
|
|
770
|
+
...(!scheduled && item.type === 'fileChange' && item.changes !== undefined ? { rawOutput: boundedJson(item.changes) } : {}),
|
|
771
|
+
...(!scheduled && (item.result !== undefined || item.error !== undefined) ? { rawOutput: boundedJson({ result: item.result, error: item.error }) } : {}),
|
|
749
772
|
},
|
|
750
773
|
});
|
|
751
774
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LayoutControlRequest } from '../rooms-tasks/layout-control.js';
|
|
2
|
+
import type { SupervisorCorrespondence, AgentHistoryQuery } from '../agent-ours/correspondence.js';
|
|
1
3
|
import { type Socket } from 'node:net';
|
|
2
4
|
import type { AgentSession, ControlFailureKind, SessionEvent, SessionSnapshot } from './types.js';
|
|
3
5
|
import type { OwnerChannelHandle, OwnerChannelManagementRequest } from '../owner-channel/channel.js';
|
|
@@ -9,8 +11,11 @@ export interface ControlRequest {
|
|
|
9
11
|
version: 1 | 2 | 3;
|
|
10
12
|
id: string;
|
|
11
13
|
token: string;
|
|
12
|
-
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
|
|
14
|
+
command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'submit_voice_prompt' | 'interrupt_v2' | 'layout_control' | 'agent_contacts' | 'agent_history' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
|
|
15
|
+
layout?: LayoutControlRequest;
|
|
16
|
+
agentHistory?: AgentHistoryQuery;
|
|
13
17
|
text?: string;
|
|
18
|
+
expectedSessionGeneration?: string;
|
|
14
19
|
permissionId?: string;
|
|
15
20
|
optionId?: string;
|
|
16
21
|
since?: number;
|
|
@@ -112,9 +117,13 @@ export declare class RoleControlServer {
|
|
|
112
117
|
private readonly sockets;
|
|
113
118
|
/** Interrupt idempotency: same command id returns the first receipt. */
|
|
114
119
|
private readonly interruptCommands;
|
|
120
|
+
private correspondence?;
|
|
121
|
+
setCorrespondence(value: SupervisorCorrespondence): void;
|
|
115
122
|
private ownerChannel?;
|
|
116
123
|
private loopManager?;
|
|
117
124
|
private reloadConfig?;
|
|
125
|
+
private layoutControl?;
|
|
126
|
+
setLayoutControl(handler: (request: LayoutControlRequest) => Promise<unknown>): void;
|
|
118
127
|
private fleetSpawner?;
|
|
119
128
|
private fleetAuditor?;
|
|
120
129
|
constructor(stateDir: string, session: AgentSession, log: (line: string) => void);
|