@ours.network/fleet 1.1.0-nightly.8 → 1.1.0

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 +231 -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 +9 -2
  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 +579 -173
  13. package/dist/briefing.js +85 -28
  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 +130 -55
  18. package/dist/config.d.ts +38 -3
  19. package/dist/config.js +193 -28
  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 +193 -55
  24. package/dist/doctor.d.ts +2 -0
  25. package/dist/doctor.js +40 -4
  26. package/dist/fleet-command-audit.d.ts +59 -9
  27. package/dist/fleet-command-audit.js +426 -63
  28. package/dist/fleet-proxy.d.ts +5 -2
  29. package/dist/harness/claude-code-session.js +2 -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 +18 -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 +21 -5
  50. package/dist/owner-channel/channel.js +312 -138
  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/owner-channel/state.d.ts +2 -0
  58. package/dist/owner-channel/state.js +8 -2
  59. package/dist/permissions.js +1 -1
  60. package/dist/preset-migration.d.ts +42 -0
  61. package/dist/preset-migration.js +450 -0
  62. package/dist/reports/artifact.d.ts +10 -0
  63. package/dist/reports/artifact.js +32 -0
  64. package/dist/reports/index.d.ts +5 -0
  65. package/dist/reports/index.js +5 -0
  66. package/dist/reports/render.d.ts +6 -0
  67. package/dist/reports/render.js +240 -0
  68. package/dist/reports/service.d.ts +42 -0
  69. package/dist/reports/service.js +57 -0
  70. package/dist/reports/tasks.d.ts +52 -0
  71. package/dist/reports/tasks.js +177 -0
  72. package/dist/reports/types.d.ts +315 -0
  73. package/dist/reports/types.js +6 -0
  74. package/dist/resolved-plan.js +13 -1
  75. package/dist/rooms-tasks/cli.d.ts +2 -0
  76. package/dist/rooms-tasks/cli.js +434 -279
  77. package/dist/rooms-tasks/close.d.ts +9 -1
  78. package/dist/rooms-tasks/close.js +23 -7
  79. package/dist/rooms-tasks/config.js +16 -25
  80. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  81. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  82. package/dist/rooms-tasks/deletion.d.ts +51 -0
  83. package/dist/rooms-tasks/deletion.js +217 -0
  84. package/dist/rooms-tasks/external-worker.d.ts +8 -0
  85. package/dist/rooms-tasks/external-worker.js +38 -0
  86. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  87. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  88. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  89. package/dist/rooms-tasks/member-overrides.js +204 -0
  90. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  91. package/dist/rooms-tasks/member-startup.js +9 -2
  92. package/dist/rooms-tasks/provision.js +146 -56
  93. package/dist/rooms-tasks/room-state.d.ts +1 -0
  94. package/dist/rooms-tasks/room-state.js +12 -1
  95. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  96. package/dist/rooms-tasks/task-room-name.js +61 -0
  97. package/dist/rooms-tasks/task-state.d.ts +115 -3
  98. package/dist/rooms-tasks/task-state.js +361 -11
  99. package/dist/rooms-tasks/templates.d.ts +4 -1
  100. package/dist/rooms-tasks/templates.js +34 -3
  101. package/dist/rooms-tasks/terminal.d.ts +3 -0
  102. package/dist/rooms-tasks/terminal.js +10 -4
  103. package/dist/rooms-tasks/types.d.ts +92 -7
  104. package/dist/rooms-tasks/types.js +14 -1
  105. package/dist/runner.d.ts +3 -0
  106. package/dist/runner.js +92 -33
  107. package/dist/session/acp.d.ts +4 -1
  108. package/dist/session/acp.js +29 -13
  109. package/dist/session/arbiter.d.ts +2 -1
  110. package/dist/session/arbiter.js +2 -0
  111. package/dist/session/codex-app-server-transport.d.ts +53 -0
  112. package/dist/session/codex-app-server-transport.js +184 -0
  113. package/dist/session/codex-app-server.d.ts +122 -0
  114. package/dist/session/codex-app-server.js +1031 -0
  115. package/dist/session/control.d.ts +2 -2
  116. package/dist/session/control.js +18 -2
  117. package/dist/session/conversation-types.d.ts +4 -4
  118. package/dist/session/types.d.ts +24 -6
  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,7 +1,11 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { randomUUID } from 'node:crypto';
3
+ import { Buffer } from 'node:buffer';
3
4
  import { replaceFileAtomically } from './atomic-file.js';
4
5
  import { isSensitiveConfigKey } from './sensitive-config.js';
6
+ import { mandatoryConfigurationFits } from './lifecycle-summary.js';
7
+ import { MARKDOWN_MAX_BYTES, MARKDOWN_MAX_CODE_POINTS, markdownCode, markdownProse, } from './rooms-tasks/markdown.js';
8
+ const SESSION_BACKENDS = new Set(['acp', 'codex-app-server']);
5
9
  export class FleetCliExit extends Error {
6
10
  exitCode;
7
11
  outcomeClass;
@@ -13,15 +17,91 @@ export class FleetCliExit extends Error {
13
17
  this.effect = effect;
14
18
  }
15
19
  }
20
+ /** Commands are Owner-silent unless they record one of these semantic lifecycle kinds. */
21
+ export const FLEET_LIFECYCLE_EVENT_KINDS = Object.freeze([
22
+ 'agent_started', 'task', 'room', 'lifecycle_failure',
23
+ ]);
24
+ /** Stable semantic dedupe basis: resource kind + stable resource ID + transition/action ID. */
25
+ export function lifecycleEventDigestBasis(value) {
26
+ const resourceKind = value.kind === 'lifecycle_failure' ? value.resource : value.kind;
27
+ return `${resourceKind}\0${value.id}\0${value.eventId}`;
28
+ }
16
29
  let collection;
17
- export function beginFleetAuditCollection() { collection = { resourceIds: {} }; }
30
+ export function beginFleetAuditCollection() { collection = { resourceIds: {}, presentations: [] }; }
31
+ let lifecycleCheckpoint;
32
+ export function setFleetAuditLifecycleCheckpoint(checkpoint) { lifecycleCheckpoint = checkpoint; }
33
+ /**
34
+ * Deliver already-recorded lifecycle events while a managed command is still
35
+ * running. On a failed checkpoint the events remain in the final audit batch,
36
+ * so a temporary Owner-channel outage loses timing, never the notice itself.
37
+ */
38
+ export async function checkpointFleetAuditPresentations() {
39
+ if (!collection || !lifecycleCheckpoint || !collection.presentations.length)
40
+ return;
41
+ const pending = collection.presentations.splice(0);
42
+ try {
43
+ await lifecycleCheckpoint(structuredClone(pending));
44
+ }
45
+ catch {
46
+ collection.presentations.unshift(...pending);
47
+ }
48
+ }
18
49
  export function recordFleetAuditResource(kind, id) {
19
50
  if (id && collection)
20
51
  collection.resourceIds[kind] = id;
21
52
  }
53
+ const PRESENTATION_LABEL_MAX_CODE_POINTS = 160;
54
+ const PRESENTATION_LABEL_MAX_BYTES = 640;
55
+ const PRESENTATION_LABEL_CONTROLS = /[\u0000-\u001f\u007f-\u009f]/gu;
56
+ const PRESENTATION_LABEL_SPOOFING = /[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/gu;
57
+ /** Immutable, single-line human label captured before mutable Task/Room state can disappear. */
58
+ export function fleetPresentationLabel(value) {
59
+ const normalized = String(value ?? '')
60
+ .replace(/\r\n?/gu, '\n')
61
+ .replace(PRESENTATION_LABEL_SPOOFING, '�')
62
+ .replace(PRESENTATION_LABEL_CONTROLS, ' ')
63
+ .replace(/\s+/gu, ' ')
64
+ .trim();
65
+ if (!normalized)
66
+ return undefined;
67
+ if (Array.from(normalized).length <= PRESENTATION_LABEL_MAX_CODE_POINTS
68
+ && Buffer.byteLength(normalized, 'utf8') <= PRESENTATION_LABEL_MAX_BYTES)
69
+ return normalized;
70
+ let bounded = '';
71
+ for (const point of normalized) {
72
+ const candidate = `${bounded}${point}…`;
73
+ if (Array.from(candidate).length > PRESENTATION_LABEL_MAX_CODE_POINTS
74
+ || Buffer.byteLength(candidate, 'utf8') > PRESENTATION_LABEL_MAX_BYTES)
75
+ break;
76
+ bounded += point;
77
+ }
78
+ return `${bounded.replace(/\s+$/u, '')}…`;
79
+ }
22
80
  export function recordFleetAuditPresentation(value) {
23
- if (collection)
24
- collection.presentation = structuredClone(value);
81
+ if (collection && FLEET_LIFECYCLE_EVENT_KINDS.includes(value.kind)) {
82
+ const basis = value.eventId ?? (value.kind === 'agent_started' ? value.actionId
83
+ : value.kind === 'task' || value.kind === 'room'
84
+ ? `${value.operation}:${value.revision ?? `${value.previousState ?? 'none'}:${value.newState}`}`
85
+ : `${value.category}:${value.state}`);
86
+ const labels = value.kind === 'task'
87
+ ? { title: fleetPresentationLabel(value.title), roomName: fleetPresentationLabel(value.roomName),
88
+ reason: fleetPresentationLabel(value.reason) }
89
+ : value.kind === 'room'
90
+ ? { name: fleetPresentationLabel(value.name) }
91
+ : value.kind === 'lifecycle_failure'
92
+ ? { label: fleetPresentationLabel(value.label) }
93
+ : {};
94
+ const presentation = structuredClone({ ...value, ...labels, eventId: basis });
95
+ const digest = lifecycleEventDigestBasis(presentation);
96
+ if (!collection.presentations.some(existing => lifecycleEventDigestBasis(existing) === digest))
97
+ collection.presentations.push(presentation);
98
+ if (value.kind === 'task')
99
+ collection.resourceIds.task = value.id;
100
+ if (value.kind === 'room')
101
+ collection.resourceIds.room = value.id;
102
+ if (value.kind === 'agent_started')
103
+ collection.resourceIds.agent = value.id;
104
+ }
25
105
  }
26
106
  export function recordFleetAuditFailure(failure) { if (collection)
27
107
  collection.failure = failure; }
@@ -30,7 +110,7 @@ export function consumeFleetAuditCollection() {
30
110
  collection = undefined;
31
111
  return {
32
112
  ...(current && Object.keys(current.resourceIds).length ? { resourceIds: current.resourceIds } : {}),
33
- ...(current?.presentation ? { presentation: current.presentation } : {}),
113
+ ...(current?.presentations.length ? { presentations: current.presentations } : {}),
34
114
  ...(current?.failure ? { failure: current.failure } : {}),
35
115
  };
36
116
  }
@@ -39,16 +119,19 @@ const AGENT_SURFACES = {
39
119
  spawn: new Set(['<none>']),
40
120
  template: new Set(['list', 'show', 'validate']),
41
121
  task: new Set(['create', 'list', 'lists', 'list-create', 'list-rename', 'list-delete', 'move',
42
- 'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', 'recover', 'work', 'finish']),
43
- room: new Set(['create', 'list', 'show', 'open', 'members', 'delete', 'close', 'recover']),
122
+ 'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', 'work', 'finish']),
123
+ room: new Set(['create', 'list', 'show', 'open', 'members', 'delete', 'close']),
44
124
  };
45
125
  export const fleetProxyCommandInventory = Object.freeze(Object.fromEntries(Object.entries(AGENT_SURFACES).map(([surface, commands]) => [surface, [...commands]])));
46
126
  const DENIED = new Set([
47
127
  'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
48
- 'watchdog-run', 'watchdog-report', 'rm', 'init', 'web',
128
+ 'watchdog-run', 'watchdog-report', 'rm', 'init', 'migrate-agent-templates', 'migrate-role-defaults', 'web',
49
129
  ]);
50
130
  export const fleetProxyTopLevelInventory = Object.freeze({
51
- safeRead: [...SAFE_READ], agent: Object.keys(AGENT_SURFACES), denied: [...DENIED],
131
+ safeRead: [...SAFE_READ], agent: Object.keys(AGENT_SURFACES), public: [
132
+ 'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
133
+ 'watchdog-run', 'watchdog-report', 'rm', 'init', 'web',
134
+ ], denied: [...DENIED],
52
135
  hidden: ['_run', '_run-temp', '_run-watchdog', '_run-watchdogs'], aliases: ['man'],
53
136
  });
54
137
  const globalValueOptions = new Set(['-c', '--configuration']);
@@ -92,18 +175,19 @@ export function classifyFleetArgv(argv) {
92
175
  if (sub.startsWith('_'))
93
176
  return { command: `${command} ${sub}`, route: 'supervisor-proxy/internal', decision: 'deny' };
94
177
  if (command !== 'spawn' && !AGENT_SURFACES[command].has(sub))
95
- return { command: `${command} ${sub}`, route: 'supervisor-proxy/unsupported', decision: 'unsupported' };
178
+ return { command: `${command} ${sub}`, route: `supervisor-proxy/${command}`, decision: 'allow' };
96
179
  return { command: sub.startsWith('-') ? command : `${command} ${sub}`,
97
180
  route: `supervisor-proxy/${command}`, decision: 'allow' };
98
181
  }
99
- if (DENIED.has(command))
100
- return { command, route: 'supervisor-proxy/permission-denied', decision: 'deny' };
101
- return { command, route: 'supervisor-proxy/unsupported', decision: 'unsupported' };
182
+ // The proxy is attribution and routing, not a product permission boundary.
183
+ // Commander validates every public command. Hidden process entry points above
184
+ // remain guarded because recursively invoking them breaks the supervisor protocol.
185
+ return { command, route: 'supervisor-proxy/public', decision: 'allow' };
102
186
  }
103
187
  const marker = (value) => value === '' ? '[REDACTED:empty]' : '[REDACTED:value]';
104
188
  const sensitiveValueFlags = new Set([
105
189
  '--identity', '--invite', '--token', '--api-token', '--password', '--password-file',
106
- '--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file',
190
+ '--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file', '--loops-file',
107
191
  '--configuration', '-c', '--public-invite', '--public-invite-file', '--invite-file',
108
192
  '--summary-file', '--text', '--message', '--summary', '--reason', '--goal', '--cwd',
109
193
  '--identity-cid', '--owner-cid', '--contact-cid', '--codex-config', '--add-dir',
@@ -251,7 +335,7 @@ export class FleetCommandAuditStore {
251
335
  const prior = { class: attempt.outcome.class, effect: attempt.outcome.effect,
252
336
  ...(attempt.outcome.exitCode === undefined ? {} : { exitCode: attempt.outcome.exitCode }),
253
337
  ...(attempt.outcome.resourceIds ? { resourceIds: attempt.outcome.resourceIds } : {}),
254
- ...(attempt.outcome.presentation ? { presentation: attempt.outcome.presentation } : {}) };
338
+ ...(attempt.outcome.presentations ? { presentations: attempt.outcome.presentations } : {}) };
255
339
  if (JSON.stringify(prior) !== JSON.stringify(outcome))
256
340
  throw new Error('conflicting fleet command outcome for existing correlation');
257
341
  }
@@ -293,7 +377,7 @@ export function validateFleetAuditBegin(value) {
293
377
  }
294
378
  export function validateFleetAuditFinish(value) {
295
379
  const input = value;
296
- const allowed = ['correlationId', 'class', 'exitCode', 'effect', 'resourceIds', 'presentation'];
380
+ const allowed = ['correlationId', 'class', 'exitCode', 'effect', 'resourceIds', 'presentations'];
297
381
  const resources = input?.resourceIds;
298
382
  if (!input || Object.keys(input).some(key => !allowed.includes(key))
299
383
  || typeof input.correlationId !== 'string' || !UUID.test(input.correlationId)
@@ -302,74 +386,353 @@ export function validateFleetAuditFinish(value) {
302
386
  || (resources !== undefined && (!resources || typeof resources !== 'object' || Array.isArray(resources)
303
387
  || Object.entries(resources).some(([key, id]) => !['agent', 'task', 'room'].includes(key)
304
388
  || typeof id !== 'string' || !SAFE_RESOURCE_ID.test(id))))
305
- || (input.presentation !== undefined && !validPresentation(input.presentation)))
389
+ || (input.presentations !== undefined && (!Array.isArray(input.presentations)
390
+ || input.presentations.length > 128 || !input.presentations.every(validPresentation))))
306
391
  throw new Error('invalid fleet audit finish fields');
307
392
  }
393
+ const APPROVAL_MODES = new Set(['ask', 'auto', 'allow', 'deny']);
394
+ const FILESYSTEM_MODES = new Set(['read-only', 'workspace', 'unrestricted']);
395
+ const UNATTENDED_MODES = new Set(['deny', 'wait']);
396
+ const FLEET_PERMISSION_MODES = new Set(['ask', 'auto', 'allow']);
397
+ const PRESENTATION_TEXT = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u;
398
+ /**
399
+ * Exact-key validation of a current-version launch configuration. A partial
400
+ * v1 configuration is rejected; a legacy presentation simply omits the field.
401
+ */
402
+ function validConfiguration(value) {
403
+ if (!value || typeof value !== 'object' || Array.isArray(value))
404
+ return false;
405
+ const c = value;
406
+ const text = (v, max = 256) => typeof v === 'string' && v.length <= max
407
+ && !PRESENTATION_TEXT.test(v);
408
+ const exact = (record, keys) => Object.keys(record).every(key => keys.includes(key));
409
+ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
410
+ const origin = (v) => {
411
+ if (!record(v))
412
+ return false;
413
+ if (v.kind === 'named')
414
+ return exact(v, ['kind', 'ref']) && text(v.ref, 160);
415
+ if (v.kind === 'inline')
416
+ return exact(v, ['kind', 'fingerprint'])
417
+ && (v.fingerprint === undefined || (typeof v.fingerprint === 'string' && /^[a-f0-9]{12}$/.test(v.fingerprint)));
418
+ return v.kind === 'unknown' && exact(v, ['kind']);
419
+ };
420
+ const count = (v) => v === undefined
421
+ || (Number.isSafeInteger(v) && Number(v) >= 0 && Number(v) <= 4_096);
422
+ return exact(c, ['version', 'template', 'role', 'brain', 'harness', 'session', 'model',
423
+ 'effort', 'mission', 'approval', 'filesystem', 'unattended', 'permissionMode', 'monitor', 'isolation', 'loops'])
424
+ && c.version === 1 && origin(c.role) && origin(c.brain)
425
+ && text(c.harness, 64) && SESSION_BACKENDS.has(c.session)
426
+ && (c.model === null || text(c.model, 160))
427
+ && (c.template === undefined || text(c.template, 160))
428
+ && (c.effort === undefined || text(c.effort, 32))
429
+ // The builder caps the mission label at 80 code points; enforce the same
430
+ // invariant here so the per-line budget proof holds for wire input too.
431
+ && (c.mission === undefined || (text(c.mission) && Array.from(String(c.mission)).length <= 80))
432
+ && APPROVAL_MODES.has(String(c.approval))
433
+ && FILESYSTEM_MODES.has(String(c.filesystem))
434
+ && UNATTENDED_MODES.has(String(c.unattended))
435
+ && record(c.permissionMode)
436
+ && exact(c.permissionMode, ['fleetMode', 'nativeMode'])
437
+ && FLEET_PERMISSION_MODES.has(String(c.permissionMode.fleetMode))
438
+ && text(c.permissionMode.nativeMode, 160)
439
+ && record(c.monitor) && exact(c.monitor, ['mode', 'interrupt'])
440
+ && ['fleet', 'native'].includes(String(c.monitor.mode))
441
+ && (typeof c.monitor.interrupt === 'boolean' || c.monitor.interrupt === 'after_tool')
442
+ && (c.isolation === undefined || (record(c.isolation)
443
+ && exact(c.isolation, ['requested', 'on_unavailable', 'network', 'read_mounts', 'write_mounts'])
444
+ && ['auto', 'bubblewrap', 'podman', 'none'].includes(String(c.isolation.requested))
445
+ && (c.isolation.on_unavailable === undefined || ['warn', 'strict'].includes(String(c.isolation.on_unavailable)))
446
+ && (c.isolation.network === undefined || ['broker', 'deny', 'allow', 'allowlist'].includes(String(c.isolation.network)))
447
+ && count(c.isolation.read_mounts) && count(c.isolation.write_mounts)))
448
+ && (c.loops === undefined || (record(c.loops)
449
+ && exact(c.loops, ['source', 'policy', 'entries'])
450
+ && ['agent-template', 'cli', 'omitted'].includes(String(c.loops.source))
451
+ && c.loops.policy === 'skip-if-busy'
452
+ && Array.isArray(c.loops.entries) && c.loops.entries.length <= 64
453
+ && c.loops.entries.every(entry => record(entry)
454
+ && exact(entry, ['name', 'enabled', 'intervalMs', 'initialDelayMs', 'jitterMs', 'prompt'])
455
+ && text(entry.name, 64) && typeof entry.enabled === 'boolean'
456
+ && [entry.intervalMs, entry.initialDelayMs, entry.jitterMs].every(Number.isSafeInteger)
457
+ && record(entry.prompt) && exact(entry.prompt, ['bytes', 'sha256'])
458
+ && Number.isSafeInteger(entry.prompt.bytes) && Number(entry.prompt.bytes) >= 0
459
+ && typeof entry.prompt.sha256 === 'string' && /^[a-f0-9]{64}$/.test(entry.prompt.sha256))))
460
+ // Escaping and code-fence growth can push a per-field-valid configuration
461
+ // past the line budget; a v1 configuration whose complete mandatory
462
+ // rendering cannot fit is invalid, never silently trimmed.
463
+ && mandatoryConfigurationFits(c);
464
+ }
308
465
  function validPresentation(value) {
309
466
  if (!value || typeof value !== 'object' || Array.isArray(value))
310
467
  return false;
311
468
  const p = value;
469
+ const configured = (record) => record.configuration === undefined || validConfiguration(record.configuration);
312
470
  const safe = (v) => typeof v === 'string' && SAFE_RESOURCE_ID.test(v);
313
471
  const text = (v) => typeof v === 'string' && v.length <= 256
314
472
  && !/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(v);
473
+ const presentationLabel = (v) => typeof v === 'string'
474
+ && fleetPresentationLabel(v) === v;
475
+ const exact = (record, keys) => Object.keys(record).every(key => keys.includes(key));
476
+ const transition = (allowed) => typeof p.operation === 'string' && typeof p.newState === 'string'
477
+ && allowed[p.operation]?.includes(`${String(p.previousState ?? 'none')}→${p.newState}`);
315
478
  if (p.kind === 'agent_started')
316
- return safe(p.name) && ['permanent', 'temporary'].includes(String(p.lifetime))
317
- && text(p.brain) && text(p.role) && safe(p.harness) && p.session === 'acp' && safe(p.parent) && safe(p.actionId)
479
+ return exact(p, ['kind', 'eventId', 'id', 'name', 'lifetime', 'brain', 'role',
480
+ 'harness', 'session', 'model', 'permissions', 'parent', 'actionId', 'inherited', 'configuration'])
481
+ && safe(p.eventId) && safe(p.id) && safe(p.name) && ['permanent', 'temporary'].includes(String(p.lifetime))
482
+ && text(p.brain) && text(p.role) && safe(p.harness)
483
+ && SESSION_BACKENDS.has(p.session) && safe(p.parent) && safe(p.actionId)
318
484
  && (p.model === undefined || text(p.model)) && (p.permissions === undefined || text(p.permissions))
319
- && Array.isArray(p.inherited) && p.inherited.every(text);
485
+ && Array.isArray(p.inherited) && p.inherited.every(text) && configured(p);
320
486
  if (p.kind === 'task')
321
- return safe(p.operation) && safe(p.id) && safe(p.newState)
322
- && (p.title === undefined || text(p.title)) && (p.previousState === undefined || safe(p.previousState))
487
+ return exact(p, ['kind', 'operation', 'eventId', 'id', 'title', 'previousState',
488
+ 'newState', 'template', 'roomId', 'roomName', 'reason', 'revision', 'list', 'agents'])
489
+ && ['create', 'start', 'work', 'block', 'unblock', 'review', 'done',
490
+ 'cancel', 'finish', 'delete', 'settling'].includes(String(p.operation)) && safe(p.eventId) && safe(p.id) && safe(p.newState)
491
+ && transition({ create: ['none→backlog', 'none→provisioning', 'none→active'],
492
+ start: ['backlog→provisioning'], work: ['provisioning→active'],
493
+ block: ['backlog→backlog', 'provisioning→provisioning', 'active→active', 'review→review'],
494
+ unblock: ['backlog→backlog', 'provisioning→provisioning', 'active→active', 'review→review'],
495
+ review: ['active→review'], done: ['review→done'], finish: ['active→done', 'review→done'],
496
+ cancel: ['backlog→cancelled', 'provisioning→cancelled', 'active→cancelled', 'review→cancelled'],
497
+ delete: [
498
+ 'backlog→deleting', 'provisioning→deleting', 'active→deleting', 'review→deleting',
499
+ 'done→deleting', 'cancelled→deleting', 'failed→deleting',
500
+ 'backlog→deleted', 'provisioning→deleted', 'active→deleted', 'review→deleted',
501
+ 'done→deleted', 'cancelled→deleted', 'failed→deleted',
502
+ ], settling: ['active→active', 'review→review', 'backlog→backlog',
503
+ 'provisioning→provisioning'] })
504
+ && (p.title === undefined || presentationLabel(p.title))
505
+ && (p.roomName === undefined || presentationLabel(p.roomName))
506
+ && (p.reason === undefined || presentationLabel(p.reason))
507
+ && (p.previousState === undefined || safe(p.previousState))
323
508
  && (p.template === undefined || text(p.template)) && (p.roomId === undefined || safe(p.roomId))
509
+ && (p.list === undefined || text(p.list)) && (p.revision === undefined || text(p.revision))
324
510
  && Array.isArray(p.agents) && p.agents.length <= 64
325
- && p.agents.every(a => a && typeof a === 'object' && safe(a.name)
511
+ && p.agents.every(a => a && typeof a === 'object'
512
+ && exact(a, ['name', 'brain', 'role', 'permissions', 'configuration'])
513
+ && safe(a.name)
326
514
  && text(a.role)
327
- && (a.brain === undefined || text(a.brain)));
515
+ && (a.brain === undefined || text(a.brain))
516
+ && (a.permissions === undefined || text(a.permissions))
517
+ && configured(a));
328
518
  if (p.kind === 'room')
329
- return safe(p.operation) && safe(p.id) && safe(p.newState)
519
+ return exact(p, ['kind', 'operation', 'eventId', 'id', 'previousState', 'newState',
520
+ 'revision', 'name', 'template', 'taskId', 'anonymous', 'memberCount', 'ownerAttached', 'participants'])
521
+ && ['create', 'activate', 'close', 'delete'].includes(String(p.operation))
522
+ && safe(p.eventId) && safe(p.id) && safe(p.newState)
523
+ && transition({ create: ['none→provisioning'], activate: ['provisioning→active'],
524
+ close: ['active→closing', 'provisioning→closing'],
525
+ delete: ['closing→deleted'] })
330
526
  && (p.previousState === undefined || safe(p.previousState))
331
- && (p.template === undefined || text(p.template))
527
+ && (p.name === undefined || presentationLabel(p.name)) && (p.template === undefined || text(p.template))
528
+ && (p.taskId === undefined || safe(p.taskId)) && (p.revision === undefined || text(p.revision))
529
+ && (p.anonymous === undefined || typeof p.anonymous === 'boolean')
530
+ && (p.ownerAttached === undefined || typeof p.ownerAttached === 'boolean')
531
+ && (p.memberCount === undefined || (Number.isSafeInteger(p.memberCount)
532
+ && Number(p.memberCount) >= 0 && Number(p.memberCount) <= 64))
332
533
  && Array.isArray(p.participants) && p.participants.length <= 64
333
- && p.participants.every(a => a && typeof a === 'object' && safe(a.name)
334
- && text(a.role));
534
+ && p.participants.every(a => a && typeof a === 'object'
535
+ && exact(a, ['name', 'id', 'brain', 'role', 'permissions', 'configuration'])
536
+ && safe(a.name)
537
+ && text(a.role)
538
+ && (a.id === undefined || safe(a.id))
539
+ && (a.brain === undefined || text(a.brain))
540
+ && (a.permissions === undefined || text(a.permissions))
541
+ && configured(a));
542
+ if (p.kind === 'lifecycle_failure')
543
+ return exact(p, ['kind', 'eventId', 'resource', 'id', 'label', 'state', 'category'])
544
+ && ['Agent', 'Task', 'Room'].includes(String(p.resource))
545
+ && safe(p.eventId) && safe(p.id) && safe(p.state)
546
+ && (p.label === undefined || presentationLabel(p.label))
547
+ && ['provision_failed', 'provision_pending', 'readiness_failed', 'settlement_failed',
548
+ 'settlement_pending', 'cleanup_failed', 'cleanup_pending'].includes(String(p.category));
335
549
  return false;
336
550
  }
337
- export function renderFleetAuditInvocation(attempt) {
338
- return ['🧾 Fleet command invoked', `Correlation: ${attempt.correlationId}`, `Agent: ${attempt.caller}`,
339
- `Route: ${attempt.classification.route}`, `Fleet action: ${attempt.classification.command}`,
340
- `Decision: ${attempt.classification.decision}`, `Invoked: ${attempt.invokedAt}`,
341
- `Raw argv (redacted): ${JSON.stringify(attempt.argv)}`].join('\n');
551
+ export function validateFleetAuditPresentations(value) {
552
+ if (!Array.isArray(value) || value.length < 1 || value.length > 128
553
+ || !value.every(validPresentation))
554
+ throw new Error('invalid Fleet lifecycle presentations');
555
+ }
556
+ const messageCodePoints = (value) => Array.from(value).length;
557
+ const messageBytes = (value) => Buffer.byteLength(value, 'utf8');
558
+ const withinMessageBounds = (value) => messageCodePoints(value) <= MARKDOWN_MAX_CODE_POINTS && messageBytes(value) <= MARKDOWN_MAX_BYTES;
559
+ function titleCase(value) {
560
+ return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
561
+ }
562
+ function commaInteger(value) {
563
+ return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
564
+ }
565
+ function renderStructuredMessage(headline, fields, action) {
566
+ return `${headline}`
567
+ + fields.map(([label, value]) => `\n- **${label}:** ${value}`).join('')
568
+ + (action ? `\n\n${action}` : '');
569
+ }
570
+ function structuredMessage(headline, fields, action) {
571
+ const visible = fields.filter((field) => field[1] !== undefined);
572
+ const render = (selected) => renderStructuredMessage(headline, selected, action);
573
+ const complete = render(visible);
574
+ if (withinMessageBounds(complete))
575
+ return complete;
576
+ const marker = ['Details omitted', 'Additional lifecycle details'];
577
+ const selected = [];
578
+ for (const field of visible) {
579
+ if (!withinMessageBounds(render([...selected, field, marker])))
580
+ break;
581
+ selected.push(field);
582
+ }
583
+ const bounded = render([...selected, marker]);
584
+ if (!withinMessageBounds(bounded))
585
+ throw new Error('mandatory lifecycle presentation exceeds message bounds');
586
+ return bounded;
587
+ }
588
+ function originFields(label, origin) {
589
+ if (origin.kind === 'named')
590
+ return [[label, `Preset ${markdownCode(origin.ref)}`]];
591
+ if (origin.kind === 'inline')
592
+ return [
593
+ [label, 'Inline definition'],
594
+ ...(origin.fingerprint ? [[`${label} fingerprint`, markdownCode(origin.fingerprint)]] : []),
595
+ ];
596
+ return [[label, 'Unknown']];
342
597
  }
343
- export function renderFleetAuditOutcome(attempt) {
344
- if (!attempt.outcome)
345
- throw new Error('fleet command audit outcome is absent');
346
- return ['🧾 Fleet command outcome', `Correlation: ${attempt.correlationId}`, `Agent: ${attempt.caller}`,
347
- `Route: ${attempt.classification.route}`, `Fleet action: ${attempt.classification.command}`,
348
- `Result: ${attempt.outcome.class}`, `Effect: ${attempt.outcome.effect}`,
349
- ...(attempt.outcome.exitCode === undefined ? [] : [`Exit: ${attempt.outcome.exitCode}`]),
350
- ...Object.entries(attempt.outcome.resourceIds ?? {}).sort(([a], [b]) => a.localeCompare(b))
351
- .map(([kind, id]) => `${kind[0]?.toUpperCase()}${kind.slice(1)} ID: ${id}`),
352
- `Completed: ${attempt.outcome.completedAt}`, `Raw argv (redacted): ${JSON.stringify(attempt.argv)}`,
353
- ...renderPresentation(attempt.outcome.presentation)].join('\n');
598
+ function interruptSummary(value) {
599
+ return value === false ? 'off' : value === true ? 'on' : markdownProse(value);
354
600
  }
355
- function renderPresentation(value) {
356
- if (!value)
357
- return [];
358
- if (value.kind === 'agent_started')
359
- return ['Structured result: Agent started', `Agent: ${value.name}`,
360
- `Brain: ${value.brain}`, `Role: ${value.role}`,
361
- `Runtime: ${value.harness}/${value.session}${value.model ? ` model=${value.model}` : ''}`,
362
- ...(value.permissions ? [`Permissions: ${value.permissions}`] : []), `Parent: ${value.parent}`,
363
- `Agent action ID: ${value.actionId}`,
364
- `Inheritance: ${value.inherited.length ? value.inherited.join(', ') : 'none'}`];
365
- if (value.kind === 'task')
366
- return ['Structured result: Task', `Operation: ${value.operation}`,
367
- `Task: ${value.title ? `${value.title} (` : ''}${value.id}${value.title ? ')' : ''}`,
368
- `Status: ${value.previousState ?? 'unresolved'} -> ${value.newState}`,
369
- ...(value.template ? [`Template: ${value.template}`] : []), ...(value.roomId ? [`Room: ${value.roomId}`] : []),
370
- `Responsible Agents: ${value.agents.length ? value.agents.map(a => `${a.name} [Brain ${a.brain ?? 'unresolved'}; Role ${a.role}]`).join('; ') : 'none'}`];
371
- return ['Structured result: Room', `Operation: ${value.operation}`, `Room: ${value.id}`,
372
- `Status: ${value.previousState ?? 'unresolved'} -> ${value.newState}`,
373
- ...(value.template ? [`Template: ${value.template}`] : []),
374
- `Participants: ${value.participants.length ? value.participants.map(a => `${a.name} [Role ${a.role}]`).join('; ') : 'none'}`];
601
+ function agentConfigurationFieldGroups(configuration) {
602
+ const optional = [
603
+ [['Monitor mode', markdownProse(configuration.monitor.mode)],
604
+ ['Interrupt', interruptSummary(configuration.monitor.interrupt)]],
605
+ ];
606
+ if (configuration.isolation)
607
+ optional.push([
608
+ ['Isolation', markdownProse(configuration.isolation.requested)],
609
+ ['Network', configuration.isolation.network ? markdownProse(configuration.isolation.network) : undefined],
610
+ ['If unavailable', configuration.isolation.on_unavailable
611
+ ? configuration.isolation.on_unavailable === 'strict' ? 'Fail' : 'Warn' : undefined],
612
+ ['Read-only mounts', String(configuration.isolation.read_mounts ?? 0)],
613
+ ['Read-write mounts', String(configuration.isolation.write_mounts ?? 0)],
614
+ ]);
615
+ for (const entry of configuration.loops?.entries ?? [])
616
+ optional.push([
617
+ ['Loop', markdownCode(entry.name)],
618
+ ['Loop status', entry.enabled ? 'Enabled' : 'Disabled'],
619
+ ['Loop interval', `${commaInteger(entry.intervalMs)} ms`],
620
+ ['Loop delay', `${commaInteger(entry.initialDelayMs)} ms`],
621
+ ['Loop jitter', `${commaInteger(entry.jitterMs)} ms`],
622
+ ['Loop prompt size', `${entry.prompt.bytes} B`],
623
+ ['Loop prompt hash', markdownCode(entry.prompt.sha256.slice(0, 12))],
624
+ ]);
625
+ return { mandatory: [
626
+ ['Template', configuration.template ? markdownCode(configuration.template) : undefined],
627
+ ...originFields('Role', configuration.role),
628
+ ['Mission', configuration.mission ? `“${markdownProse(configuration.mission)}”` : undefined],
629
+ ...originFields('Brain', configuration.brain),
630
+ ['Harness', markdownCode(configuration.harness)],
631
+ ['Model', configuration.model === null ? 'Harness default' : markdownCode(configuration.model)],
632
+ ['Effort', configuration.effort ? markdownProse(configuration.effort) : undefined],
633
+ ['Approval', markdownProse(configuration.approval)],
634
+ ['Filesystem', markdownProse(configuration.filesystem)],
635
+ ['Wait', markdownProse(configuration.unattended)],
636
+ ['Fleet mode', markdownProse(configuration.permissionMode.fleetMode)],
637
+ ['Native mode', markdownProse(configuration.permissionMode.nativeMode)],
638
+ ], optional, tail: [
639
+ ['Loop policy', configuration.loops ? 'Skip if busy' : undefined],
640
+ ['Loop source', configuration.loops
641
+ ? markdownProse(configuration.loops.source.replace(/-/g, ' ')) : undefined],
642
+ ] };
643
+ }
644
+ function boundedAgentMessage(headline, prefix, optionalGroups, suffix, action) {
645
+ const visible = (fields) => fields.filter((field) => field[1] !== undefined);
646
+ const requiredPrefix = visible(prefix);
647
+ const requiredSuffix = visible(suffix);
648
+ const groups = optionalGroups.map(visible).filter(group => group.length > 0);
649
+ const marker = ['Details omitted', 'Additional optional Agent configuration'];
650
+ const render = (optional, omitted) => renderStructuredMessage(headline, [...requiredPrefix, ...optional, ...(omitted ? [marker] : []), ...requiredSuffix], action);
651
+ const all = groups.flat();
652
+ const complete = render(all, false);
653
+ if (withinMessageBounds(complete))
654
+ return complete;
655
+ const included = [];
656
+ for (const group of groups) {
657
+ const candidate = render([...included, ...group], true);
658
+ if (!withinMessageBounds(candidate))
659
+ break;
660
+ included.push(...group);
661
+ }
662
+ const bounded = render(included, true);
663
+ if (!withinMessageBounds(bounded))
664
+ throw new Error('mandatory Agent presentation exceeds message bounds');
665
+ return bounded;
666
+ }
667
+ /** Compact Owner presentation. It intentionally has no command, argv, environment, or correlation data. */
668
+ export function renderFleetLifecycleEvent(value) {
669
+ if (value.kind === 'lifecycle_failure') {
670
+ const label = markdownProse(value.label ?? value.id);
671
+ if (value.category === 'provision_failed')
672
+ return structuredMessage(`⚠️ Couldn’t prepare ${value.resource} “${label}”`, [['Status', 'Provisioning failed'], ['Resource', value.resource], [`${value.resource} ID`, markdownCode(value.id)]], `**Next:** Check the Fleet service logs, correct the configuration, then run the same ${value.resource === 'Room' ? 'create' : 'start'} command again.`);
673
+ if (value.category === 'provision_pending')
674
+ return structuredMessage(`⏳ ${value.resource} “${label}” is still getting ready`, [['Status', 'Waiting for Agents'], ['Resource', value.resource], [`${value.resource} ID`, markdownCode(value.id)]], value.resource === 'Task'
675
+ ? `**Next:** Check member readiness, then run ${markdownCode(`task start ${value.id}`)}.`
676
+ : '**Next:** Check member readiness, then repeat the same Room command.');
677
+ if (value.category === 'readiness_failed')
678
+ return structuredMessage(`⚠️ Agent didn’t become ready: ${label}`, [['Status', 'Readiness failed'], ['Agent ID', markdownCode(value.id)]], '**Next:** Check the Agent log, correct its configuration, then create it again.');
679
+ if (value.resource === 'Task')
680
+ return structuredMessage(`⚠️ Task cleanup is incomplete: ${label}`, [['Status', 'Finish pending'], ['Task ID', markdownCode(value.id)]], '**Next:** Check the Fleet service logs, then repeat the same `done`, `finish`, or `cancel` command.');
681
+ return structuredMessage(`⚠️ Room cleanup is incomplete: ${label}`, [['Status', 'Deletion pending'], ['Room ID', markdownCode(value.id)]], '**Next:** Check the Fleet service logs, then repeat the same confirmed Room delete command.');
682
+ }
683
+ if (value.kind === 'agent_started') {
684
+ const prefix = [
685
+ ['Started by', markdownProse(value.parent)],
686
+ ['Lifetime', titleCase(value.lifetime)],
687
+ ];
688
+ if (value.configuration) {
689
+ const configuration = agentConfigurationFieldGroups(value.configuration);
690
+ return boundedAgentMessage(`🚀 Agent launched: ${markdownProse(value.name)}`, [...prefix, ...configuration.mandatory], configuration.optional, [...configuration.tail, ['Agent ID', markdownCode(value.id)]], 'Launch was accepted; harness and room-seat readiness converge separately.');
691
+ }
692
+ return structuredMessage(`🚀 Agent launched: ${markdownProse(value.name)}`, [...prefix,
693
+ ['Resolved details', 'Unavailable'],
694
+ ['Role', markdownProse(value.role)],
695
+ ['Brain', markdownProse(value.brain)],
696
+ ...(value.permissions ? [['Permissions', markdownProse(value.permissions)]] : []),
697
+ ['Agent ID', markdownCode(value.id)]], 'Launch was accepted; harness and room-seat readiness converge separately.');
698
+ }
699
+ if (value.kind === 'task') {
700
+ const title = markdownProse(value.title ?? value.id);
701
+ const id = markdownCode(value.id);
702
+ const list = value.list ? markdownProse(value.list) : undefined;
703
+ if (value.operation === 'create' && value.newState === 'backlog')
704
+ return structuredMessage('✅ Task added to Backlog', [['Task', `“${title}”`], ['Task ID', id], ['List', list],
705
+ ['Template', value.template ? markdownCode(value.template) : undefined]]);
706
+ if (value.operation === 'create' || value.operation === 'start' || value.operation === 'work')
707
+ return structuredMessage(`🚀 Task started: ${title}`, [['Status', 'Active'], ['List', list], ['Room', value.roomId
708
+ ? markdownProse(value.roomName ?? value.roomId) : undefined],
709
+ ['Room ID', value.roomId ? markdownCode(value.roomId) : undefined],
710
+ ['Team', `${value.agents.length} Agent${value.agents.length === 1 ? '' : 's'} ready`], ['Task ID', id]], 'The task room and its Agents are ready to work.');
711
+ if (value.operation === 'block')
712
+ return structuredMessage(`⛔ Task blocked: ${title}`, [['Status', titleCase(value.newState)], ['Blocked', 'Yes'],
713
+ ['Reason', value.reason ? markdownProse(value.reason) : undefined], ['Task ID', id]]);
714
+ if (value.operation === 'unblock')
715
+ return structuredMessage(`✅ Task unblocked: ${title}`, [['Status', titleCase(value.newState)], ['Task ID', id]]);
716
+ if (value.operation === 'review')
717
+ return structuredMessage(`🔎 Task ready for review: ${title}`, [['List', list], ['Task ID', id]]);
718
+ if (value.operation === 'done' || value.operation === 'finish')
719
+ return structuredMessage(`🎉 Task completed: ${title}`, [['Status', 'Done'], ['List', list], ['Task ID', id]], 'The task room and temporary Agents have been cleaned up.');
720
+ if (value.operation === 'cancel')
721
+ return structuredMessage(`🚫 Task cancelled: ${title}`, [['Status', 'Cancelled'], ['List', list], ['Task ID', id]], 'The task room and temporary Agents have been cleaned up.');
722
+ if (value.operation === 'delete' && value.newState === 'deleting')
723
+ return structuredMessage(`🗑️ Task deletion started: ${title}`, [['Current status', 'Deleting'], ['Task ID', id]], 'Fleet is removing the linked room and temporary Agents in the background.');
724
+ if (value.operation === 'delete')
725
+ return structuredMessage(`🗑️ Task deleted: ${title}`, [['Task ID', id]], 'Temporary Agents were cleaned up.');
726
+ return structuredMessage(`⚠️ Task cleanup is incomplete: ${title}`, [['Status', 'Finish pending'], ['Task ID', id]], '**Next:** Check the Fleet service logs, then repeat the same `done`, `finish`, or `cancel` command.');
727
+ }
728
+ const name = markdownProse(value.name ?? value.id);
729
+ if (value.operation === 'activate')
730
+ return structuredMessage(`🏠 Room ready: ${name}`, [['Status', 'Active'], ['Template', value.template ? markdownCode(value.template) : undefined],
731
+ ['Team', `${value.memberCount ?? value.participants.length} Agent${(value.memberCount ?? value.participants.length) === 1 ? '' : 's'} ready`],
732
+ ['Room ID', markdownCode(value.id)]]);
733
+ if (value.operation === 'delete')
734
+ return structuredMessage(`🗑️ Room deleted: ${name}`, [['Room ID', markdownCode(value.id)]], 'Temporary Agents were cleaned up.');
735
+ if (value.operation === 'close')
736
+ return structuredMessage(`🗑️ Room deletion started: ${name}`, [['Current status', 'Deleting'], ['Room ID', markdownCode(value.id)]]);
737
+ return structuredMessage(`⏳ Room “${name}” is still getting ready`, [['Status', 'Waiting for Agents'], ['Resource', 'Room'], ['Room ID', markdownCode(value.id)]], '**Next:** Check member readiness, then repeat the same Room command.');
375
738
  }
@@ -1,4 +1,5 @@
1
- import type { MonitorConfig, ResolvedRole } from './config.js';
1
+ import type { MonitorConfig, ResolvedRole, SessionBackendId } from './config.js';
2
+ import type { AgentLaunchConfiguration } from './lifecycle-summary.js';
2
3
  import type { SpawnOpts } from './spawn.js';
3
4
  /** Present only inside a managed role process. The CLI treats it as a routing hint, not authority. */
4
5
  export declare const FLEET_PROXY_STATE_DIR_ENV = "OURS_FLEET_PROXY_STATE_DIR";
@@ -9,7 +10,7 @@ export interface ManagedFleetSpawnResult {
9
10
  lifetime: 'permanent' | 'temporary';
10
11
  statePath: string;
11
12
  harness: string;
12
- session: 'acp';
13
+ session: SessionBackendId;
13
14
  model?: string;
14
15
  monitor: Pick<MonitorConfig, 'mode' | 'interrupt'>;
15
16
  /** Adapter-resolved portable policy and exact native runtime mode. */
@@ -21,6 +22,8 @@ export interface ManagedFleetSpawnResult {
21
22
  creationActionId: string;
22
23
  brainSummary: string;
23
24
  roleSummary: string;
25
+ /** Launch configuration captured from the exact resolved role; absent only from pre-upgrade daemons. */
26
+ configuration?: AgentLaunchConfiguration;
24
27
  }
25
28
  /**
26
29
  * Fill only omitted spawn settings from the live caller. Explicit agent choices
@@ -21,7 +21,8 @@ export class ClaudeCodeAgentSessionAdapter {
21
21
  start(options) {
22
22
  const { role, prep, launch } = options;
23
23
  return this.transport({
24
- name: role.name, argv: launch.argv, cwd: options.cwd, env: launch.env,
24
+ name: role.name, harness: role.harness,
25
+ argv: launch.argv, cwd: options.cwd, env: launch.env,
25
26
  stateDir: options.stateDir, mode: options.mode, permissions: options.permissions,
26
27
  modeId: this.strategy.permissionModeId(role),
27
28
  mcpServers: this.strategy.mcpServers(role),