@ours.network/fleet 1.1.0-nightly.9 → 1.1.1

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.
Files changed (202) hide show
  1. package/README.md +285 -46
  2. package/dist/application/capabilities.js +18 -6
  3. package/dist/application/fleet-query-service.js +7 -4
  4. package/dist/application/role-command-service.js +9 -0
  5. package/dist/application/role-creation-service.d.ts +4 -4
  6. package/dist/application/role-creation-service.js +8 -1
  7. package/dist/application/role-repository.d.ts +4 -2
  8. package/dist/application/role-repository.js +6 -3
  9. package/dist/application/session-control.d.ts +3 -1
  10. package/dist/application/session-control.js +4 -2
  11. package/dist/application/task-room-service.d.ts +92 -39
  12. package/dist/application/task-room-service.js +562 -289
  13. package/dist/briefing.js +79 -22
  14. package/dist/build-info.json +5 -5
  15. package/dist/canonical-json.d.ts +2 -0
  16. package/dist/canonical-json.js +10 -0
  17. package/dist/cli.js +129 -47
  18. package/dist/config.d.ts +42 -3
  19. package/dist/config.js +202 -29
  20. package/dist/creation.d.ts +1 -1
  21. package/dist/creation.js +7 -4
  22. package/dist/docs.d.ts +1 -1
  23. package/dist/docs.js +195 -41
  24. package/dist/doctor.d.ts +2 -0
  25. package/dist/doctor.js +40 -4
  26. package/dist/fleet-command-audit.d.ts +22 -1
  27. package/dist/fleet-command-audit.js +355 -50
  28. package/dist/fleet-proxy.d.ts +5 -2
  29. package/dist/harness/claude-code-session.js +5 -1
  30. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  31. package/dist/harness/codex-app-server-proxy.js +348 -14
  32. package/dist/harness/codex-session.d.ts +9 -2
  33. package/dist/harness/codex-session.js +21 -2
  34. package/dist/harness/codex.d.ts +5 -1
  35. package/dist/harness/codex.js +104 -10
  36. package/dist/harness/types.d.ts +1 -1
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +3 -2
  39. package/dist/init-guidance.d.ts +1 -1
  40. package/dist/init-guidance.js +1 -1
  41. package/dist/init-wizard.d.ts +98 -0
  42. package/dist/init-wizard.js +631 -0
  43. package/dist/lifecycle-summary.d.ts +134 -0
  44. package/dist/lifecycle-summary.js +179 -0
  45. package/dist/loops/config.d.ts +10 -1
  46. package/dist/loops/config.js +38 -5
  47. package/dist/model-recovery.d.ts +1 -1
  48. package/dist/ops.js +2 -17
  49. package/dist/owner-channel/channel.d.ts +19 -3
  50. package/dist/owner-channel/channel.js +301 -120
  51. package/dist/owner-channel/commands.d.ts +28 -6
  52. package/dist/owner-channel/commands.js +109 -27
  53. package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
  54. package/dist/owner-channel/lifecycle-outbox.js +87 -0
  55. package/dist/owner-channel/ours-client.d.ts +19 -0
  56. package/dist/owner-channel/ours-client.js +3 -0
  57. package/dist/permissions.js +1 -1
  58. package/dist/preset-migration.d.ts +42 -0
  59. package/dist/preset-migration.js +450 -0
  60. package/dist/reports/artifact.d.ts +10 -0
  61. package/dist/reports/artifact.js +32 -0
  62. package/dist/reports/index.d.ts +5 -0
  63. package/dist/reports/index.js +5 -0
  64. package/dist/reports/render.d.ts +6 -0
  65. package/dist/reports/render.js +240 -0
  66. package/dist/reports/service.d.ts +42 -0
  67. package/dist/reports/service.js +57 -0
  68. package/dist/reports/tasks.d.ts +52 -0
  69. package/dist/reports/tasks.js +177 -0
  70. package/dist/reports/types.d.ts +315 -0
  71. package/dist/reports/types.js +6 -0
  72. package/dist/resolved-plan.js +13 -1
  73. package/dist/rooms-tasks/cli.d.ts +2 -0
  74. package/dist/rooms-tasks/cli.js +361 -285
  75. package/dist/rooms-tasks/close.d.ts +9 -1
  76. package/dist/rooms-tasks/close.js +23 -7
  77. package/dist/rooms-tasks/config.js +16 -25
  78. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  79. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  80. package/dist/rooms-tasks/deletion.d.ts +51 -0
  81. package/dist/rooms-tasks/deletion.js +217 -0
  82. package/dist/rooms-tasks/external-worker.d.ts +8 -0
  83. package/dist/rooms-tasks/external-worker.js +38 -0
  84. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  85. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  86. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  87. package/dist/rooms-tasks/member-overrides.js +204 -0
  88. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  89. package/dist/rooms-tasks/member-startup.js +9 -2
  90. package/dist/rooms-tasks/provision.js +146 -83
  91. package/dist/rooms-tasks/room-state.d.ts +1 -0
  92. package/dist/rooms-tasks/room-state.js +12 -1
  93. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  94. package/dist/rooms-tasks/task-room-name.js +61 -0
  95. package/dist/rooms-tasks/task-state.d.ts +115 -3
  96. package/dist/rooms-tasks/task-state.js +361 -11
  97. package/dist/rooms-tasks/templates.d.ts +4 -1
  98. package/dist/rooms-tasks/templates.js +34 -3
  99. package/dist/rooms-tasks/terminal.d.ts +3 -0
  100. package/dist/rooms-tasks/terminal.js +10 -4
  101. package/dist/rooms-tasks/types.d.ts +92 -7
  102. package/dist/rooms-tasks/types.js +14 -1
  103. package/dist/runner.d.ts +3 -0
  104. package/dist/runner.js +97 -47
  105. package/dist/session/acp.d.ts +27 -1
  106. package/dist/session/acp.js +307 -17
  107. package/dist/session/arbiter.d.ts +2 -1
  108. package/dist/session/arbiter.js +2 -0
  109. package/dist/session/codex-app-server-transport.d.ts +53 -0
  110. package/dist/session/codex-app-server-transport.js +184 -0
  111. package/dist/session/codex-app-server.d.ts +122 -0
  112. package/dist/session/codex-app-server.js +1031 -0
  113. package/dist/session/control.d.ts +1 -1
  114. package/dist/session/control.js +17 -1
  115. package/dist/session/conversation-types.d.ts +4 -4
  116. package/dist/session/stall-watchdog.d.ts +60 -0
  117. package/dist/session/stall-watchdog.js +210 -0
  118. package/dist/session/types.d.ts +29 -8
  119. package/dist/session/types.js +28 -0
  120. package/dist/spawn.d.ts +9 -0
  121. package/dist/spawn.js +77 -9
  122. package/dist/web/fleet-config-service.d.ts +1 -0
  123. package/dist/web/fleet-config-service.js +25 -7
  124. package/dist/web/runtime.js +11 -6
  125. package/dist/web/server.js +28 -1
  126. package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
  127. package/dist/web-app/index.html +1 -1
  128. package/package.json +4 -4
  129. package/presets/brain-catalog.json +17 -0
  130. package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
  131. package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
  132. package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
  133. package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
  134. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  135. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  136. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  137. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  138. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  139. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  140. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  141. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  142. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  143. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  144. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  145. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  146. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  147. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  148. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  149. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  150. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  151. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  152. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  153. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  154. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  155. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  156. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  157. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  158. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  159. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
  188. package/presets/fleet/roles/Coordinator.yaml +37 -0
  189. package/presets/fleet/roles/Critic.yaml +23 -1
  190. package/presets/fleet/roles/Developer.yaml +27 -5
  191. package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
  192. package/presets/fleet/room_templates/pair.yaml +9 -7
  193. package/presets/fleet/room_templates/single.yaml +5 -4
  194. package/presets/fleet/room_templates/team.yaml +12 -8
  195. package/presets/manifest.json +2 -1
  196. package/presets/fleet/agents/Architect.yaml +0 -3
  197. package/presets/fleet/agents/Secretary.yaml +0 -3
  198. package/presets/fleet/agents/Tester.yaml +0 -3
  199. package/presets/fleet/roles/Agent.yaml +0 -6
  200. package/presets/fleet/roles/Architect.yaml +0 -6
  201. package/presets/fleet/roles/Secretary.yaml +0 -6
  202. package/presets/fleet/roles/Tester.yaml +0 -6
@@ -1,14 +1,45 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from '../canonical-json.js';
3
+ import { redactLaunchDefinition, sealLaunchSnapshot } from './launch-snapshot.js';
2
4
  export function hashTemplate(t) {
3
- const canonical = JSON.stringify({
5
+ const canonical = canonicalJson({
4
6
  name: t.name, version: t.version, description: t.description,
5
7
  room: t.room, contract: t.contract, members: t.members,
6
8
  });
7
9
  return createHash('sha256').update(canonical).digest('hex');
8
10
  }
9
- export function snapshotTemplate(t) {
11
+ export function snapshotTemplate(t, agentTemplates) {
10
12
  const { sourceFile: _, ...semantic } = t;
11
- return { ...semantic, content_hash: hashTemplate(t) };
13
+ return { ...semantic, members: semantic.members.map(member => {
14
+ const definition = agentTemplates?.[member.agent_template];
15
+ return {
16
+ ...member,
17
+ ...(definition ? {
18
+ agent_projection: redactLaunchDefinition(definition),
19
+ agent_template_hash: createHash('sha256').update(canonicalJson(definition)).digest('hex'),
20
+ } : {}),
21
+ };
22
+ }), content_hash: agentTemplates ? createHash('sha256').update(canonicalJson({
23
+ template: hashTemplate(t),
24
+ members: semantic.members.map(member => ({
25
+ agent_template: member.agent_template,
26
+ agent_template_hash: agentTemplates?.[member.agent_template]
27
+ ? createHash('sha256').update(canonicalJson(agentTemplates[member.agent_template])).digest('hex')
28
+ : null,
29
+ })),
30
+ })).digest('hex') : hashTemplate(t) };
31
+ }
32
+ /** Explicit mutation boundary immediately before Task/Room plan persistence. */
33
+ export function sealTemplateSnapshot(snapshot, agentTemplates, launchDefinitions) {
34
+ const referenced = Object.fromEntries([...new Set(snapshot.members.map(member => member.launch_definition_id ?? member.agent_template))]
35
+ .sort().map(id => {
36
+ const member = snapshot.members.find(candidate => (candidate.launch_definition_id ?? candidate.agent_template) === id);
37
+ const definition = launchDefinitions?.[id] ?? agentTemplates[member.agent_template];
38
+ if (!definition)
39
+ throw new Error(`Agent Template '${id}' not found`);
40
+ return [id, definition];
41
+ }));
42
+ return { ...snapshot, launch_snapshot_hash: sealLaunchSnapshot(referenced) };
12
43
  }
13
44
  export function resolveTemplate(name, customTemplates) {
14
45
  const atIdx = name.lastIndexOf('@');
@@ -1,6 +1,9 @@
1
1
  import { type RoomCloseDeps } from './close.js';
2
2
  import type { CoworkAdapter } from './cowork-adapter.js';
3
3
  import type { TaskOutcome, TaskRecord, TaskTerminalIntent } from './types.js';
4
+ export declare const TASK_OPERATION_LOCK_STALE_MS: number;
5
+ /** Common per-task operation lock serializing terminal, deletion, and recovery acceptance. */
6
+ export declare function taskOperationLockPath(taskId: string): string;
4
7
  export interface TaskTerminalInput {
5
8
  taskId: string;
6
9
  kind: TaskTerminalIntent['kind'];
@@ -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
- function taskOperationLockPath(taskId) {
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`);
@@ -45,7 +49,9 @@ export function settleTaskTerminalIntent(input) {
45
49
  return finishTaskTerminalIntent(input.taskId);
46
50
  }
47
51
  catch (error) {
48
- setTaskTerminalIntentError(input.taskId, errorText(error), `Retry 'ours-fleet task recover ${input.taskId}'.`);
52
+ setTaskTerminalIntentError(input.taskId, errorText(error), intent.kind === 'cancelled'
53
+ ? `Retry 'ours-fleet task cancel ${input.taskId} ${input.taskId}'.`
54
+ : `Retry 'ours-fleet task done ${input.taskId}'.`);
49
55
  throw error;
50
56
  }
51
57
  }, {}, TASK_OPERATION_LOCK_STALE_MS);
@@ -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;
@@ -82,7 +133,7 @@ export interface SagaCursor {
82
133
  error?: string;
83
134
  recovery_hint?: string;
84
135
  }
85
- export type ProvisioningDetail = 'waiting_cowork' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'member_failed' | 'waiting_seats' | 'uncertain';
136
+ export type ProvisioningDetail = 'waiting_cowork' | 'waiting_owner_authorization' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'member_failed' | 'waiting_seats' | 'uncertain';
86
137
  export interface RoomRoleBriefingDefinition {
87
138
  role: string;
88
139
  text: string;
@@ -103,6 +154,13 @@ export interface RoomMemberLaunchState {
103
154
  /** Redacted, deterministic effective Agent launch definition retained for retry inspection. */
104
155
  agent_definition?: Record<string, unknown>;
105
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;
106
164
  launch_id?: string;
107
165
  updated_at: string;
108
166
  error?: string;
@@ -175,6 +233,8 @@ export interface RoomOrchestrationRecord {
175
233
  goal?: string;
176
234
  task_id?: string;
177
235
  template_snapshot?: TemplateSnapshot;
236
+ /** Resolved once at launch; legacy absence means non-anonymous. */
237
+ room_policy?: RoomLaunchPolicy;
178
238
  saga: SagaCursor;
179
239
  provisioning_detail?: ProvisioningDetail;
180
240
  owner_seat_cid?: string;
@@ -192,15 +252,19 @@ export interface TemplateMemberSlot {
192
252
  slot: string;
193
253
  role: string;
194
254
  count: number;
195
- /** Canonical Agent definition, or stable reference to a declared Agent. */
196
- agent: import('../config.js').AgentDefinition | {
197
- ref: string;
198
- };
255
+ /** Stable reference to an inert Agent Template, never a persistent Agent. */
256
+ agent_template: string;
199
257
  }
200
258
  export interface TemplateRoomConfig {
201
259
  quiet_membership?: boolean;
202
260
  anonymous?: boolean;
203
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;
204
268
  export interface TemplateDefinition {
205
269
  name: string;
206
270
  version: number;
@@ -211,8 +275,27 @@ export interface TemplateDefinition {
211
275
  contract?: string;
212
276
  members: TemplateMemberSlot[];
213
277
  }
214
- export interface TemplateSnapshot extends TemplateDefinition {
278
+ export interface TemplateSnapshotMember extends TemplateMemberSlot {
279
+ /** Source of the effective temporary-loop policy, independent of its sealed value. */
280
+ loop_source?: 'agent-template' | 'cli' | 'omitted';
281
+ /** Secret-safe public projection; launch material lives in the sealed snapshot. */
282
+ agent_projection?: Record<string, unknown>;
283
+ agent_template_hash?: string;
284
+ /** Private sealed-snapshot lookup key; harmless provenance, not an identity. */
285
+ launch_definition_id?: string;
286
+ role_preset?: {
287
+ id: string;
288
+ hash: string;
289
+ };
290
+ brain_preset?: {
291
+ id: string;
292
+ hash: string;
293
+ };
294
+ }
295
+ export interface TemplateSnapshot extends Omit<TemplateDefinition, 'members'> {
296
+ members: TemplateSnapshotMember[];
215
297
  content_hash: string;
298
+ launch_snapshot_hash?: string;
216
299
  }
217
300
  export interface RoomsOwnerConfig {
218
301
  provider: string;
@@ -247,4 +330,6 @@ export declare const ROOMS_COWORK_KEYS: readonly ["config"];
247
330
  export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
248
331
  export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
249
332
  export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
250
- export declare const TEMPLATE_MEMBER_KEYS: readonly ["slot", "role", "count", "agent"];
333
+ export declare const TEMPLATE_ROOM_KEYS: readonly ["quiet_membership", "anonymous"];
334
+ /** `agent` is accepted only so validation can emit its actionable migration error. */
335
+ 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,4 +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 TEMPLATE_MEMBER_KEYS = ['slot', 'role', 'count', 'agent'];
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
@@ -7,6 +7,7 @@ import type { AgentSession, ExitRecord, TurnResult } from './session/types.js';
7
7
  import type { AgentSessionAdapter, AgentSessionStartOptions } from './harness/agent-session.js';
8
8
  import { type OwnerChannelHandle, type OwnerChannelOptions } from './owner-channel/channel.js';
9
9
  import { type OwnerBinderLease } from './owner-channel/binder.js';
10
+ import { ScheduledLoopManager, type ScheduledLoopManagerHandle } from './loops/manager.js';
10
11
  export interface RunnerDeps {
11
12
  exec: Exec;
12
13
  cpuDelegated(): boolean;
@@ -26,6 +27,8 @@ export interface RunnerDeps {
26
27
  startAgentSession(adapter: AgentSessionAdapter, options: AgentSessionStartOptions): Promise<AgentSession>;
27
28
  /** Construct the authenticated role control route (injectable where sockets are unavailable). */
28
29
  createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
30
+ /** Construct scheduled-loop execution (injectable for fail-closed startup tests). */
31
+ createLoopManager(...args: ConstructorParameters<typeof ScheduledLoopManager>): ScheduledLoopManagerHandle;
29
32
  /** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
30
33
  acquireOwnerBinder(stateDir: string, role: string, identity: string): Promise<OwnerBinderLease>;
31
34
  /** Ask the still-authenticated predecessor to emit the fixed recovery notice. */
package/dist/runner.js CHANGED
@@ -14,7 +14,7 @@ import { selectIsolationBackend } from './isolation/registry.js';
14
14
  import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
15
15
  import { resolveLaunchRuntime } from './isolation/runtime.js';
16
16
  import { controlRequest, RoleControlServer } from './session/control.js';
17
- 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';
18
18
  import { effectiveModelForRole, modelRecoveryHeld, reconcileModelRecovery, recordModelFailure, classifyFailureText, } from './model-recovery.js';
19
19
  import { rotateWorklog } from './worklog.js';
20
20
  import { reconcilePermanentRoleIdentities } from './creation.js';
@@ -26,6 +26,7 @@ import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-prox
26
26
  import { effectivePermissionMode } from './permissions.js';
27
27
  import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
28
28
  import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
29
+ import { FleetCommandAuditStore } from './fleet-command-audit.js';
29
30
  export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
30
31
  export class SupervisorRecycleRequiredError extends Error {
31
32
  code = SUPERVISOR_RECYCLE_REQUIRED;
@@ -53,6 +54,7 @@ const defaultDeps = () => ({
53
54
  createOwnerChannel: opts => new OwnerChannel(opts),
54
55
  startAgentSession: (adapter, options) => adapter.start(options),
55
56
  createControlServer: (stateDir, session, log) => new RoleControlServer(stateDir, session, log),
57
+ createLoopManager: (...args) => new ScheduledLoopManager(...args),
56
58
  acquireOwnerBinder: (stateDir, role, identity) => acquireOwnerBinderLease(stateDir, role, identity),
57
59
  reportOwnerStartupFailure: async (stateDir) => {
58
60
  const response = await controlRequest(stateDir, {
@@ -69,6 +71,32 @@ const defaultDeps = () => ({
69
71
  });
70
72
  const MONITOR_OWNER_FILE = '.monitor-owner';
71
73
  const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
74
+ function localFleetAuditor(stateDir, caller, log) {
75
+ const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
76
+ return {
77
+ async begin(requestId, argv) {
78
+ let attempt = store.begin(requestId, caller, argv);
79
+ if (attempt.invocation === 'sending') {
80
+ log(`[${caller}] fleet proxy command ${attempt.correlationId} `
81
+ + `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
82
+ attempt = store.invocation(attempt.correlationId, caller, 'delivered');
83
+ }
84
+ return attempt;
85
+ },
86
+ async finish(input) {
87
+ let attempt = store.finish(input.correlationId, caller, {
88
+ class: input.class, effect: input.effect,
89
+ ...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
90
+ ...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
91
+ ...(input.presentations ? { presentations: input.presentations } : {}),
92
+ });
93
+ if (attempt.outcome?.delivery === 'sending')
94
+ attempt = store.outcome(attempt.correlationId, caller, 'delivered');
95
+ return attempt;
96
+ },
97
+ async present() { },
98
+ };
99
+ }
72
100
  /** Environment injected only into the managed harness process. */
73
101
  export function managedFleetProxyEnv(role, stateDir) {
74
102
  const roleEnv = { ...(role.env ?? {}) };
@@ -464,6 +492,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
464
492
  const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
465
493
  const prep = await adapter.prepareSession(role, { stateDir: dir, runCwd });
466
494
  const sessionBackend = role.session ?? 'acp';
495
+ const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
467
496
  let launch = adapter.agentSession.prepareLaunch(role, prep);
468
497
  // Isolation is additive: only roles that declare `isolation:` are wrapped.
469
498
  let wrappedArgv = launch.argv;
@@ -554,7 +583,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
554
583
  let control;
555
584
  let unsubscribeRecovery;
556
585
  let monitorLoop;
557
- let acpStartupComplete = false;
586
+ let sessionStartupComplete = false;
558
587
  let sessionClosed = false;
559
588
  let ownerChannel;
560
589
  let ownerBinder;
@@ -583,9 +612,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
583
612
  arbiter = new RoleTurnArbiter(agentSession);
584
613
  sessionHandle = arbiter;
585
614
  unsubscribeRecovery = agentSession.subscribe(event => {
586
- if (event.kind !== 'error' || !event.text)
615
+ if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
587
616
  return;
588
- const evidence = classifyFailureText(event.text, 'acp', new Date(deps.now()).toISOString());
617
+ const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
589
618
  if (evidence)
590
619
  resolvedMonitorDeps.onFailureEvidence?.(evidence);
591
620
  });
@@ -610,31 +639,50 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
610
639
  + `${error?.message ?? String(error)}`);
611
640
  }
612
641
  }
613
- control = deps.createControlServer(dir, arbiter, deps.log);
614
642
  try {
643
+ if (role.owner_channel)
644
+ ownerChannel = deps.createOwnerChannel({
645
+ role: name,
646
+ harness: role.harness,
647
+ config: role.owner_channel,
648
+ session: arbiter,
649
+ stateDir: dir,
650
+ env: role.env,
651
+ log: deps.log,
652
+ ...(ownerBinder ? { binderLease: ownerBinder } : {}),
653
+ ...(configPath ? { configPath } : {}),
654
+ });
655
+ control = deps.createControlServer(dir, arbiter, deps.log);
656
+ control.setFleetAuditor(ownerChannel ? {
657
+ begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
658
+ finish: input => ownerChannel.finishFleetCommandAudit(input),
659
+ present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
660
+ } : localFleetAuditor(dir, name, deps.log));
615
661
  await control.start();
616
662
  }
617
663
  catch (error) {
664
+ await ownerChannel?.close().catch(() => undefined);
618
665
  ownerBinder?.release();
619
666
  await agentSession.close();
620
667
  unsubscribeRecovery?.();
621
668
  throw error;
622
669
  }
623
- control.setFleetSpawner(async (requested) => {
624
- const event = await executeManagedSpawn(role, configPath, requested, deps.log);
625
- // Room-member launches are collected with their Task/Room transaction so
626
- // causal delivery is Task → Room → Agents → Room active → Task active.
627
- if (!requested.roomMemberStartup && role.owner_channel && ownerChannel?.notifyFleetSpawn) {
628
- try {
629
- await ownerChannel.notifyFleetSpawn(event);
630
- }
631
- catch (error) {
632
- deps.log(`[${name}] Agent lifecycle notice delivery failed: `
633
- + `${error?.message ?? String(error)}`);
670
+ if (!role.roomMemberStartup)
671
+ control.setFleetSpawner(async (requested) => {
672
+ const event = await executeManagedSpawn(role, configPath, requested, deps.log);
673
+ // Room-member launches are collected with their Task/Room transaction so
674
+ // causal delivery is Task → Room → Agents → Room active → Task active.
675
+ if (!requested.roomMemberStartup && role.owner_channel && ownerChannel?.notifyFleetSpawn) {
676
+ try {
677
+ await ownerChannel.notifyFleetSpawn(event);
678
+ }
679
+ catch (error) {
680
+ deps.log(`[${name}] Agent lifecycle notice delivery failed: `
681
+ + `${error?.message ?? String(error)}`);
682
+ }
634
683
  }
635
- }
636
- return event;
637
- });
684
+ return event;
685
+ });
638
686
  resolvedMonitorDeps.delivery = {
639
687
  // A wake is only delivered when its turn TERMINATES successfully. A
640
688
  // refusal or a cancellation reached the agent and was not acted on, so
@@ -645,7 +693,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
645
693
  // steer into the live turn instead; after it completes, honor the
646
694
  // configured interrupt policy normally.
647
695
  const policy = options?.interrupt;
648
- const interrupt = policy === true && acpStartupComplete;
696
+ const interrupt = policy === true && sessionStartupComplete;
649
697
  const promptOptions = {
650
698
  interrupt, steer: true,
651
699
  ...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
@@ -653,7 +701,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
653
701
  };
654
702
  // Startup is already a protected boundary: as with immediate mode,
655
703
  // steer rather than waiting on/cancelling the runner-owned first turn.
656
- const result = policy === 'after_tool' && acpStartupComplete
704
+ const result = policy === 'after_tool' && sessionStartupComplete
657
705
  ? await arbiter.submitPromptAfterTool(text, promptOptions)
658
706
  : await arbiter.submitPrompt(text, promptOptions);
659
707
  const steered = result.accepted
@@ -699,7 +747,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
699
747
  // valid. Keep every unproven cancellation, refusal, shutdown, and genuine
700
748
  // failure terminal so a role that never accepted its briefing is not
701
749
  // silently reported as healthy.
702
- const interruptedForWake = isRecoverableTempStartupCancellation(temp, started);
750
+ const interruptedForWake = isRecoverableTempStartupCancellation(temp, started)
751
+ || (started.outcome === 'cancelled' && started.cancellationSource === 'stall-watchdog');
703
752
  if (!started.succeeded && !interruptedForWake) {
704
753
  monitor?.stop();
705
754
  await control.close();
@@ -714,32 +763,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
714
763
  exit: {
715
764
  version: 1,
716
765
  class: 'program-exit',
717
- detail: `ACP startup triggered model recovery (${modelRecovery})`,
766
+ detail: `${sessionLabel} startup triggered model recovery (${modelRecovery})`,
718
767
  },
719
768
  rotated: false,
720
769
  mode,
721
770
  modelRecovery,
722
771
  };
723
772
  }
724
- throw new Error(`[${name}] ACP startup prompt ${started.outcome}` +
773
+ throw new Error(`[${name}] ${sessionLabel} startup prompt ${started.outcome}` +
725
774
  `${started.detail ? `: ${started.detail}` : ''}`);
726
775
  }
727
- if (interruptedForWake)
728
- deps.log(`[${name}] ACP startup prompt cancelled by ${started.cancellationSource}; `
776
+ if (started.cancellationSource === 'stall-watchdog')
777
+ deps.log(`[${name}] ${sessionLabel} startup diagnostic recovery requires operator attention; keeping supervisor alive`);
778
+ else if (interruptedForWake)
779
+ deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
729
780
  + 'keeping temporary supervisor alive');
730
- acpStartupComplete = true;
731
- if (role.owner_channel) {
732
- ownerChannel = deps.createOwnerChannel({
733
- role: name,
734
- harness: role.harness,
735
- config: role.owner_channel,
736
- session: arbiter,
737
- stateDir: dir,
738
- env: role.env,
739
- log: deps.log,
740
- ...(ownerBinder ? { binderLease: ownerBinder } : {}),
741
- ...(configPath ? { configPath } : {}),
742
- });
781
+ sessionStartupComplete = true;
782
+ if (ownerChannel) {
743
783
  try {
744
784
  await ownerChannel.start();
745
785
  }
@@ -758,10 +798,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
758
798
  }
759
799
  if (ownerChannel) {
760
800
  control.setOwnerChannel(ownerChannel);
761
- control.setFleetAuditor({
762
- begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
763
- finish: input => ownerChannel.finishFleetCommandAudit(input),
764
- });
765
801
  }
766
802
  reloadLoopConfig = async () => {
767
803
  const nextRole = findRole(loadConfig(configPath), name);
@@ -772,7 +808,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
772
808
  if (generation === loopGeneration && (loopManager || !definitions.length))
773
809
  return { changed: false, loops: definitions.length };
774
810
  if (!loopManager && definitions.length) {
775
- loopManager = new ScheduledLoopManager(name, definitions, dir, arbiter, {
811
+ loopManager = deps.createLoopManager(name, definitions, dir, arbiter, {
776
812
  now: deps.now,
777
813
  setTimer: (callback, ms) => setTimeout(callback, ms),
778
814
  clearTimer: timer => clearTimeout(timer),
@@ -788,10 +824,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
788
824
  deps.log(`[${name}] scheduled loops reloaded (${definitions.length} definitions)`);
789
825
  return { changed: true, loops: definitions.length };
790
826
  };
791
- control.setConfigReloader(reloadLoopConfig);
827
+ // Temporary agents are immutable launch snapshots: never re-resolve mutable Fleet YAML.
828
+ if (!temp)
829
+ control.setConfigReloader(reloadLoopConfig);
792
830
  if (role.loops?.length) {
793
831
  try {
794
- loopManager = new ScheduledLoopManager(name, role.loops, dir, arbiter, {
832
+ loopManager = deps.createLoopManager(name, role.loops, dir, arbiter, {
795
833
  now: deps.now,
796
834
  setTimer: (callback, ms) => setTimeout(callback, ms),
797
835
  clearTimer: timer => clearTimeout(timer),
@@ -801,6 +839,17 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
801
839
  loopManager.start();
802
840
  }
803
841
  catch (error) {
842
+ if (temp) {
843
+ monitor?.stop();
844
+ if (monitorLoop)
845
+ await monitorLoop;
846
+ await control.close();
847
+ ownerBinder?.release();
848
+ await agentSession.close();
849
+ unsubscribeRecovery?.();
850
+ throw new Error(`[${name}] configured temporary loop manager failed to start: `
851
+ + `${error?.message ?? String(error)}`);
852
+ }
804
853
  deps.log(`[${name}] scheduled loop manager unavailable: ${error?.name ?? 'Error'}`);
805
854
  }
806
855
  }
@@ -946,7 +995,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
946
995
  rotated = true;
947
996
  deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
948
997
  };
949
- if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED))
998
+ if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
999
+ || exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
950
1000
  // This is a deliberate adapter reclamation, not evidence that resume state
951
1001
  // is poisoned. Preserve the context even when the resumed generation hits
952
1002
  // the same bound immediately; runSupervised still counts the fast exit and
@@ -27,7 +27,15 @@ export declare const CODEX_DISABLE_INHERITED_MCP_ENV = "OURS_FLEET_CODEX_DISABLE
27
27
  export declare function promptContentBlocks(text: string, origin?: PromptOrigin): acp.ContentBlock[];
28
28
  export declare function runtimeSelector(options: acp.SessionConfigOption[] | null | undefined, category: string): RuntimeSelectorMetadata | undefined;
29
29
  export interface AcpSessionOptions {
30
+ /** Opt-in Fleet watchdog, owned by this ACP session, never a process restart. */
31
+ stallRecovery?: {
32
+ timeoutMs?: number;
33
+ tickMs?: number;
34
+ cancelWaitMs?: number;
35
+ };
30
36
  name: string;
37
+ /** Harness identity used only for honest optional capability reporting. */
38
+ harness?: string;
31
39
  argv: string[];
32
40
  cwd: string;
33
41
  env: Record<string, string>;
@@ -89,6 +97,7 @@ export declare function classifyStopReason(stopReason: string | undefined): Turn
89
97
  export declare class AcpSession implements AgentSession {
90
98
  private readonly options;
91
99
  readonly backend: "acp";
100
+ readonly capabilities: import("./types.js").AgentSessionCapabilities;
92
101
  readonly pid: number;
93
102
  private readonly child;
94
103
  private readonly events;
@@ -115,7 +124,7 @@ export declare class AcpSession implements AgentSession {
115
124
  private queueDepth;
116
125
  private exit;
117
126
  private steeringSupported;
118
- private capabilities?;
127
+ private agentCapabilities?;
119
128
  private runtimeModel?;
120
129
  private reasoningEffort?;
121
130
  private controllerCount;
@@ -141,6 +150,15 @@ export declare class AcpSession implements AgentSession {
141
150
  private terminate;
142
151
  /** ACP-authenticated in-flight calls, including independently reserved permissions. */
143
152
  private readonly activeToolCalls;
153
+ private stallWatchdog?;
154
+ private stallToolHistory?;
155
+ private stallRecoveryClaimed;
156
+ private managedTurnCount;
157
+ private steeringWasUsed;
158
+ private steeringRequests;
159
+ private retryNativeTurnId?;
160
+ private stallTimer?;
161
+ private stallAttempt?;
144
162
  private readonly toolBoundaryWaiters;
145
163
  private activeTurn?;
146
164
  private constructor();
@@ -272,7 +290,13 @@ export declare class AcpSession implements AgentSession {
272
290
  private declaredMcpServers;
273
291
  private initialize;
274
292
  private captureRuntimeMetadata;
293
+ private startStallWatchdog;
294
+ private checkStallWatchdog;
295
+ private stallObservation;
296
+ private recoverStall;
297
+ /** Keep the original queue slot (including startup) until recovery finishes. */
275
298
  private runPrompt;
299
+ private runSinglePrompt;
276
300
  private steerPrompt;
277
301
  private requestPermission;
278
302
  /**
@@ -291,6 +315,8 @@ export declare class AcpSession implements AgentSession {
291
315
  * malformed requests on the ordinary fail-closed path.
292
316
  */
293
317
  private isEffectiveCodexProtectedMcpApproval;
318
+ /** Pinned codex-acp 1.1.7 structured metadata, never stderr or assistant text. */
319
+ private recordStallMetadata;
294
320
  private recordUpdate;
295
321
  /**
296
322
  * Codex ACP's phase extension is the only currently supported visibility