@ours.network/fleet 1.2.0-nightly.2 → 1.2.0-nightly.21

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 (277) hide show
  1. package/README.md +152 -29
  2. package/dist/agent-ours/bridge.d.ts +3 -0
  3. package/dist/agent-ours/bridge.js +178 -0
  4. package/dist/agent-ours/controller.d.ts +14 -0
  5. package/dist/agent-ours/controller.js +60 -0
  6. package/dist/agent-ours/correspondence.d.ts +10 -0
  7. package/dist/agent-ours/correspondence.js +1 -0
  8. package/dist/agent-ours/file-rpc.d.ts +23 -0
  9. package/dist/agent-ours/file-rpc.js +123 -0
  10. package/dist/agent-ours/harness.d.ts +14 -0
  11. package/dist/agent-ours/harness.js +72 -0
  12. package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
  13. package/dist/agent-ours/mcp-endpoint.js +111 -0
  14. package/dist/agent-ours/runtime.d.ts +59 -0
  15. package/dist/agent-ours/runtime.js +283 -0
  16. package/dist/agent-ours/service.d.ts +23 -0
  17. package/dist/agent-ours/service.js +372 -0
  18. package/dist/agent-ours/state.d.ts +45 -0
  19. package/dist/agent-ours/state.js +95 -0
  20. package/dist/agent-ours/wire.d.ts +14 -0
  21. package/dist/agent-ours/wire.js +55 -0
  22. package/dist/agent-recovery-gate.js +1 -1
  23. package/dist/application/conversation-history.d.ts +6 -0
  24. package/dist/application/conversation-history.js +65 -0
  25. package/dist/application/fleet-query-service.d.ts +1 -1
  26. package/dist/application/fleet-query-service.js +2 -2
  27. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  28. package/dist/application/legacy-supervisor-identity.js +43 -0
  29. package/dist/application/model-catalog.d.ts +1 -1
  30. package/dist/application/model-catalog.js +5 -2
  31. package/dist/application/preset-provenance.d.ts +19 -0
  32. package/dist/application/preset-provenance.js +59 -0
  33. package/dist/application/room-layout-definitions.d.ts +45 -0
  34. package/dist/application/room-layout-definitions.js +130 -0
  35. package/dist/application/session-control.d.ts +8 -0
  36. package/dist/application/session-control.js +21 -3
  37. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  38. package/dist/application/supervisor-ours-tools.js +113 -0
  39. package/dist/application/task-layouts.d.ts +89 -0
  40. package/dist/application/task-layouts.js +248 -0
  41. package/dist/application/task-room-service.d.ts +99 -3
  42. package/dist/application/task-room-service.js +296 -31
  43. package/dist/atomic-file.d.ts +5 -0
  44. package/dist/atomic-file.js +62 -1
  45. package/dist/briefing.js +10 -59
  46. package/dist/build-info.json +5 -4
  47. package/dist/capabilities.d.ts +2 -1
  48. package/dist/capabilities.js +2 -0
  49. package/dist/cli.js +65 -2
  50. package/dist/client-profile.d.ts +5 -16
  51. package/dist/client-profile.js +4 -112
  52. package/dist/config.d.ts +4 -0
  53. package/dist/config.js +7 -0
  54. package/dist/creation.d.ts +1 -1
  55. package/dist/creation.js +5 -16
  56. package/dist/daemon-recovery.d.ts +1 -8
  57. package/dist/daemon-recovery.js +8 -113
  58. package/dist/docs.d.ts +3 -3
  59. package/dist/docs.js +43 -23
  60. package/dist/doctor.d.ts +1 -1
  61. package/dist/doctor.js +46 -59
  62. package/dist/erased-resources.d.ts +9 -0
  63. package/dist/erased-resources.js +87 -0
  64. package/dist/fleet-command-audit.d.ts +1 -0
  65. package/dist/fleet-command-audit.js +12 -7
  66. package/dist/harness/agent-session.d.ts +2 -0
  67. package/dist/harness/claude-code-session.js +4 -2
  68. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  69. package/dist/harness/codex-app-server-proxy.js +41 -5
  70. package/dist/harness/codex-session.js +9 -2
  71. package/dist/harness/codex.d.ts +3 -2
  72. package/dist/harness/codex.js +4 -3
  73. package/dist/harness/hermes-session.js +7 -1
  74. package/dist/init-wizard.js +20 -9
  75. package/dist/monitor.d.ts +1 -34
  76. package/dist/monitor.js +24 -151
  77. package/dist/owner-channel/channel.d.ts +3 -0
  78. package/dist/owner-channel/channel.js +34 -6
  79. package/dist/owner-channel/commands.d.ts +4 -0
  80. package/dist/owner-channel/commands.js +8 -0
  81. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  82. package/dist/owner-channel/ours-client.js +2 -17
  83. package/dist/preset-migration.js +15 -0
  84. package/dist/rooms-tasks/add-member.d.ts +48 -0
  85. package/dist/rooms-tasks/add-member.js +83 -0
  86. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  87. package/dist/rooms-tasks/archived-absence.js +48 -0
  88. package/dist/rooms-tasks/cli.js +34 -5
  89. package/dist/rooms-tasks/close.d.ts +5 -2
  90. package/dist/rooms-tasks/close.js +208 -28
  91. package/dist/rooms-tasks/config.js +3 -1
  92. package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
  93. package/dist/rooms-tasks/cowork-adapter.js +11 -117
  94. package/dist/rooms-tasks/cowork-http.d.ts +3 -0
  95. package/dist/rooms-tasks/cowork-http.js +70 -0
  96. package/dist/rooms-tasks/deletion.d.ts +1 -1
  97. package/dist/rooms-tasks/deletion.js +74 -5
  98. package/dist/rooms-tasks/erasure.d.ts +6 -0
  99. package/dist/rooms-tasks/erasure.js +169 -0
  100. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  101. package/dist/rooms-tasks/external-worker.js +3 -4
  102. package/dist/rooms-tasks/layout-cli.d.ts +2 -0
  103. package/dist/rooms-tasks/layout-cli.js +81 -0
  104. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  105. package/dist/rooms-tasks/layout-config.js +89 -0
  106. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  107. package/dist/rooms-tasks/layout-control.js +52 -0
  108. package/dist/rooms-tasks/layout-service.d.ts +39 -0
  109. package/dist/rooms-tasks/layout-service.js +173 -0
  110. package/dist/rooms-tasks/layout.d.ts +102 -0
  111. package/dist/rooms-tasks/layout.js +227 -0
  112. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  113. package/dist/rooms-tasks/live-readiness.js +85 -0
  114. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  115. package/dist/rooms-tasks/member-startup.js +2 -0
  116. package/dist/rooms-tasks/provision.d.ts +14 -0
  117. package/dist/rooms-tasks/provision.js +182 -48
  118. package/dist/rooms-tasks/room-state.d.ts +4 -2
  119. package/dist/rooms-tasks/room-state.js +53 -8
  120. package/dist/rooms-tasks/task-state.d.ts +42 -7
  121. package/dist/rooms-tasks/task-state.js +153 -23
  122. package/dist/rooms-tasks/types.d.ts +28 -0
  123. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  124. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  125. package/dist/rooms-tasks/workspace.d.ts +26 -0
  126. package/dist/rooms-tasks/workspace.js +195 -0
  127. package/dist/runner.d.ts +23 -3
  128. package/dist/runner.js +574 -494
  129. package/dist/session/acp.d.ts +3 -0
  130. package/dist/session/acp.js +7 -0
  131. package/dist/session/arbiter.d.ts +3 -0
  132. package/dist/session/arbiter.js +20 -2
  133. package/dist/session/codex-app-server-transport.js +1 -1
  134. package/dist/session/codex-app-server.d.ts +3 -0
  135. package/dist/session/codex-app-server.js +25 -1
  136. package/dist/session/control.d.ts +10 -1
  137. package/dist/session/control.js +36 -2
  138. package/dist/session/conversation-normalizer.d.ts +2 -1
  139. package/dist/session/conversation-normalizer.js +1 -1
  140. package/dist/session/conversation-types.d.ts +5 -3
  141. package/dist/session/task-notice.d.ts +14 -0
  142. package/dist/session/task-notice.js +13 -0
  143. package/dist/session/types.d.ts +3 -0
  144. package/dist/socket-path.d.ts +2 -0
  145. package/dist/socket-path.js +22 -0
  146. package/dist/spawn.js +19 -16
  147. package/dist/startup-readiness.d.ts +13 -0
  148. package/dist/startup-readiness.js +38 -0
  149. package/dist/supervisor/launchd.js +2 -1
  150. package/dist/supervisor/systemd.js +9 -23
  151. package/dist/temp-idle.d.ts +17 -0
  152. package/dist/temp-idle.js +42 -0
  153. package/dist/temp-lifecycle.d.ts +6 -1
  154. package/dist/temp-lifecycle.js +29 -13
  155. package/dist/temp-supervisor-recovery.d.ts +3 -0
  156. package/dist/temp-supervisor-recovery.js +46 -0
  157. package/dist/watchdog/briefing.js +3 -16
  158. package/dist/watchdog/run.js +19 -5
  159. package/dist/web/agent-attachments.d.ts +29 -0
  160. package/dist/web/agent-attachments.js +162 -0
  161. package/dist/web/auth.d.ts +4 -1
  162. package/dist/web/auth.js +23 -2
  163. package/dist/web/control.js +2 -1
  164. package/dist/web/fleet-config-service.d.ts +4 -1
  165. package/dist/web/fleet-config-service.js +46 -15
  166. package/dist/web/prefix-gateway.d.ts +20 -0
  167. package/dist/web/prefix-gateway.js +150 -0
  168. package/dist/web/runtime.d.ts +4 -0
  169. package/dist/web/runtime.js +19 -14
  170. package/dist/web/server.d.ts +7 -0
  171. package/dist/web/server.js +293 -25
  172. package/docs/room-layouts.md +92 -0
  173. package/docs/task-workspaces.md +130 -0
  174. package/examples/room-layouts/fresh.yaml +27 -0
  175. package/examples/room-layouts/shared.yaml +19 -0
  176. package/package.json +17 -13
  177. package/presets/README.md +78 -0
  178. package/presets/brain-catalog.json +8 -2
  179. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  180. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  181. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  182. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  183. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  184. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  185. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  186. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  187. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  188. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  189. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  190. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  191. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  192. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  193. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  194. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  195. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  196. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  197. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  198. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  199. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  200. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  201. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  202. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  203. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  204. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  205. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  206. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  207. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  208. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  209. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  210. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  211. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  212. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  213. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  214. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  215. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  216. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  217. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  218. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  219. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  220. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  221. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  222. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  223. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  224. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  225. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  226. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  227. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  228. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  229. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  230. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  231. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  232. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  233. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  234. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  235. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  236. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  237. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  238. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  239. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  240. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  241. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  242. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  243. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  244. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  245. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  246. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  247. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  248. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  249. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  250. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  251. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  252. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  253. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  254. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  255. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  256. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  257. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  258. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  259. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  260. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  261. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  262. package/presets/fleet/roles/Coordinator.yaml +22 -0
  263. package/presets/fleet/roles/Engineer.yaml +40 -0
  264. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  265. package/presets/fleet/room_layouts/pair.yaml +24 -0
  266. package/presets/fleet/room_layouts/single.yaml +17 -0
  267. package/presets/fleet/room_layouts/team.yaml +30 -0
  268. package/presets/fleet/room_templates/engineering.yaml +19 -0
  269. package/presets/manifest.json +2 -2
  270. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  271. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  272. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  273. package/dist/web-app/icons/ours-fleet.svg +0 -4
  274. package/dist/web-app/index.html +0 -17
  275. package/dist/web-app/manifest.webmanifest +0 -15
  276. package/dist/web-app/offline.html +0 -18
  277. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,65 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ /** Read the existing ACP session ledger without opening its writer/recovery store. */
6
+ export async function resumedConversationPage(stateDir, request, live, expectedSessionId) {
7
+ let sessionId;
8
+ let files;
9
+ try {
10
+ sessionId = (await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim();
11
+ files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort();
12
+ }
13
+ catch {
14
+ return live;
15
+ }
16
+ if (!sessionId || sessionId !== expectedSessionId || !files.length)
17
+ return live;
18
+ const after = Number(request.after ?? 0);
19
+ if (!Number.isSafeInteger(after) || after < 0)
20
+ return live;
21
+ const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
22
+ const events = [];
23
+ let next = String(after);
24
+ let first;
25
+ let hasMore = false;
26
+ scan: for (const file of files) {
27
+ const input = createReadStream(join(stateDir, '.conversation', file), { encoding: 'utf8' });
28
+ const lines = createInterface({ input, crlfDelay: Infinity });
29
+ try {
30
+ for await (const line of lines) {
31
+ let event;
32
+ try {
33
+ event = JSON.parse(line);
34
+ }
35
+ catch {
36
+ continue;
37
+ }
38
+ const cursor = Number(event.seq);
39
+ if (!Number.isSafeInteger(cursor) || cursor <= after)
40
+ continue;
41
+ const historical = ['fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'].includes(event.kind);
42
+ const visible = event.source !== 'agent_replay' && (event.sessionGeneration === live.snapshot.sessionGeneration || (historical && event.acpSessionId === sessionId));
43
+ if (!visible) {
44
+ next = String(cursor);
45
+ continue;
46
+ }
47
+ if (events.length === limit) {
48
+ hasMore = true;
49
+ break scan;
50
+ }
51
+ first ??= String(cursor);
52
+ events.push(event);
53
+ next = String(cursor);
54
+ }
55
+ }
56
+ finally {
57
+ lines.close();
58
+ input.destroy();
59
+ }
60
+ }
61
+ // If a fresh/resumed session changed during the read, use the live page only.
62
+ if ((await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim() !== sessionId)
63
+ return live;
64
+ return { ...live, events, firstAvailableCursor: first, nextCursor: next, hasMore };
65
+ }
@@ -24,7 +24,7 @@ export declare class FleetQueryService {
24
24
  private readonly options;
25
25
  private readonly control;
26
26
  constructor(options: FleetQueryOptions);
27
- list(): Promise<Array<{
27
+ list(includeTemporary?: boolean): Promise<Array<{
28
28
  role: RoleRecord;
29
29
  status: RoleStatus;
30
30
  capabilities: RoleCapabilities;
@@ -75,9 +75,9 @@ export class FleetQueryService {
75
75
  this.options = options;
76
76
  this.control = options.control ?? controlRequest;
77
77
  }
78
- async list() {
78
+ async list(includeTemporary = false) {
79
79
  const roles = (await this.options.repository.list())
80
- .filter(role => role.configured && role.lifetime === 'permanent');
80
+ .filter(role => (role.configured && role.lifetime === 'permanent') || (includeTemporary && role.lifetime === 'temporary'));
81
81
  return Promise.all(roles.map(async (role) => {
82
82
  const status = await this.status(role);
83
83
  return { role, status, capabilities: roleCapabilities(role, status, this.options.capabilityContext) };
@@ -0,0 +1,7 @@
1
+ /** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
2
+ export declare function legacySupervisorIdentity(role: string, name: string, temporary: boolean, generation: number): {
3
+ name: string;
4
+ cid: string;
5
+ generation: number;
6
+ proof: string;
7
+ };
@@ -0,0 +1,43 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { stateRoot } from '../paths.js';
5
+ import { binderKey } from '../agent-ours/state.js';
6
+ import { FleetError } from './errors.js';
7
+ /** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
8
+ export function legacySupervisorIdentity(role, name, temporary, generation) {
9
+ const root = join(stateRoot(), 'private-ours');
10
+ try {
11
+ const matches = [];
12
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
13
+ if (!entry.isDirectory() || !/^[a-f0-9]{64}$/.test(entry.name))
14
+ continue;
15
+ const dir = join(root, entry.name);
16
+ const instance = JSON.parse(readFileSync(join(dir, 'instance.json'), 'utf8'));
17
+ if (instance.role !== role)
18
+ continue;
19
+ const state = JSON.parse(readFileSync(join(dir, 'state.json'), 'utf8'));
20
+ const pin = JSON.parse(readFileSync(join(dir, 'identity-pin.json'), 'utf8'));
21
+ if (!Number.isSafeInteger(generation) || generation < 1
22
+ || typeof state.action !== 'string' || !state.action
23
+ || instance.temporary !== temporary || typeof instance.instance !== 'string' || !instance.instance
24
+ || state.version !== 1 || state.instance !== instance.instance
25
+ || state.name !== name || state.lifetime !== (temporary ? 'temporary' : 'permanent')
26
+ || state.generation !== generation || !['READY', 'SERVING'].includes(state.phase)
27
+ || typeof state.daemon !== 'string' || !state.daemon || binderKey(state.daemon, name) !== entry.name
28
+ || !/^[a-f0-9]{64}$/i.test(state.cid ?? '')
29
+ || pin.daemon !== state.daemon || pin.name !== name || pin.cid !== state.cid)
30
+ throw new Error('mismatched legacy ownership proof');
31
+ matches.push({ name, cid: state.cid, generation, proof: createHash('sha256').update(JSON.stringify([
32
+ instance.instance, state.action, state.daemon, name, state.cid, state.lifetime, generation,
33
+ ])).digest('hex') });
34
+ }
35
+ if (matches.length !== 1)
36
+ throw new Error('missing or ambiguous legacy ownership proof');
37
+ return matches[0];
38
+ }
39
+ catch {
40
+ // The private directory also contains ownership material: never echo parse errors.
41
+ throw new FleetError('capability_unavailable', 'legacy supervisor ownership proof is missing, ambiguous, or mismatched');
42
+ }
43
+ }
@@ -14,7 +14,7 @@ export interface HarnessModelCatalog {
14
14
  export declare function codexModelCatalog(path?: string): HarnessModelCatalog;
15
15
  /**
16
16
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
17
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
17
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
18
18
  * not a claim about account entitlement (which Claude validates at launch).
19
19
  */
20
20
  export declare function claudeModelCatalog(): HarnessModelCatalog;
@@ -41,17 +41,20 @@ export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache
41
41
  }
42
42
  /**
43
43
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
44
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
44
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
45
45
  * not a claim about account entitlement (which Claude validates at launch).
46
46
  */
47
47
  export function claudeModelCatalog() {
48
48
  const reasoningEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
49
49
  return {
50
50
  models: [
51
+ { id: 'claude-fable-5-1', label: 'Claude Fable 5.1', reasoningEfforts, source: 'claude-adapter-2.1' },
52
+ { id: 'claude-opus-5-5', label: 'Claude Opus 5.5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
+ { id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoningEfforts: [], source: 'claude-adapter-2.1' },
51
54
  { id: 'claude-fable-5', label: 'Claude Fable 5', reasoningEfforts, source: 'claude-adapter-2.1' },
52
55
  { id: 'claude-opus-5', label: 'Claude Opus 5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
56
  { id: 'claude-sonnet-5', label: 'Claude Sonnet 5', reasoningEfforts, source: 'claude-adapter-2.1' },
54
57
  ],
55
- warnings: ['Claude Code exposes no typed model-list endpoint; choices are exact 2.1 adapter IDs and entitlement is validated at launch.'],
58
+ warnings: ['Claude Code exposes no typed model-list endpoint; choices require a current Claude runtime (verified with 2.1.281); older bundled SDKs need CLAUDE_CODE_EXECUTABLE. Entitlement is validated at launch.'],
56
59
  };
57
60
  }
@@ -0,0 +1,19 @@
1
+ export declare const PROVENANCE_KINDS: {
2
+ readonly role: "roles";
3
+ readonly brain: "brains";
4
+ readonly template: "agent_templates";
5
+ readonly layout: "room_layouts";
6
+ };
7
+ export type ProvenanceKind = keyof typeof PROVENANCE_KINDS;
8
+ export type ProvenanceStatus = 'predefined' | 'changed' | 'custom';
9
+ export interface ProvenanceEntry {
10
+ status: ProvenanceStatus;
11
+ preset?: string;
12
+ }
13
+ export type Provenance = Record<ProvenanceKind, Record<string, ProvenanceEntry>>;
14
+ export declare class PresetProvenance {
15
+ private configPath?;
16
+ private presetRoot;
17
+ constructor(configPath?: string | undefined, presetRoot?: string);
18
+ read(): Provenance;
19
+ }
@@ -0,0 +1,59 @@
1
+ /** Where each configured definition came from, compared with the packaged presets.
2
+ * A definition is `predefined` when it has the same value as the shipped preset of
3
+ * the same name, `changed` when a preset of that name exists but the value differs,
4
+ * and `custom` otherwise. Values are compared after parsing, so the formatting the
5
+ * configuration editor writes does not count as a change. Preset text is returned
6
+ * so users can see exactly what ships; it contains no host data.
7
+ */
8
+ import { isDeepStrictEqual } from 'node:util';
9
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { basename, extname, join } from 'node:path';
11
+ import { parse } from 'yaml';
12
+ import { defaultConfigPath } from '../paths.js';
13
+ import { splitRootFor } from '../config.js';
14
+ import { packagedPresetRoot } from '../preset-bootstrap.js';
15
+ export const PROVENANCE_KINDS = { role: 'roles', brain: 'brains', template: 'agent_templates', layout: 'room_layouts' };
16
+ const MAX_BYTES = 256 * 1024;
17
+ /** Regular, bounded YAML files of one directory, by definition name. Anything else is skipped. */
18
+ function yamlFiles(dir) {
19
+ const files = new Map();
20
+ if (!existsSync(dir) || !lstatSync(dir).isDirectory())
21
+ return files;
22
+ for (const file of readdirSync(dir).sort()) {
23
+ if (!/\.ya?ml$/.test(file))
24
+ continue;
25
+ const path = join(dir, file), stat = lstatSync(path);
26
+ if (!stat.isFile() || stat.size > MAX_BYTES)
27
+ continue;
28
+ files.set(basename(file, extname(file)), readFileSync(path, 'utf8'));
29
+ }
30
+ return files;
31
+ }
32
+ const parsed = (text) => { try {
33
+ return parse(text);
34
+ }
35
+ catch {
36
+ return Symbol('unparseable');
37
+ } };
38
+ export class PresetProvenance {
39
+ configPath;
40
+ presetRoot;
41
+ constructor(configPath, presetRoot = join(packagedPresetRoot(), 'fleet')) {
42
+ this.configPath = configPath;
43
+ this.presetRoot = presetRoot;
44
+ }
45
+ read() {
46
+ const split = splitRootFor(this.configPath ?? defaultConfigPath());
47
+ const result = {};
48
+ for (const [kind, dir] of Object.entries(PROVENANCE_KINDS)) {
49
+ const presets = yamlFiles(join(this.presetRoot, dir)), installed = yamlFiles(join(split, dir));
50
+ result[kind] = Object.fromEntries([...installed].map(([name, text]) => {
51
+ const preset = presets.get(name);
52
+ if (preset === undefined)
53
+ return [name, { status: 'custom' }];
54
+ return [name, { status: isDeepStrictEqual(parsed(text), parsed(preset)) ? 'predefined' : 'changed', preset }];
55
+ }));
56
+ }
57
+ return result;
58
+ }
59
+ }
@@ -0,0 +1,45 @@
1
+ import { loadConfig } from '../config.js';
2
+ import { type RoomLayoutDefinition } from '../rooms-tasks/layout-config.js';
3
+ export declare const ABSENT_REVISION = "absent";
4
+ export type LayoutDefinitionBody = Omit<RoomLayoutDefinition, 'sourceFile'>;
5
+ export type LayoutListEntry = {
6
+ name: string;
7
+ revision: string;
8
+ file: string;
9
+ definition: LayoutDefinitionBody;
10
+ issues: string[];
11
+ definition_hash: string;
12
+ } | {
13
+ name: string;
14
+ revision: string;
15
+ file: string;
16
+ error: string;
17
+ };
18
+ export declare class RoomLayoutDefinitions {
19
+ private configPath?;
20
+ private deps;
21
+ constructor(configPath?: string | undefined, deps?: {
22
+ loadConfiguration?: typeof loadConfig;
23
+ });
24
+ private root;
25
+ private pathFor;
26
+ private currentRevision;
27
+ /** Agent-template references that do not resolve in the current Fleet configuration. */
28
+ private templateIssues;
29
+ /** The configured default (`rooms.defaults.template`, else `tasks.default_room_template`) when it names a usable layout. */
30
+ defaultLayout(entries?: LayoutListEntry[]): string | undefined;
31
+ list(): LayoutListEntry[];
32
+ validate(raw: unknown): {
33
+ definition?: LayoutDefinitionBody;
34
+ issues: string[];
35
+ };
36
+ save(name: string, expectedRevision: string, raw: unknown): Promise<{
37
+ name: string;
38
+ revision: string;
39
+ definition: LayoutDefinitionBody;
40
+ }>;
41
+ remove(name: string, expectedRevision: string): Promise<{
42
+ name: string;
43
+ deleted: boolean;
44
+ }>;
45
+ }
@@ -0,0 +1,130 @@
1
+ /** Web authoring of `fleet/room_layouts/<name>.yaml`.
2
+ * Writes are revision-guarded (sha256 of the file bytes) and validated with the
3
+ * same parser the CLI uses. Retained layout runs keep their own snapshots, so
4
+ * editing or deleting a definition never changes an existing run.
5
+ */
6
+ import { createHash } from 'node:crypto';
7
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync } from 'node:fs';
8
+ import { basename, extname, join } from 'node:path';
9
+ import { stringify } from 'yaml';
10
+ import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
11
+ import { parseFleetDocument } from '../config-yaml.js';
12
+ import { defaultConfigPath } from '../paths.js';
13
+ import { loadConfig, splitRootFor } from '../config.js';
14
+ import { assertLayoutFile, validLayoutKey, validateRoomLayout } from '../rooms-tasks/layout-config.js';
15
+ import { FleetError } from './errors.js';
16
+ import { layoutDefinitionHash } from './task-layouts.js';
17
+ export const ABSENT_REVISION = 'absent';
18
+ const revisionOf = (bytes) => createHash('sha256').update(bytes).digest('hex');
19
+ export class RoomLayoutDefinitions {
20
+ configPath;
21
+ deps;
22
+ constructor(configPath, deps = {}) {
23
+ this.configPath = configPath;
24
+ this.deps = deps;
25
+ }
26
+ root() { return join(splitRootFor(this.configPath ?? defaultConfigPath()), 'room_layouts'); }
27
+ pathFor(name) {
28
+ if (!validLayoutKey(name))
29
+ throw new FleetError('invalid_request', 'layout name must start with a letter and use letters, digits, - or _');
30
+ return join(this.root(), `${name}.yaml`);
31
+ }
32
+ currentRevision(path) {
33
+ if (!existsSync(path))
34
+ return ABSENT_REVISION;
35
+ assertLayoutFile(path);
36
+ return revisionOf(readFileSync(path));
37
+ }
38
+ /** Agent-template references that do not resolve in the current Fleet configuration. */
39
+ templateIssues(definition) {
40
+ let templates = {};
41
+ try {
42
+ templates = (this.deps.loadConfiguration ?? loadConfig)(this.configPath).agentTemplates ?? {};
43
+ }
44
+ catch (error) {
45
+ return [`fleet configuration unavailable: ${error.message}`];
46
+ }
47
+ return Object.entries(definition.participants).flatMap(([key, participant]) => participant.agent_template && !Object.hasOwn(templates, participant.agent_template)
48
+ ? [`participant ${key}: agent template not found: ${participant.agent_template}`] : []);
49
+ }
50
+ /** The configured default (`rooms.defaults.template`, else `tasks.default_room_template`) when it names a usable layout. */
51
+ defaultLayout(entries = this.list()) {
52
+ let name;
53
+ try {
54
+ const config = (this.deps.loadConfiguration ?? loadConfig)(this.configPath);
55
+ name = config.rooms?.defaults?.template ?? config.tasks?.default_room_template;
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ const entry = entries.find(e => e.name === name);
61
+ return entry && !('error' in entry) && !entry.issues.length ? entry.name : undefined;
62
+ }
63
+ list() {
64
+ const root = this.root();
65
+ if (!existsSync(root))
66
+ return [];
67
+ assertLayoutFile(root, true);
68
+ return readdirSync(root).filter(file => /\.ya?ml$/.test(file)).sort().map(file => {
69
+ const name = basename(file, extname(file)), path = join(root, file);
70
+ try {
71
+ if (!validLayoutKey(name))
72
+ throw Error(`invalid room layout id: ${name}`);
73
+ assertLayoutFile(path);
74
+ const bytes = readFileSync(path);
75
+ const definition = validateRoomLayout(parseFleetDocument(path, bytes.toString('utf8'), 'strict').value, file);
76
+ return { name, file, revision: revisionOf(bytes), definition, issues: this.templateIssues(definition), definition_hash: layoutDefinitionHash(definition) };
77
+ }
78
+ catch (error) {
79
+ // Only a trusted regular file may be read to offer an overwrite revision.
80
+ let revision = ABSENT_REVISION;
81
+ try {
82
+ assertLayoutFile(path);
83
+ revision = revisionOf(readFileSync(path));
84
+ }
85
+ catch { /* not trusted: never read */ }
86
+ return { name, file, revision, error: error.message };
87
+ }
88
+ });
89
+ }
90
+ validate(raw) {
91
+ let definition;
92
+ try {
93
+ definition = validateRoomLayout(raw, 'room layout');
94
+ }
95
+ catch (error) {
96
+ return { issues: [error.message] };
97
+ }
98
+ return { definition, issues: this.templateIssues(definition) };
99
+ }
100
+ async save(name, expectedRevision, raw) {
101
+ const path = this.pathFor(name);
102
+ const { definition, issues } = this.validate(raw);
103
+ if (!definition || issues.length)
104
+ throw new FleetError('invalid_request', issues.join('; '));
105
+ const root = this.root();
106
+ mkdirSync(root, { recursive: true, mode: 0o700 });
107
+ assertLayoutFile(root, true);
108
+ return withFileLock(`${path}.lock`, async () => {
109
+ const current = this.currentRevision(path);
110
+ if (current !== expectedRevision)
111
+ throw new FleetError('stale_state', 'room layout changed since it was loaded; reload before saving');
112
+ const { description, participants, rooms } = definition;
113
+ const text = stringify({ version: 1, ...(description ? { description } : {}), participants, rooms });
114
+ replaceFileAtomically(path, text, 0o600);
115
+ return { name, revision: revisionOf(text), definition };
116
+ });
117
+ }
118
+ async remove(name, expectedRevision) {
119
+ const path = this.pathFor(name);
120
+ return withFileLock(`${path}.lock`, async () => {
121
+ const current = this.currentRevision(path);
122
+ if (current === ABSENT_REVISION)
123
+ return { name, deleted: false };
124
+ if (current !== expectedRevision)
125
+ throw new FleetError('stale_state', 'room layout changed since it was loaded; reload before deleting');
126
+ unlinkSync(path);
127
+ return { name, deleted: true };
128
+ });
129
+ }
130
+ }
@@ -1,3 +1,4 @@
1
+ import type { AgentHistoryQuery } from '../agent-ours/correspondence.js';
1
2
  import { controlRequest } from '../session/control.js';
2
3
  import type { ConversationEventV1, ConversationSnapshot, PromptReceipt } from '../session/conversation-types.js';
3
4
  import type { SessionSnapshot } from '../session/types.js';
@@ -23,6 +24,8 @@ export interface InterruptReceipt {
23
24
  reasonCode?: string;
24
25
  }
25
26
  export interface RoleSessionControl {
27
+ agentContacts?(): Promise<unknown>;
28
+ agentHistory?(request: AgentHistoryQuery): Promise<unknown>;
26
29
  describe(): Promise<SessionDescriptor>;
27
30
  snapshot(): Promise<SessionSnapshot>;
28
31
  recentOutput(request?: {
@@ -45,6 +48,7 @@ export interface RoleSessionControl {
45
48
  commandId: string;
46
49
  text: string;
47
50
  actorBrowserSession: string;
51
+ expectedSessionGeneration?: string;
48
52
  source: 'owner_admin_console';
49
53
  }): Promise<PromptReceipt>;
50
54
  interruptV2?(commandId: string): Promise<InterruptReceipt & {
@@ -75,6 +79,9 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
75
79
  private readonly stateDir;
76
80
  private readonly request;
77
81
  constructor(stateDir: string, request?: typeof controlRequest);
82
+ agentContacts(): Promise<unknown>;
83
+ agentHistory(request: AgentHistoryQuery): Promise<unknown>;
84
+ private requireCorrespondence;
78
85
  describe(): Promise<SessionDescriptor>;
79
86
  snapshot(): Promise<SessionSnapshot>;
80
87
  recentOutput(request?: {
@@ -91,6 +98,7 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
91
98
  commandId: string;
92
99
  text: string;
93
100
  actorBrowserSession: string;
101
+ expectedSessionGeneration?: string;
94
102
  source: 'owner_admin_console';
95
103
  }): Promise<PromptReceipt>;
96
104
  interruptV2(commandId: string): Promise<InterruptReceipt & {
@@ -1,3 +1,4 @@
1
+ import { resumedConversationPage } from './conversation-history.js';
1
2
  import { controlRequest, followConversation } from '../session/control.js';
2
3
  import { FleetError, normalizeError } from './errors.js';
3
4
  const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
@@ -8,6 +9,10 @@ export class RoleSessionControlAdapter {
8
9
  this.stateDir = stateDir;
9
10
  this.request = request;
10
11
  }
12
+ async agentContacts() { await this.requireCorrespondence(); return this.call('agent_contacts'); }
13
+ async agentHistory(request) { await this.requireCorrespondence(); return this.call('agent_history', { agentHistory: request }); }
14
+ async requireCorrespondence() { if (!(await this.describe()).features.includes('agent_correspondence'))
15
+ throw new FleetError('capability_unavailable', 'This session needs an updated supervisor to show contacts and history.'); }
11
16
  async describe() {
12
17
  const response = await this.call('snapshot');
13
18
  const result = response;
@@ -58,9 +63,20 @@ export class RoleSessionControlAdapter {
58
63
  };
59
64
  }
60
65
  async conversationPage(request = {}) {
61
- return await this.call('conversation_page', {
66
+ const live = await this.call('conversation_page', {
62
67
  after: request.after, limit: request.limit,
63
68
  });
69
+ const snapshot = await this.snapshot();
70
+ if (snapshot.backend !== 'acp' || !snapshot.sessionId)
71
+ return live;
72
+ try {
73
+ const history = await resumedConversationPage(this.stateDir, request, live, snapshot.sessionId);
74
+ const check = await this.call('conversation_page', { limit: 1 });
75
+ return check.snapshot.sessionGeneration === live.snapshot.sessionGeneration ? history : await this.call('conversation_page', request);
76
+ }
77
+ catch {
78
+ return live;
79
+ }
64
80
  }
65
81
  async submitPromptV2(request) {
66
82
  if (!request.text.trim())
@@ -68,13 +84,15 @@ export class RoleSessionControlAdapter {
68
84
  if (Buffer.byteLength(request.text) > 32 * 1024)
69
85
  throw new FleetError('invalid_request', 'text exceeds 32 KiB');
70
86
  try {
71
- return await this.call('submit_prompt_v2', {
87
+ return await this.call(request.expectedSessionGeneration ? 'submit_voice_prompt' : 'submit_prompt_v2', {
72
88
  commandId: request.commandId, text: request.text, actor: request.actorBrowserSession,
73
- source: request.source,
89
+ source: request.source, expectedSessionGeneration: request.expectedSessionGeneration,
74
90
  });
75
91
  }
76
92
  catch (error) {
77
93
  const fleetError = normalizeError(error);
94
+ if (fleetError.message.includes('session_changed:'))
95
+ throw new FleetError('stale_state', 'Agent session changed. Record a new voice message.');
78
96
  if (fleetError.message.includes('idempotency_conflict'))
79
97
  throw new FleetError('idempotency_conflict', 'this command id was already used with a different prompt body');
80
98
  throw fleetError;