@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.20
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 +169 -54
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +58 -6
- package/dist/application/task-room-service.js +426 -78
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +227 -73
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +67 -2
- package/dist/config.js +260 -30
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +171 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +543 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -0
- package/dist/lifecycle-summary.d.ts +118 -0
- package/dist/lifecycle-summary.js +161 -0
- package/dist/loops/config.js +3 -2
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +4 -0
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +503 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +176 -0
- package/dist/rooms-tasks/member-overrides.d.ts +27 -0
- package/dist/rooms-tasks/member-overrides.js +170 -0
- package/dist/rooms-tasks/provision.js +179 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +94 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +9 -2
- package/dist/runner.js +107 -40
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +38 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +6 -31
- package/dist/spawn.js +57 -234
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
|
@@ -2,10 +2,11 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { withFileLock } from '../atomic-file.js';
|
|
3
3
|
import { stateRoot } from '../paths.js';
|
|
4
4
|
import { deleteManagedRoom } from './close.js';
|
|
5
|
-
import { beginTaskTerminalIntent, finishTaskTerminalIntent, getTask, setTaskTerminalIntentError, } from './task-state.js';
|
|
5
|
+
import { assertNoPendingDeletion, beginTaskTerminalIntent, finishTaskTerminalIntent, getTask, setTaskTerminalIntentError, } from './task-state.js';
|
|
6
6
|
import { getRoomRecord } from './room-state.js';
|
|
7
|
-
const TASK_OPERATION_LOCK_STALE_MS = 10 * 60_000;
|
|
8
|
-
|
|
7
|
+
export const TASK_OPERATION_LOCK_STALE_MS = 10 * 60_000;
|
|
8
|
+
/** Common per-task operation lock serializing terminal, deletion, and recovery acceptance. */
|
|
9
|
+
export function taskOperationLockPath(taskId) {
|
|
9
10
|
return join(stateRoot(), 'locks', 'task-terminal', encodeURIComponent(taskId));
|
|
10
11
|
}
|
|
11
12
|
function errorText(error) {
|
|
@@ -24,6 +25,9 @@ export function acceptTaskTerminalIntent(input) {
|
|
|
24
25
|
export function settleTaskTerminalIntent(input) {
|
|
25
26
|
return withFileLock(taskOperationLockPath(input.taskId), async () => {
|
|
26
27
|
const current = getTask(input.taskId);
|
|
28
|
+
// Deletion supersedes a pending terminal intent: fail boundedly before any
|
|
29
|
+
// room side effect so the deletion worker owns all remaining cleanup.
|
|
30
|
+
assertNoPendingDeletion(current);
|
|
27
31
|
const intent = current.terminal_intent;
|
|
28
32
|
if (!intent)
|
|
29
33
|
throw new Error(`task ${input.taskId} has no accepted terminal intent`);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* membership, and archive. Messenger Server owns owner presentation.
|
|
6
6
|
* Fleet stores only IDs, CIDs, orchestration state, and saga cursors.
|
|
7
7
|
*/
|
|
8
|
+
import type { AgentLaunchConfiguration } from '../lifecycle-summary.js';
|
|
8
9
|
export type TaskState = 'backlog' | 'provisioning' | 'active' | 'review' | 'done' | 'cancelled' | 'failed';
|
|
9
10
|
export declare const TASK_TERMINAL_STATES: readonly TaskState[];
|
|
10
11
|
export declare const TASK_CANCELLABLE_STATES: readonly TaskState[];
|
|
@@ -44,6 +45,47 @@ export interface TaskTerminalIntent {
|
|
|
44
45
|
first_failure?: string;
|
|
45
46
|
first_recovery_hint?: string;
|
|
46
47
|
}
|
|
48
|
+
/** Origin evidence for an accepted deletion; retained until the record is unlinked. */
|
|
49
|
+
export type TaskDeletionActor = {
|
|
50
|
+
kind: 'local_control';
|
|
51
|
+
surface: 'cli' | 'web';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'authenticated_owner';
|
|
54
|
+
surface: 'messenger';
|
|
55
|
+
cid: string;
|
|
56
|
+
};
|
|
57
|
+
export type TaskDeletionMemberPhase = 'pending' | 'stop_requested' | 'liveness_absent' | 'archive_secured' | 'identity_absent';
|
|
58
|
+
/**
|
|
59
|
+
* Durable retirement cursor for a managed member when no room orchestration
|
|
60
|
+
* record exists to carry the equivalent seat cursor.
|
|
61
|
+
*/
|
|
62
|
+
export interface TaskDeletionMemberCursor {
|
|
63
|
+
name: string;
|
|
64
|
+
identity_cid: string;
|
|
65
|
+
phase: TaskDeletionMemberPhase;
|
|
66
|
+
launch_id?: string;
|
|
67
|
+
archive_path?: string;
|
|
68
|
+
updated_at: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* First-wins durable deletion intent. There is no settled status: settlement
|
|
72
|
+
* ends by unlinking the task record, so physical absence is the completion
|
|
73
|
+
* evidence. While pending, the task is hidden from normal operation and every
|
|
74
|
+
* lifecycle mutation or room publication is rejected.
|
|
75
|
+
*/
|
|
76
|
+
export interface TaskDeletionIntent {
|
|
77
|
+
status: 'pending';
|
|
78
|
+
accepted_at: string;
|
|
79
|
+
actor: TaskDeletionActor;
|
|
80
|
+
room_id?: string;
|
|
81
|
+
/** Snapshot of managed members at acceptance; the missing-room retirement evidence. */
|
|
82
|
+
members: TaskDeletionMemberCursor[];
|
|
83
|
+
error?: string;
|
|
84
|
+
error_at?: string;
|
|
85
|
+
recovery_hint?: string;
|
|
86
|
+
first_failure?: string;
|
|
87
|
+
first_recovery_hint?: string;
|
|
88
|
+
}
|
|
47
89
|
export interface TaskRecord {
|
|
48
90
|
task_id: string;
|
|
49
91
|
/** Stable organizational list identifier. Missing legacy values mean `default`. */
|
|
@@ -56,6 +98,14 @@ export interface TaskRecord {
|
|
|
56
98
|
state: TaskState;
|
|
57
99
|
blocked?: TaskBlocked;
|
|
58
100
|
template?: TaskTemplateRef;
|
|
101
|
+
execution_plan?: {
|
|
102
|
+
schema_version: 1;
|
|
103
|
+
snapshot: TemplateSnapshot;
|
|
104
|
+
room_policy?: RoomLaunchPolicy;
|
|
105
|
+
overrides: Record<string, unknown>;
|
|
106
|
+
overrides_hash: string;
|
|
107
|
+
plan_hash: string;
|
|
108
|
+
};
|
|
59
109
|
no_room?: boolean;
|
|
60
110
|
room_id?: string;
|
|
61
111
|
room_identity_cid?: string;
|
|
@@ -67,6 +117,7 @@ export interface TaskRecord {
|
|
|
67
117
|
ended_at?: string;
|
|
68
118
|
outcome?: TaskOutcome;
|
|
69
119
|
terminal_intent?: TaskTerminalIntent;
|
|
120
|
+
deletion?: TaskDeletionIntent;
|
|
70
121
|
}
|
|
71
122
|
export interface TaskListRecord {
|
|
72
123
|
list_id: string;
|
|
@@ -100,6 +151,16 @@ export interface RoomMemberLaunchState {
|
|
|
100
151
|
/** Expected authenticated proxy caller while adopting a post-spawn crash. */
|
|
101
152
|
caller_role?: string;
|
|
102
153
|
mission_sha256?: string;
|
|
154
|
+
/** Redacted, deterministic effective Agent launch definition retained for retry inspection. */
|
|
155
|
+
agent_definition?: Record<string, unknown>;
|
|
156
|
+
agent_fingerprint?: string;
|
|
157
|
+
agent_template?: string;
|
|
158
|
+
agent_template_hash?: string;
|
|
159
|
+
/**
|
|
160
|
+
* Operator-facing launch configuration captured from the exact resolved
|
|
161
|
+
* launch state; the single source for later Task/Room lifecycle reports.
|
|
162
|
+
*/
|
|
163
|
+
presentation?: AgentLaunchConfiguration;
|
|
103
164
|
launch_id?: string;
|
|
104
165
|
updated_at: string;
|
|
105
166
|
error?: string;
|
|
@@ -172,6 +233,8 @@ export interface RoomOrchestrationRecord {
|
|
|
172
233
|
goal?: string;
|
|
173
234
|
task_id?: string;
|
|
174
235
|
template_snapshot?: TemplateSnapshot;
|
|
236
|
+
/** Resolved once at launch; legacy absence means non-anonymous. */
|
|
237
|
+
room_policy?: RoomLaunchPolicy;
|
|
175
238
|
saga: SagaCursor;
|
|
176
239
|
provisioning_detail?: ProvisioningDetail;
|
|
177
240
|
owner_seat_cid?: string;
|
|
@@ -189,35 +252,48 @@ export interface TemplateMemberSlot {
|
|
|
189
252
|
slot: string;
|
|
190
253
|
role: string;
|
|
191
254
|
count: number;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
export interface TemplateRoleOverrides {
|
|
196
|
-
harness?: string;
|
|
197
|
-
model?: string;
|
|
198
|
-
model_chain?: string[];
|
|
199
|
-
permissions?: Record<string, unknown>;
|
|
200
|
-
isolation?: Record<string, unknown>;
|
|
201
|
-
cwd?: string;
|
|
202
|
-
env?: Record<string, string>;
|
|
203
|
-
persona?: string;
|
|
204
|
-
mission?: string;
|
|
255
|
+
/** Stable reference to an inert Agent Template, never a persistent Agent. */
|
|
256
|
+
agent_template: string;
|
|
205
257
|
}
|
|
206
258
|
export interface TemplateRoomConfig {
|
|
207
259
|
quiet_membership?: boolean;
|
|
208
260
|
anonymous?: boolean;
|
|
209
261
|
}
|
|
262
|
+
export interface RoomLaunchPolicy {
|
|
263
|
+
anonymous: boolean;
|
|
264
|
+
}
|
|
265
|
+
export declare const LEGACY_ROOM_LAUNCH_POLICY: Readonly<RoomLaunchPolicy>;
|
|
266
|
+
/** Validate durable policy before it can influence room creation or recovery. */
|
|
267
|
+
export declare function storedRoomLaunchPolicy(value: RoomLaunchPolicy | undefined): RoomLaunchPolicy;
|
|
210
268
|
export interface TemplateDefinition {
|
|
211
269
|
name: string;
|
|
212
270
|
version: number;
|
|
213
271
|
description: string;
|
|
214
|
-
|
|
272
|
+
/** Authoring provenance only; excluded from snapshots and semantic hashes. */
|
|
273
|
+
sourceFile?: string;
|
|
215
274
|
room?: TemplateRoomConfig;
|
|
216
275
|
contract?: string;
|
|
217
276
|
members: TemplateMemberSlot[];
|
|
218
277
|
}
|
|
219
|
-
export interface
|
|
278
|
+
export interface TemplateSnapshotMember extends TemplateMemberSlot {
|
|
279
|
+
/** Secret-safe public projection; launch material lives in the sealed snapshot. */
|
|
280
|
+
agent_projection?: Record<string, unknown>;
|
|
281
|
+
agent_template_hash?: string;
|
|
282
|
+
/** Private sealed-snapshot lookup key; harmless provenance, not an identity. */
|
|
283
|
+
launch_definition_id?: string;
|
|
284
|
+
role_preset?: {
|
|
285
|
+
id: string;
|
|
286
|
+
hash: string;
|
|
287
|
+
};
|
|
288
|
+
brain_preset?: {
|
|
289
|
+
id: string;
|
|
290
|
+
hash: string;
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
export interface TemplateSnapshot extends Omit<TemplateDefinition, 'members'> {
|
|
294
|
+
members: TemplateSnapshotMember[];
|
|
220
295
|
content_hash: string;
|
|
296
|
+
launch_snapshot_hash?: string;
|
|
221
297
|
}
|
|
222
298
|
export interface RoomsOwnerConfig {
|
|
223
299
|
provider: string;
|
|
@@ -252,5 +328,6 @@ export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
|
252
328
|
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
253
329
|
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
254
330
|
export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
|
|
255
|
-
export declare const
|
|
256
|
-
|
|
331
|
+
export declare const TEMPLATE_ROOM_KEYS: readonly ["quiet_membership", "anonymous"];
|
|
332
|
+
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
|
333
|
+
export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "agent_template", "agent"];
|
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export const TASK_TERMINAL_STATES = ['done', 'cancelled', 'failed'];
|
|
9
9
|
export const TASK_CANCELLABLE_STATES = ['backlog', 'provisioning', 'active', 'review'];
|
|
10
|
+
export const LEGACY_ROOM_LAUNCH_POLICY = Object.freeze({
|
|
11
|
+
anonymous: false,
|
|
12
|
+
});
|
|
13
|
+
/** Validate durable policy before it can influence room creation or recovery. */
|
|
14
|
+
export function storedRoomLaunchPolicy(value) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return { ...LEGACY_ROOM_LAUNCH_POLICY };
|
|
17
|
+
if (typeof value.anonymous !== 'boolean')
|
|
18
|
+
throw new Error('invalid durable Room launch policy; anonymous must be a boolean');
|
|
19
|
+
return { ...value };
|
|
20
|
+
}
|
|
10
21
|
// ── Validation keys ─────────────────────────────────────────────────────
|
|
11
22
|
export const ROOMS_KEYS = ['cowork', 'owner', 'defaults'];
|
|
12
23
|
export const ROOMS_OWNER_KEYS = ['provider', 'public_invite', 'public_invite_file', 'expected_cid', 'role'];
|
|
@@ -14,7 +25,6 @@ export const ROOMS_COWORK_KEYS = ['config'];
|
|
|
14
25
|
export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
|
|
15
26
|
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
|
|
16
27
|
export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
];
|
|
28
|
+
export const TEMPLATE_ROOM_KEYS = ['quiet_membership', 'anonymous'];
|
|
29
|
+
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
|
30
|
+
export const TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'agent_template', 'agent'];
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ResolvedRole } from './config.js';
|
|
2
2
|
import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
|
|
3
|
+
import { type DaemonGenerationProbe } from './daemon-recovery.js';
|
|
3
4
|
import { type Exec } from './exec.js';
|
|
4
5
|
import { RoleControlServer } from './session/control.js';
|
|
5
6
|
import type { AgentSession, ExitRecord, TurnResult } from './session/types.js';
|
|
@@ -15,6 +16,7 @@ export interface RunnerDeps {
|
|
|
15
16
|
log(line: string): void;
|
|
16
17
|
/** HTTP transport for the monitor's daemon long-poll (injectable for tests). */
|
|
17
18
|
fetch: FetchLike;
|
|
19
|
+
probeGeneration(env: NodeJS.ProcessEnv): Promise<DaemonGenerationProbe>;
|
|
18
20
|
/** Construct the supervisor mail monitor (injectable so tests stub it out). */
|
|
19
21
|
createMonitor(opts: MonitorOpts): MonitorHandle;
|
|
20
22
|
/** Construct trusted owner ingress (injectable for lifecycle tests). */
|
|
@@ -23,7 +25,7 @@ export interface RunnerDeps {
|
|
|
23
25
|
/** Neutral construction seam for deterministic runner lifecycle tests. */
|
|
24
26
|
startAgentSession(adapter: AgentSessionAdapter, options: AgentSessionStartOptions): Promise<AgentSession>;
|
|
25
27
|
/** Construct the authenticated role control route (injectable where sockets are unavailable). */
|
|
26
|
-
createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
|
|
28
|
+
createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
|
|
27
29
|
/** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
|
|
28
30
|
acquireOwnerBinder(stateDir: string, role: string, identity: string): Promise<OwnerBinderLease>;
|
|
29
31
|
/** Ask the still-authenticated predecessor to emit the fixed recovery notice. */
|
|
@@ -31,6 +33,11 @@ export interface RunnerDeps {
|
|
|
31
33
|
/** Lets a test (or a shutdown path) end the supervised restart loop. */
|
|
32
34
|
shouldStop?(): boolean;
|
|
33
35
|
}
|
|
36
|
+
export declare const SUPERVISOR_RECYCLE_REQUIRED = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
|
|
37
|
+
export declare class SupervisorRecycleRequiredError extends Error {
|
|
38
|
+
readonly code = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
34
41
|
/** Environment injected only into the managed harness process. */
|
|
35
42
|
export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
|
|
36
43
|
/**
|
|
@@ -176,4 +183,4 @@ export declare function runSupervised(name: string, opts?: {
|
|
|
176
183
|
allowResumeRotation?: boolean;
|
|
177
184
|
}, d: Partial<RunnerDeps>) => Promise<AttemptResult>): Promise<RestartLedger>;
|
|
178
185
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
179
|
-
export declare function runTemp(name: string, deps?: Partial<RunnerDeps
|
|
186
|
+
export declare function runTemp(name: string, deps?: Partial<RunnerDeps>, attempt?: typeof runOnce): Promise<void>;
|
package/dist/runner.js
CHANGED
|
@@ -6,13 +6,15 @@ import { agentDir, stateRoot } from './paths.js';
|
|
|
6
6
|
import { loadConfig, findRole, isolationContextFor, resolveMonitorConfig, resolvePermissions, } from './config.js';
|
|
7
7
|
import { getAdapter } from './harness/registry.js';
|
|
8
8
|
import { createMonitor, probeIdentityPresence, } from './monitor.js';
|
|
9
|
+
import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
|
|
10
|
+
import { recoverAgentIdentity } from './agent-recovery-gate.js';
|
|
9
11
|
import { realExec } from './exec.js';
|
|
10
12
|
import { resolveIsolation } from './isolation/policy.js';
|
|
11
13
|
import { selectIsolationBackend } from './isolation/registry.js';
|
|
12
14
|
import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
|
|
13
15
|
import { resolveLaunchRuntime } from './isolation/runtime.js';
|
|
14
16
|
import { controlRequest, RoleControlServer } from './session/control.js';
|
|
15
|
-
import { ACP_CANCEL_DEADLINE_EXCEEDED, classifyShellStatus } from './session/types.js';
|
|
17
|
+
import { ACP_CANCEL_DEADLINE_EXCEEDED, CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED, classifyShellStatus, } from './session/types.js';
|
|
16
18
|
import { effectiveModelForRole, modelRecoveryHeld, reconcileModelRecovery, recordModelFailure, classifyFailureText, } from './model-recovery.js';
|
|
17
19
|
import { rotateWorklog } from './worklog.js';
|
|
18
20
|
import { reconcilePermanentRoleIdentities } from './creation.js';
|
|
@@ -24,6 +26,14 @@ import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-prox
|
|
|
24
26
|
import { effectivePermissionMode } from './permissions.js';
|
|
25
27
|
import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
|
|
26
28
|
import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
|
|
29
|
+
export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
|
|
30
|
+
export class SupervisorRecycleRequiredError extends Error {
|
|
31
|
+
code = SUPERVISOR_RECYCLE_REQUIRED;
|
|
32
|
+
constructor() {
|
|
33
|
+
super('owner channel requires a fresh supervisor process after unproven client quiescence');
|
|
34
|
+
this.name = 'SupervisorRecycleRequiredError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
27
37
|
const defaultDeps = () => ({
|
|
28
38
|
exec: realExec,
|
|
29
39
|
cpuDelegated: () => cpuControllerDelegated(),
|
|
@@ -38,6 +48,7 @@ const defaultDeps = () => ({
|
|
|
38
48
|
now: () => Date.now(),
|
|
39
49
|
log: line => process.stderr.write(line + '\n'),
|
|
40
50
|
fetch: (url, init) => globalThis.fetch(url, init),
|
|
51
|
+
probeGeneration: env => probeDaemonGeneration((url, init) => globalThis.fetch(url, init), env),
|
|
41
52
|
createMonitor: opts => createMonitor(opts),
|
|
42
53
|
createOwnerChannel: opts => new OwnerChannel(opts),
|
|
43
54
|
startAgentSession: (adapter, options) => adapter.start(options),
|
|
@@ -453,6 +464,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
453
464
|
const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
|
|
454
465
|
const prep = await adapter.prepareSession(role, { stateDir: dir, runCwd });
|
|
455
466
|
const sessionBackend = role.session ?? 'acp';
|
|
467
|
+
const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
|
|
456
468
|
let launch = adapter.agentSession.prepareLaunch(role, prep);
|
|
457
469
|
// Isolation is additive: only roles that declare `isolation:` are wrapped.
|
|
458
470
|
let wrappedArgv = launch.argv;
|
|
@@ -532,6 +544,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
532
544
|
name, identity: role.identity, agentDir: dir, cfg: role.monitor,
|
|
533
545
|
deps: resolvedMonitorDeps,
|
|
534
546
|
}) : null;
|
|
547
|
+
const daemonObserver = new DaemonGenerationObserver();
|
|
548
|
+
const initialDaemon = daemonObserver.observe(await deps.probeGeneration(resolvedMonitorDeps.env));
|
|
549
|
+
let sessionStartedWithoutDaemonBaseline = initialDaemon.kind !== 'baseline';
|
|
535
550
|
if (monitor)
|
|
536
551
|
await monitor.prime({ resetCursor: resetMonitorCursor });
|
|
537
552
|
rmSync(exitFile, { force: true });
|
|
@@ -540,13 +555,13 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
540
555
|
let control;
|
|
541
556
|
let unsubscribeRecovery;
|
|
542
557
|
let monitorLoop;
|
|
543
|
-
let
|
|
558
|
+
let sessionStartupComplete = false;
|
|
544
559
|
let sessionClosed = false;
|
|
545
560
|
let ownerChannel;
|
|
546
561
|
let ownerBinder;
|
|
547
|
-
const pendingFleetSpawnNotices = [];
|
|
548
562
|
let loopManager;
|
|
549
563
|
let arbiter;
|
|
564
|
+
let recoveryController;
|
|
550
565
|
let reloadLoopConfig;
|
|
551
566
|
let loopGeneration = JSON.stringify((role.loops ?? []).map(loop => [
|
|
552
567
|
loop.name, loop.definitionHash, loop.promptHash,
|
|
@@ -571,7 +586,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
571
586
|
unsubscribeRecovery = agentSession.subscribe(event => {
|
|
572
587
|
if (event.kind !== 'error' || !event.text)
|
|
573
588
|
return;
|
|
574
|
-
const evidence = classifyFailureText(event.text,
|
|
589
|
+
const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
|
|
575
590
|
if (evidence)
|
|
576
591
|
resolvedMonitorDeps.onFailureEvidence?.(evidence);
|
|
577
592
|
});
|
|
@@ -606,23 +621,22 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
606
621
|
unsubscribeRecovery?.();
|
|
607
622
|
throw error;
|
|
608
623
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
624
|
+
if (!role.roomMemberStartup)
|
|
625
|
+
control.setFleetSpawner(async (requested) => {
|
|
626
|
+
const event = await executeManagedSpawn(role, configPath, requested, deps.log);
|
|
627
|
+
// Room-member launches are collected with their Task/Room transaction so
|
|
628
|
+
// causal delivery is Task → Room → Agents → Room active → Task active.
|
|
629
|
+
if (!requested.roomMemberStartup && role.owner_channel && ownerChannel?.notifyFleetSpawn) {
|
|
630
|
+
try {
|
|
631
|
+
await ownerChannel.notifyFleetSpawn(event);
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
deps.log(`[${name}] Agent lifecycle notice delivery failed: `
|
|
635
|
+
+ `${error?.message ?? String(error)}`);
|
|
636
|
+
}
|
|
620
637
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
pendingFleetSpawnNotices.push(event);
|
|
624
|
-
return event;
|
|
625
|
-
});
|
|
638
|
+
return event;
|
|
639
|
+
});
|
|
626
640
|
resolvedMonitorDeps.delivery = {
|
|
627
641
|
// A wake is only delivered when its turn TERMINATES successfully. A
|
|
628
642
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
@@ -633,7 +647,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
633
647
|
// steer into the live turn instead; after it completes, honor the
|
|
634
648
|
// configured interrupt policy normally.
|
|
635
649
|
const policy = options?.interrupt;
|
|
636
|
-
const interrupt = policy === true &&
|
|
650
|
+
const interrupt = policy === true && sessionStartupComplete;
|
|
637
651
|
const promptOptions = {
|
|
638
652
|
interrupt, steer: true,
|
|
639
653
|
...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
|
|
@@ -641,7 +655,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
641
655
|
};
|
|
642
656
|
// Startup is already a protected boundary: as with immediate mode,
|
|
643
657
|
// steer rather than waiting on/cancelling the runner-owned first turn.
|
|
644
|
-
const result = policy === 'after_tool' &&
|
|
658
|
+
const result = policy === 'after_tool' && sessionStartupComplete
|
|
645
659
|
? await arbiter.submitPromptAfterTool(text, promptOptions)
|
|
646
660
|
: await arbiter.submitPrompt(text, promptOptions);
|
|
647
661
|
const steered = result.accepted
|
|
@@ -702,20 +716,20 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
702
716
|
exit: {
|
|
703
717
|
version: 1,
|
|
704
718
|
class: 'program-exit',
|
|
705
|
-
detail:
|
|
719
|
+
detail: `${sessionLabel} startup triggered model recovery (${modelRecovery})`,
|
|
706
720
|
},
|
|
707
721
|
rotated: false,
|
|
708
722
|
mode,
|
|
709
723
|
modelRecovery,
|
|
710
724
|
};
|
|
711
725
|
}
|
|
712
|
-
throw new Error(`[${name}]
|
|
726
|
+
throw new Error(`[${name}] ${sessionLabel} startup prompt ${started.outcome}` +
|
|
713
727
|
`${started.detail ? `: ${started.detail}` : ''}`);
|
|
714
728
|
}
|
|
715
729
|
if (interruptedForWake)
|
|
716
|
-
deps.log(`[${name}]
|
|
730
|
+
deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
|
|
717
731
|
+ 'keeping temporary supervisor alive');
|
|
718
|
-
|
|
732
|
+
sessionStartupComplete = true;
|
|
719
733
|
if (role.owner_channel) {
|
|
720
734
|
ownerChannel = deps.createOwnerChannel({
|
|
721
735
|
role: name,
|
|
@@ -743,18 +757,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
743
757
|
throw new Error(`[${name}] owner channel failed to start: `
|
|
744
758
|
+ `${error?.message ?? String(error)}`);
|
|
745
759
|
}
|
|
746
|
-
for (const event of pendingFleetSpawnNotices.splice(0)) {
|
|
747
|
-
try {
|
|
748
|
-
await ownerChannel.notifyFleetSpawn?.(event);
|
|
749
|
-
}
|
|
750
|
-
catch (error) {
|
|
751
|
-
deps.log(`[${name}] deferred spawned-agent owner notice failed: `
|
|
752
|
-
+ `${error?.message ?? String(error)}`);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
760
|
}
|
|
756
|
-
if (ownerChannel)
|
|
761
|
+
if (ownerChannel) {
|
|
757
762
|
control.setOwnerChannel(ownerChannel);
|
|
763
|
+
control.setFleetAuditor({
|
|
764
|
+
begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
|
|
765
|
+
finish: input => ownerChannel.finishFleetCommandAudit(input),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
758
768
|
reloadLoopConfig = async () => {
|
|
759
769
|
const nextRole = findRole(loadConfig(configPath), name);
|
|
760
770
|
const definitions = nextRole.loops ?? [];
|
|
@@ -801,16 +811,49 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
801
811
|
// The monitor loop lives exactly as long as the session: it starts once the
|
|
802
812
|
// pane pid is known and is stopped when that pid dies (task dies with runner).
|
|
803
813
|
monitorLoop ??= monitor?.run(pid);
|
|
814
|
+
recoveryController = new RoleRecoveryController({
|
|
815
|
+
role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
|
|
816
|
+
log: deps.log,
|
|
817
|
+
recoverAgent: async () => {
|
|
818
|
+
const evidence = await recoverAgentIdentity(arbiter, role.identity);
|
|
819
|
+
return evidence.ok ? { ok: true } : { ok: false, reason: evidence.reason };
|
|
820
|
+
},
|
|
821
|
+
recoverOwner: async (epoch) => {
|
|
822
|
+
if (!ownerChannel?.recover)
|
|
823
|
+
return { ok: true };
|
|
824
|
+
try {
|
|
825
|
+
await ownerChannel.recover(epoch);
|
|
826
|
+
return { ok: true };
|
|
827
|
+
}
|
|
828
|
+
catch (error) {
|
|
829
|
+
return { ok: false, reason: error instanceof Error
|
|
830
|
+
? `OWNER_${error.name.toUpperCase()}` : 'OWNER_UNKNOWN_ERROR' };
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
});
|
|
804
834
|
const start = deps.now();
|
|
805
835
|
let nextLoopReloadAt = deps.now() + 30_000;
|
|
806
836
|
let nextIdentityPollAt = deps.now();
|
|
837
|
+
let nextDaemonProbeAt = deps.now();
|
|
807
838
|
let lastReloadError = '';
|
|
808
839
|
let identityObserved = false;
|
|
809
840
|
let identityAbsentSince;
|
|
810
841
|
let retirementReason;
|
|
842
|
+
let supervisorRecycleRequired = false;
|
|
811
843
|
while (sessionHandle.isAlive()) {
|
|
812
844
|
await deps.sleep(temp ? 500 : 2000);
|
|
813
845
|
const now = deps.now();
|
|
846
|
+
if (now >= nextDaemonProbeAt) {
|
|
847
|
+
nextDaemonProbeAt = now + 2_000;
|
|
848
|
+
const observation = daemonObserver.observe(await deps.probeGeneration(resolvedMonitorDeps.env));
|
|
849
|
+
if (observation.kind === 'lost')
|
|
850
|
+
recoveryController.noteLoss(observation.reason);
|
|
851
|
+
if (observation.kind === 'changed' || observation.kind === 'available'
|
|
852
|
+
|| (observation.kind === 'baseline' && sessionStartedWithoutDaemonBaseline)) {
|
|
853
|
+
sessionStartedWithoutDaemonBaseline = false;
|
|
854
|
+
void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
|
|
855
|
+
}
|
|
856
|
+
}
|
|
814
857
|
if (temp && deps.shouldStop?.()) {
|
|
815
858
|
retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
|
|
816
859
|
deps.log(`[${name}] temporary supervisor retirement requested (${retirementReason})`);
|
|
@@ -861,6 +904,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
861
904
|
control?.setLoopManager(undefined);
|
|
862
905
|
await loopManager.stop();
|
|
863
906
|
}
|
|
907
|
+
// Let already-settled path operations publish their aggregate result before
|
|
908
|
+
// the shutdown fence invalidates the epoch. This never waits on I/O.
|
|
909
|
+
await Promise.resolve();
|
|
910
|
+
await Promise.resolve();
|
|
911
|
+
recoveryController?.cancel();
|
|
864
912
|
control?.setConfigReloader(undefined);
|
|
865
913
|
// Close the authenticated control route before releasing the binder lease;
|
|
866
914
|
// otherwise the predecessor can unlink the replacement's new socket.
|
|
@@ -871,7 +919,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
871
919
|
}
|
|
872
920
|
if (ownerChannel)
|
|
873
921
|
await ownerChannel.close();
|
|
874
|
-
|
|
922
|
+
if (ownerChannel?.binderReleaseSafe?.() === false) {
|
|
923
|
+
deps.log(`[${name}] owner binder retained because client quiescence was not proven at shutdown`);
|
|
924
|
+
supervisorRecycleRequired = true;
|
|
925
|
+
}
|
|
926
|
+
else
|
|
927
|
+
ownerBinder?.release();
|
|
875
928
|
if (monitor) {
|
|
876
929
|
monitor.stop();
|
|
877
930
|
await monitorLoop;
|
|
@@ -895,7 +948,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
895
948
|
rotated = true;
|
|
896
949
|
deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
|
|
897
950
|
};
|
|
898
|
-
if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
951
|
+
if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
|
|
952
|
+
|| exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
|
|
899
953
|
// This is a deliberate adapter reclamation, not evidence that resume state
|
|
900
954
|
// is poisoned. Preserve the context even when the resumed generation hits
|
|
901
955
|
// the same bound immediately; runSupervised still counts the fast exit and
|
|
@@ -920,6 +974,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
920
974
|
}
|
|
921
975
|
else
|
|
922
976
|
deps.log(`[${name}] ${exitRecord.detail} (${elapsed.toFixed(0)}s) -> next start RESUMES context`);
|
|
977
|
+
if (supervisorRecycleRequired)
|
|
978
|
+
throw new SupervisorRecycleRequiredError();
|
|
923
979
|
return {
|
|
924
980
|
elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
|
|
925
981
|
...(retirementReason ? { retirementReason } : {}),
|
|
@@ -996,6 +1052,16 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
|
|
|
996
1052
|
result = await attempt(name, { configPath: opts.configPath, allowResumeRotation: !ledger.resumeDiscarded }, deps);
|
|
997
1053
|
}
|
|
998
1054
|
catch (e) {
|
|
1055
|
+
if (e instanceof SupervisorRecycleRequiredError) {
|
|
1056
|
+
writeRestartLedger(dir, {
|
|
1057
|
+
...ledger,
|
|
1058
|
+
lastReason: SUPERVISOR_RECYCLE_REQUIRED,
|
|
1059
|
+
nextDelayMs: 0,
|
|
1060
|
+
updatedAt: stamp(),
|
|
1061
|
+
});
|
|
1062
|
+
deps.log(`[${name}] supervisor recycle required: ${SUPERVISOR_RECYCLE_REQUIRED}`);
|
|
1063
|
+
throw e;
|
|
1064
|
+
}
|
|
999
1065
|
// A session that could not even start is an immediate failure like any
|
|
1000
1066
|
// other; it must count, or an unstartable role loops forever.
|
|
1001
1067
|
result = {
|
|
@@ -1089,7 +1155,7 @@ function fastFailSecsFor(name, configPath) {
|
|
|
1089
1155
|
}
|
|
1090
1156
|
}
|
|
1091
1157
|
/** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
|
|
1092
|
-
export async function runTemp(name, deps = {}) {
|
|
1158
|
+
export async function runTemp(name, deps = {}, attempt = runOnce) {
|
|
1093
1159
|
const dir = agentDir(name, true);
|
|
1094
1160
|
await markTempSupervisorActive(dir);
|
|
1095
1161
|
let signal;
|
|
@@ -1100,7 +1166,7 @@ export async function runTemp(name, deps = {}) {
|
|
|
1100
1166
|
let result;
|
|
1101
1167
|
let failure;
|
|
1102
1168
|
try {
|
|
1103
|
-
result = await
|
|
1169
|
+
result = await attempt(name, { temp: true }, {
|
|
1104
1170
|
...deps,
|
|
1105
1171
|
shouldStop: () => Boolean(signal) || (deps.shouldStop?.() ?? false),
|
|
1106
1172
|
});
|
|
@@ -1114,6 +1180,7 @@ export async function runTemp(name, deps = {}) {
|
|
|
1114
1180
|
const requested = requestedTempStopReason(dir);
|
|
1115
1181
|
const reason = requested
|
|
1116
1182
|
?? result?.retirementReason
|
|
1183
|
+
?? (failure instanceof SupervisorRecycleRequiredError ? 'supervisor-recycle' : undefined)
|
|
1117
1184
|
?? (signal ? 'supervisor-signal' : failure ? 'startup-failure' : 'session-ended');
|
|
1118
1185
|
// A service-manager stop can make the child connection close before the
|
|
1119
1186
|
// runner reaches its normal loop. That is still a successful requested
|
package/dist/session/acp.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export declare function promptContentBlocks(text: string, origin?: PromptOrigin)
|
|
|
28
28
|
export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
|
|
29
29
|
export interface AcpSessionOptions {
|
|
30
30
|
name: string;
|
|
31
|
+
/** Harness identity used only for honest optional capability reporting. */
|
|
32
|
+
harness?: string;
|
|
31
33
|
argv: string[];
|
|
32
34
|
cwd: string;
|
|
33
35
|
env: Record<string, string>;
|
|
@@ -36,6 +38,11 @@ export interface AcpSessionOptions {
|
|
|
36
38
|
permissions: CommonPermissions;
|
|
37
39
|
/** Native permission-mode id to request via session/set_mode; undefined keeps the agent default. */
|
|
38
40
|
modeId?: string;
|
|
41
|
+
/** Ordered explicit Brain choices that must be applied before readiness. */
|
|
42
|
+
configSelections?: Array<{
|
|
43
|
+
configId: string;
|
|
44
|
+
value: string;
|
|
45
|
+
}>;
|
|
39
46
|
/** Adapter-resolved live permission policy; separate from ACP agent-specific session modes. */
|
|
40
47
|
permissionMode?: NonNullable<SessionSnapshot['permissionMode']>;
|
|
41
48
|
/** Adapter-authenticated request-metadata vocabulary; never inferred from ACP `_meta`. */
|
|
@@ -84,6 +91,7 @@ export declare function classifyStopReason(stopReason: string | undefined): Turn
|
|
|
84
91
|
export declare class AcpSession implements AgentSession {
|
|
85
92
|
private readonly options;
|
|
86
93
|
readonly backend: "acp";
|
|
94
|
+
readonly capabilities: import("./types.js").AgentSessionCapabilities;
|
|
87
95
|
readonly pid: number;
|
|
88
96
|
private readonly child;
|
|
89
97
|
private readonly events;
|
|
@@ -110,7 +118,7 @@ export declare class AcpSession implements AgentSession {
|
|
|
110
118
|
private queueDepth;
|
|
111
119
|
private exit;
|
|
112
120
|
private steeringSupported;
|
|
113
|
-
private
|
|
121
|
+
private agentCapabilities?;
|
|
114
122
|
private runtimeModel?;
|
|
115
123
|
private reasoningEffort?;
|
|
116
124
|
private controllerCount;
|