@ours.network/fleet 1.2.0-nightly.9 → 2.0.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 (429) hide show
  1. package/README.md +280 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
  11. package/dist/agent-ours/mcp-endpoint.js +101 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +27 -1
  15. package/dist/agent-ours/service.js +107 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +31 -0
  19. package/dist/application/conversation-history.js +339 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +3 -1
  34. package/dist/application/role-repository.js +89 -54
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +120 -31
  49. package/dist/build-info.json +10 -5
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +391 -38
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +214 -20
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +13 -0
  74. package/dist/fleet-command-audit.js +31 -9
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +55 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +182 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +66 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-review.d.ts +113 -0
  180. package/dist/rooms-tasks/task-review.js +561 -0
  181. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  182. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  183. package/dist/rooms-tasks/task-state.d.ts +51 -5
  184. package/dist/rooms-tasks/task-state.js +358 -50
  185. package/dist/rooms-tasks/task-tools.d.ts +35 -0
  186. package/dist/rooms-tasks/task-tools.js +134 -0
  187. package/dist/rooms-tasks/types.d.ts +78 -0
  188. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  189. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  190. package/dist/rooms-tasks/workspace.d.ts +35 -0
  191. package/dist/rooms-tasks/workspace.js +325 -0
  192. package/dist/runner.d.ts +23 -4
  193. package/dist/runner.js +409 -61
  194. package/dist/session/acp.d.ts +75 -2
  195. package/dist/session/acp.js +345 -82
  196. package/dist/session/arbiter.d.ts +98 -0
  197. package/dist/session/arbiter.js +272 -3
  198. package/dist/session/codex-app-server.d.ts +45 -1
  199. package/dist/session/codex-app-server.js +225 -32
  200. package/dist/session/control.d.ts +17 -3
  201. package/dist/session/control.js +219 -23
  202. package/dist/session/conversation-normalizer.d.ts +2 -1
  203. package/dist/session/conversation-normalizer.js +1 -1
  204. package/dist/session/conversation-observation.d.ts +25 -0
  205. package/dist/session/conversation-observation.js +100 -0
  206. package/dist/session/conversation-replay.d.ts +50 -0
  207. package/dist/session/conversation-replay.js +203 -0
  208. package/dist/session/conversation-store.d.ts +15 -1
  209. package/dist/session/conversation-store.js +213 -26
  210. package/dist/session/conversation-types.d.ts +57 -4
  211. package/dist/session/prompt-batch.d.ts +79 -0
  212. package/dist/session/prompt-batch.js +120 -0
  213. package/dist/session/task-notice.d.ts +14 -0
  214. package/dist/session/task-notice.js +13 -0
  215. package/dist/session/types.d.ts +42 -1
  216. package/dist/setup-tunnel-preflight.d.ts +34 -0
  217. package/dist/setup-tunnel-preflight.js +62 -0
  218. package/dist/socket-path.d.ts +2 -0
  219. package/dist/socket-path.js +22 -0
  220. package/dist/spawn.d.ts +5 -1
  221. package/dist/spawn.js +40 -4
  222. package/dist/startup-prompt.d.ts +15 -0
  223. package/dist/startup-prompt.js +71 -0
  224. package/dist/startup-readiness.d.ts +20 -1
  225. package/dist/startup-readiness.js +66 -3
  226. package/dist/startup-telemetry.d.ts +16 -0
  227. package/dist/startup-telemetry.js +46 -0
  228. package/dist/subscriptions/cli.d.ts +91 -0
  229. package/dist/subscriptions/cli.js +223 -0
  230. package/dist/subscriptions/launch.d.ts +37 -0
  231. package/dist/subscriptions/launch.js +93 -0
  232. package/dist/subscriptions/login.d.ts +77 -0
  233. package/dist/subscriptions/login.js +215 -0
  234. package/dist/subscriptions/observed.d.ts +7 -0
  235. package/dist/subscriptions/observed.js +44 -0
  236. package/dist/subscriptions/probe.d.ts +10 -0
  237. package/dist/subscriptions/probe.js +73 -0
  238. package/dist/subscriptions/service.d.ts +102 -0
  239. package/dist/subscriptions/service.js +364 -0
  240. package/dist/subscriptions/store.d.ts +71 -0
  241. package/dist/subscriptions/store.js +143 -0
  242. package/dist/supervisor/adoption.d.ts +8 -0
  243. package/dist/supervisor/adoption.js +78 -0
  244. package/dist/supervisor/catalog.d.ts +62 -0
  245. package/dist/supervisor/catalog.js +254 -0
  246. package/dist/supervisor/fleet.d.ts +8 -0
  247. package/dist/supervisor/fleet.js +89 -0
  248. package/dist/supervisor/index.js +3 -6
  249. package/dist/supervisor/launchd.d.ts +1 -1
  250. package/dist/supervisor/launchd.js +26 -13
  251. package/dist/supervisor/legacy.d.ts +35 -0
  252. package/dist/supervisor/legacy.js +524 -0
  253. package/dist/supervisor/manager.d.ts +15 -0
  254. package/dist/supervisor/manager.js +270 -0
  255. package/dist/supervisor/registration-refresh.d.ts +45 -0
  256. package/dist/supervisor/registration-refresh.js +266 -0
  257. package/dist/supervisor/scope.d.ts +4 -0
  258. package/dist/supervisor/scope.js +10 -0
  259. package/dist/supervisor/systemd.d.ts +2 -2
  260. package/dist/supervisor/systemd.js +18 -37
  261. package/dist/task-supervision.d.ts +9 -0
  262. package/dist/task-supervision.js +145 -0
  263. package/dist/temp-idle.d.ts +17 -0
  264. package/dist/temp-idle.js +42 -0
  265. package/dist/temp-lifecycle.d.ts +23 -8
  266. package/dist/temp-lifecycle.js +123 -66
  267. package/dist/temp-supervisor-recovery.d.ts +1 -1
  268. package/dist/temp-supervisor-recovery.js +2 -2
  269. package/dist/tunnel-setup-completion.d.ts +7 -0
  270. package/dist/tunnel-setup-completion.js +13 -0
  271. package/dist/tunnel-setup-input.d.ts +19 -0
  272. package/dist/tunnel-setup-input.js +130 -0
  273. package/dist/watchdog/query.d.ts +3 -1
  274. package/dist/watchdog/query.js +2 -1
  275. package/dist/web/agent-attachments.d.ts +29 -0
  276. package/dist/web/agent-attachments.js +162 -0
  277. package/dist/web/auth.d.ts +65 -2
  278. package/dist/web/auth.js +201 -11
  279. package/dist/web/available-port.d.ts +2 -0
  280. package/dist/web/available-port.js +23 -0
  281. package/dist/web/control.d.ts +3 -3
  282. package/dist/web/control.js +10 -8
  283. package/dist/web/conversation-stream-codec.d.ts +23 -0
  284. package/dist/web/conversation-stream-codec.js +110 -0
  285. package/dist/web/conversation-stream.d.ts +26 -0
  286. package/dist/web/conversation-stream.js +239 -0
  287. package/dist/web/events.d.ts +2 -0
  288. package/dist/web/events.js +9 -0
  289. package/dist/web/fleet-config-service.d.ts +7 -1
  290. package/dist/web/fleet-config-service.js +61 -16
  291. package/dist/web/prefix-gateway.d.ts +32 -0
  292. package/dist/web/prefix-gateway.js +248 -0
  293. package/dist/web/resource-views.d.ts +138 -0
  294. package/dist/web/resource-views.js +519 -0
  295. package/dist/web/runtime.d.ts +3 -1
  296. package/dist/web/runtime.js +132 -34
  297. package/dist/web/server.d.ts +19 -0
  298. package/dist/web/server.js +893 -71
  299. package/dist/web/service.d.ts +11 -2
  300. package/dist/web/service.js +30 -4
  301. package/dist/web/workspace-devices.d.ts +107 -0
  302. package/dist/web/workspace-devices.js +277 -0
  303. package/dist/web/workspace-tunnel.d.ts +1 -0
  304. package/dist/web/workspace-tunnel.js +34 -0
  305. package/dist/workspace-enrollment.d.ts +83 -0
  306. package/dist/workspace-enrollment.js +605 -0
  307. package/dist/workspace-replacement.d.ts +66 -0
  308. package/dist/workspace-replacement.js +238 -0
  309. package/docs/acp-files.md +146 -0
  310. package/docs/conversation-stream.md +216 -0
  311. package/docs/fleet-migration.md +54 -0
  312. package/docs/live-voice.md +13 -0
  313. package/docs/room-layouts.md +227 -0
  314. package/docs/self-reminders.md +374 -0
  315. package/docs/task-progress-review.md +143 -0
  316. package/docs/task-workspaces.md +225 -0
  317. package/docs/validation/managed-cli-permissions.md +421 -0
  318. package/examples/room-layouts/fresh.yaml +27 -0
  319. package/examples/room-layouts/scoped.yaml +21 -0
  320. package/examples/room-layouts/shared.yaml +19 -0
  321. package/package.json +25 -20
  322. package/presets/README.md +78 -0
  323. package/presets/brain-catalog.json +8 -2
  324. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  325. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  326. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  327. package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
  328. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  329. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  330. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  331. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  332. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  333. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  334. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  335. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  336. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  337. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  338. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  340. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  341. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  342. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  343. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  344. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  345. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  351. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  352. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  353. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  354. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  387. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  396. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  397. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  398. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  399. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  400. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  406. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  407. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  408. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  409. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  410. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  411. package/presets/fleet/roles/Coordinator.yaml +21 -13
  412. package/presets/fleet/roles/Critic.yaml +24 -0
  413. package/presets/fleet/roles/Developer.yaml +32 -0
  414. package/presets/fleet/roles/Engineer.yaml +72 -0
  415. package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
  416. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  417. package/presets/fleet/room_layouts/pair.yaml +24 -0
  418. package/presets/fleet/room_layouts/single.yaml +17 -0
  419. package/presets/fleet/room_layouts/team.yaml +30 -0
  420. package/presets/fleet/room_templates/engineering.yaml +19 -0
  421. package/presets/manifest.json +2 -2
  422. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  423. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  424. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  425. package/dist/web-app/icons/ours-fleet.svg +0 -4
  426. package/dist/web-app/index.html +0 -17
  427. package/dist/web-app/manifest.webmanifest +0 -15
  428. package/dist/web-app/offline.html +0 -18
  429. package/dist/web-app/sw.js +0 -51
@@ -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,6 +1,10 @@
1
- import { controlRequest } from '../session/control.js';
2
- import type { ConversationEventV1, ConversationSnapshot, PromptReceipt } from '../session/conversation-types.js';
1
+ import type { ConversationRoleObservation, ConversationRoleState, ObserveConversationRole } from './conversation-role-observer.js';
2
+ import { type ConversationTailPage } from './conversation-history.js';
3
+ import type { AgentHistoryQuery } from '../agent-ours/correspondence.js';
4
+ import { controlRequest, followConversation } from '../session/control.js';
5
+ import type { ConversationEventV1, ConversationSnapshot, ConversationObservationState, PromptReceipt } from '../session/conversation-types.js';
3
6
  import type { SessionSnapshot } from '../session/types.js';
7
+ import type { PassiveConversationFollowRequest, ConversationReplayPage, ConversationReplayReady } from '../session/conversation-replay.js';
4
8
  import type { OutputPage, SendReceipt, SessionDescriptor } from './types.js';
5
9
  export interface ConversationPageView {
6
10
  events: ConversationEventV1[];
@@ -12,6 +16,23 @@ export interface ConversationPageView {
12
16
  export interface ConversationFollowHandle {
13
17
  close(): void;
14
18
  }
19
+ export interface LegacyConversationFollowRequest {
20
+ after?: string;
21
+ onPage(page: ConversationPageView): void;
22
+ onEvent(event: ConversationEventV1): void;
23
+ onClose(reason?: string): void;
24
+ }
25
+ export interface PassiveConversationFollow extends PassiveConversationFollowRequest {
26
+ /** Reports role updates from the owned or borrowed observer; never attaches a controller. */
27
+ onRole?(state: ConversationRoleState): void;
28
+ /** Application-only borrowed owner; never sent over the private wire. */
29
+ roleObservation?: ConversationRoleObservation;
30
+ onReplay(page: ConversationReplayPage): void;
31
+ onReady(ready: ConversationReplayReady): void;
32
+ onEvent(event: ConversationEventV1): void;
33
+ onState(state: ConversationObservationState): void;
34
+ onClose(reason?: string): void;
35
+ }
15
36
  /**
16
37
  * An interrupt that reached the session. `state` says whether the turn stopped
17
38
  * cooperatively or through bounded forced recovery — both are accepted, so no
@@ -23,7 +44,11 @@ export interface InterruptReceipt {
23
44
  reasonCode?: string;
24
45
  }
25
46
  export interface RoleSessionControl {
26
- describe(): Promise<SessionDescriptor>;
47
+ /** Standalone owner for role-only observation; dispose with that connection. */
48
+ observeRole?: ObserveConversationRole;
49
+ agentContacts?(): Promise<unknown>;
50
+ agentHistory?(request: AgentHistoryQuery): Promise<unknown>;
51
+ describe(roleObservation?: ConversationRoleObservation): Promise<SessionDescriptor>;
27
52
  snapshot(): Promise<SessionSnapshot>;
28
53
  recentOutput(request?: {
29
54
  since?: number;
@@ -41,12 +66,25 @@ export interface RoleSessionControl {
41
66
  after?: string;
42
67
  limit?: number;
43
68
  }): Promise<ConversationPageView>;
69
+ /** The newest visible events (or those before `before`), read backwards from the ledger. */
70
+ conversationTail?(request: {
71
+ before?: string;
72
+ limit?: number;
73
+ signal?: AbortSignal;
74
+ }): Promise<ConversationTailPage>;
44
75
  submitPromptV2?(request: {
45
76
  commandId: string;
46
77
  text: string;
47
78
  actorBrowserSession: string;
79
+ expectedSessionGeneration?: string;
80
+ requireIdle?: boolean;
48
81
  source: 'owner_admin_console';
49
82
  }): Promise<PromptReceipt>;
83
+ interruptPromptV2?(request: {
84
+ commandId: string;
85
+ expectedSessionGeneration: string;
86
+ promptId: string;
87
+ }): Promise<InterruptReceipt>;
50
88
  interruptV2?(commandId: string): Promise<InterruptReceipt & {
51
89
  commandId: string;
52
90
  }>;
@@ -64,18 +102,19 @@ export interface RoleSessionControl {
64
102
  * replay page; `onEvent` every subsequent durable event; `onClose` fires
65
103
  * exactly once when the underlying control stream ends.
66
104
  */
67
- followConversation?(request: {
68
- after?: string;
69
- onPage(page: ConversationPageView): void;
70
- onEvent(event: ConversationEventV1): void;
71
- onClose(reason?: string): void;
72
- }): Promise<ConversationFollowHandle>;
105
+ followConversation?(request: LegacyConversationFollowRequest | PassiveConversationFollow): Promise<ConversationFollowHandle>;
73
106
  }
74
107
  export declare class RoleSessionControlAdapter implements RoleSessionControl {
75
108
  private readonly stateDir;
76
109
  private readonly request;
77
- constructor(stateDir: string, request?: typeof controlRequest);
78
- describe(): Promise<SessionDescriptor>;
110
+ private readonly openFollow;
111
+ readonly observeRole?: ObserveConversationRole | undefined;
112
+ private controlHealthy;
113
+ constructor(stateDir: string, request?: typeof controlRequest, openFollow?: typeof followConversation, observeRole?: ObserveConversationRole | undefined);
114
+ agentContacts(): Promise<unknown>;
115
+ agentHistory(request: AgentHistoryQuery): Promise<unknown>;
116
+ private requireCorrespondence;
117
+ describe(roleObservation?: ConversationRoleObservation): Promise<SessionDescriptor>;
79
118
  snapshot(): Promise<SessionSnapshot>;
80
119
  recentOutput(request?: {
81
120
  since?: number;
@@ -87,12 +126,24 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
87
126
  after?: string;
88
127
  limit?: number;
89
128
  }): Promise<ConversationPageView>;
129
+ conversationTail(request?: {
130
+ before?: string;
131
+ limit?: number;
132
+ signal?: AbortSignal;
133
+ }): Promise<ConversationTailPage>;
90
134
  submitPromptV2(request: {
91
135
  commandId: string;
92
136
  text: string;
93
137
  actorBrowserSession: string;
138
+ expectedSessionGeneration?: string;
139
+ requireIdle?: boolean;
94
140
  source: 'owner_admin_console';
95
141
  }): Promise<PromptReceipt>;
142
+ interruptPromptV2(request: {
143
+ commandId: string;
144
+ expectedSessionGeneration: string;
145
+ promptId: string;
146
+ }): Promise<InterruptReceipt>;
96
147
  interruptV2(commandId: string): Promise<InterruptReceipt & {
97
148
  commandId: string;
98
149
  }>;
@@ -105,12 +156,7 @@ export declare class RoleSessionControlAdapter implements RoleSessionControl {
105
156
  accepted: true;
106
157
  commandId: string;
107
158
  }>;
108
- followConversation(request: {
109
- after?: string;
110
- onPage(page: ConversationPageView): void;
111
- onEvent(event: ConversationEventV1): void;
112
- onClose(reason?: string): void;
113
- }): Promise<ConversationFollowHandle>;
159
+ followConversation(request: LegacyConversationFollowRequest | PassiveConversationFollow): Promise<ConversationFollowHandle>;
114
160
  respondPermission(request: {
115
161
  permissionId: string;
116
162
  optionId: string;
@@ -1,20 +1,42 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { conversationTailPage, resumedConversationPage } from './conversation-history.js';
1
4
  import { controlRequest, followConversation } from '../session/control.js';
2
5
  import { FleetError, normalizeError } from './errors.js';
3
6
  const visibleEvents = (events) => events.filter(event => event.kind !== 'thought');
4
7
  export class RoleSessionControlAdapter {
5
8
  stateDir;
6
9
  request;
7
- constructor(stateDir, request = controlRequest) {
10
+ openFollow;
11
+ observeRole;
12
+ controlHealthy = true;
13
+ constructor(stateDir, request = controlRequest, openFollow = followConversation, observeRole) {
8
14
  this.stateDir = stateDir;
9
15
  this.request = request;
16
+ this.openFollow = openFollow;
17
+ this.observeRole = observeRole;
10
18
  }
11
- async describe() {
12
- const response = await this.call('snapshot');
13
- const result = response;
14
- return {
15
- backend: result.backend, protocolVersion: result.protocolVersion ?? 1,
16
- features: result.features ?? [], snapshot: result,
17
- };
19
+ async agentContacts() { await this.requireCorrespondence(); return this.call('agent_contacts'); }
20
+ async agentHistory(request) { await this.requireCorrespondence(); return this.call('agent_history', { agentHistory: request }); }
21
+ async requireCorrespondence() { if (!(await this.describe()).features.includes('agent_correspondence'))
22
+ throw new FleetError('capability_unavailable', 'This session needs an updated supervisor to show contacts and history.'); }
23
+ async describe(roleObservation) {
24
+ // A describe probe owns and always disposes its temporary watcher. Follow uses
25
+ // a separate connection-owned observer and publishes updates via onRole.
26
+ const role = roleObservation ?? this.observeRole?.(() => { });
27
+ try {
28
+ const response = await this.call('snapshot');
29
+ const result = response;
30
+ const features = (result.features ?? []).filter(feature => feature !== 'conversation_live_complete');
31
+ if (this.controlHealthy && role?.snapshot().health === 'healthy' && role.snapshot().availability === 'available'
32
+ && features.includes('conversation_observation') && features.includes('control_heartbeat'))
33
+ features.push('conversation_live_complete');
34
+ return { backend: result.backend, protocolVersion: result.protocolVersion ?? 1, features, snapshot: result };
35
+ }
36
+ finally {
37
+ if (!roleObservation)
38
+ role?.dispose();
39
+ }
18
40
  }
19
41
  async snapshot() {
20
42
  return await this.call('snapshot');
@@ -58,28 +80,103 @@ export class RoleSessionControlAdapter {
58
80
  };
59
81
  }
60
82
  async conversationPage(request = {}) {
61
- return await this.call('conversation_page', {
83
+ const live = await this.call('conversation_page', {
62
84
  after: request.after, limit: request.limit,
63
85
  });
86
+ const snapshot = await this.snapshot();
87
+ if (snapshot.backend !== 'acp' || !snapshot.sessionId)
88
+ return live;
89
+ try {
90
+ const history = await resumedConversationPage(this.stateDir, request, live, snapshot.sessionId);
91
+ const check = await this.call('conversation_page', { limit: 1 });
92
+ return check.snapshot.sessionGeneration === live.snapshot.sessionGeneration ? history : await this.call('conversation_page', request);
93
+ }
94
+ catch {
95
+ return live;
96
+ }
97
+ }
98
+ async conversationTail(request = {}) {
99
+ // A replacement while reading disk invalidates both the tail and its snapshot.
100
+ for (let attempt = 0; attempt < 3; attempt++) {
101
+ request.signal?.throwIfAborted();
102
+ const live = await this.call('conversation_page', { limit: 1 });
103
+ request.signal?.throwIfAborted();
104
+ const snapshot = await this.snapshot();
105
+ let resumed;
106
+ if (snapshot.backend === 'acp' && snapshot.sessionId) {
107
+ try {
108
+ if ((await readFile(join(this.stateDir, '.acp-session-id'), 'utf8')).trim() === snapshot.sessionId)
109
+ resumed = snapshot.sessionId;
110
+ }
111
+ catch { /* no resumed history: live generation only */ }
112
+ }
113
+ let page;
114
+ try {
115
+ page = await conversationTailPage(this.stateDir, request, live.snapshot, resumed, request.signal);
116
+ }
117
+ catch (error) {
118
+ request.signal?.throwIfAborted();
119
+ throw new FleetError('invalid_request', error.message);
120
+ }
121
+ request.signal?.throwIfAborted();
122
+ const check = await this.call('conversation_page', { limit: 1 });
123
+ request.signal?.throwIfAborted();
124
+ if (check.snapshot.sessionGeneration === live.snapshot.sessionGeneration)
125
+ return page;
126
+ }
127
+ throw new FleetError('stale_state', 'Session generation changed during conversation baseline; explicit recovery required.');
64
128
  }
65
129
  async submitPromptV2(request) {
66
130
  if (!request.text.trim())
67
131
  throw new FleetError('invalid_request', 'text is required');
68
132
  if (Buffer.byteLength(request.text) > 32 * 1024)
69
133
  throw new FleetError('invalid_request', 'text exceeds 32 KiB');
134
+ // A prompt bound to a session generation (voice or attachments) is checked atomically by supervisors that
135
+ // support it. Older supervisors do not know that command and never answer, so check the generation here
136
+ // and submit an ordinary prompt; its command id keeps retries idempotent either way.
137
+ const features = request.requireIdle ? (await this.describe()).features : undefined;
138
+ if (request.requireIdle && (!request.expectedSessionGeneration || !features?.includes('idle_bound_prompts')))
139
+ throw new FleetError('capability_unavailable', 'Live voice requires an updated supervisor.');
140
+ let command = 'submit_prompt_v2';
141
+ if (request.expectedSessionGeneration) {
142
+ if ((await this.describe()).features.includes('generation_bound_prompts'))
143
+ command = 'submit_voice_prompt';
144
+ else {
145
+ const live = await this.call('conversation_page', { limit: 1 });
146
+ if (live.snapshot?.sessionGeneration !== request.expectedSessionGeneration)
147
+ throw new FleetError('stale_state', 'Agent session changed. Record a new voice message.');
148
+ }
149
+ }
70
150
  try {
71
- return await this.call('submit_prompt_v2', {
151
+ return await this.call(command, {
72
152
  commandId: request.commandId, text: request.text, actor: request.actorBrowserSession,
73
- source: request.source,
153
+ source: request.source, expectedSessionGeneration: request.expectedSessionGeneration, requireIdle: request.requireIdle,
74
154
  });
75
155
  }
76
156
  catch (error) {
77
157
  const fleetError = normalizeError(error);
158
+ if (fleetError.message.includes('session_changed:'))
159
+ throw new FleetError('stale_state', 'Agent session changed. Record a new voice message.');
160
+ if (fleetError.message.includes('voice_busy:'))
161
+ throw new FleetError('conflict', 'Agent is already working. Wait for the current turn.');
78
162
  if (fleetError.message.includes('idempotency_conflict'))
79
163
  throw new FleetError('idempotency_conflict', 'this command id was already used with a different prompt body');
80
164
  throw fleetError;
81
165
  }
82
166
  }
167
+ async interruptPromptV2(request) {
168
+ if (!(await this.describe()).features.includes('targeted_voice_interrupt'))
169
+ throw new FleetError('capability_unavailable', 'Live voice requires an updated supervisor.');
170
+ try {
171
+ return await this.call('interrupt_voice_v2', request);
172
+ }
173
+ catch (error) {
174
+ const failure = normalizeError(error);
175
+ if (failure.message.includes('session_changed:'))
176
+ throw new FleetError('stale_state', 'Agent session changed.');
177
+ throw failure;
178
+ }
179
+ }
83
180
  async interruptV2(commandId) {
84
181
  const receipt = await this.call('interrupt_v2', { commandId });
85
182
  return {
@@ -101,32 +198,133 @@ export class RoleSessionControlAdapter {
101
198
  }
102
199
  }
103
200
  async followConversation(request) {
201
+ const passive = 'controller' in request ? request : undefined;
104
202
  let sawPage = false;
105
203
  let closed = false;
204
+ let role;
205
+ let unsubscribeRole;
206
+ let lifetime;
207
+ let roleRevision = -1;
208
+ let silence;
209
+ let lastControlHeartbeat = Date.now();
210
+ const armSilence = () => {
211
+ if (silence)
212
+ clearTimeout(silence);
213
+ silence = setTimeout(() => {
214
+ // Liveness only: no role/session snapshots, regardless of web traffic.
215
+ if (Date.now() - lastControlHeartbeat >= 45_000) {
216
+ this.controlHealthy = false;
217
+ finish('control-silent');
218
+ }
219
+ else
220
+ armSilence();
221
+ }, Math.max(1, 45_000 - (Date.now() - lastControlHeartbeat)));
222
+ silence.unref?.();
223
+ };
224
+ let follow;
106
225
  const finish = (reason) => {
107
226
  if (closed)
108
227
  return;
109
228
  closed = true;
229
+ passive?.signal?.removeEventListener('abort', aborted);
230
+ if (silence)
231
+ clearTimeout(silence);
232
+ unsubscribeRole?.();
233
+ if (!passive?.roleObservation)
234
+ role?.dispose();
235
+ follow?.close();
110
236
  request.onClose(reason);
111
237
  };
112
- const follow = await followConversation(this.stateDir, request.after, message => {
113
- if (message.conversationEvent) {
114
- request.onEvent(message.conversationEvent);
115
- return;
238
+ const aborted = () => finish('conversation follow aborted');
239
+ passive?.signal?.addEventListener('abort', aborted, { once: true });
240
+ if (passive?.signal?.aborted)
241
+ aborted();
242
+ if (closed)
243
+ return { close: () => { } };
244
+ try {
245
+ if (passive) {
246
+ const changed = (state) => {
247
+ if (closed || state.roleRevision <= roleRevision)
248
+ return;
249
+ roleRevision = state.roleRevision;
250
+ passive.onRole?.(state);
251
+ if (state.health !== 'healthy') {
252
+ this.controlHealthy = false;
253
+ finish(state.failure ?? 'role-observation-unavailable');
254
+ }
255
+ else if (lifetime !== undefined && (state.roleLifetime !== lifetime || state.availability !== 'available' || state.stateDir !== this.stateDir))
256
+ finish('role-replaced');
257
+ lifetime = state.roleLifetime;
258
+ };
259
+ role = passive.roleObservation ?? this.observeRole?.(() => { });
260
+ if (role) {
261
+ const registration = role.subscribe(changed);
262
+ unsubscribeRole = registration.unsubscribe;
263
+ const initial = registration.snapshot;
264
+ changed(initial);
265
+ if (initial.availability !== 'available' || initial.stateDir !== this.stateDir)
266
+ finish('role-unavailable');
267
+ if (closed)
268
+ return { close: () => { } };
269
+ }
270
+ armSilence();
116
271
  }
117
- if (!sawPage) {
118
- sawPage = true;
272
+ follow = await this.openFollow(this.stateDir, passive ? { controller: false, expectedGeneration: passive.expectedGeneration, after: passive.after, signal: passive.signal } : request.after, message => {
273
+ if (closed)
274
+ return;
275
+ if (passive) {
276
+ // A delayed callback must not revive an already elapsed source deadline.
277
+ if (Date.now() - lastControlHeartbeat >= 45_000) {
278
+ this.controlHealthy = false;
279
+ finish('control-silent');
280
+ return;
281
+ }
282
+ if (message.controlHeartbeat === true || message.conversationReplay || message.conversationReady || message.conversationEvent || message.conversationState) {
283
+ lastControlHeartbeat = Date.now();
284
+ this.controlHealthy = true;
285
+ armSilence();
286
+ }
287
+ if (message.controlHeartbeat === true)
288
+ return;
289
+ }
119
290
  if (message.ok === false) {
120
291
  finish(String(message.error ?? 'conversation follow refused'));
121
- follow.close();
122
292
  return;
123
293
  }
124
- request.onPage(message.result);
294
+ if (message.conversationEvent) {
295
+ request.onEvent(message.conversationEvent);
296
+ return;
297
+ }
298
+ if (passive) {
299
+ if (message.conversationReplay)
300
+ passive.onReplay(message.conversationReplay);
301
+ else if (message.conversationReady)
302
+ passive.onReady(message.conversationReady);
303
+ else if (message.conversationState)
304
+ passive.onState(message.conversationState);
305
+ }
306
+ else if (!sawPage) {
307
+ sawPage = true;
308
+ request.onPage(message.result);
309
+ }
310
+ });
311
+ // Abort may win while the connector is still creating its handle.
312
+ if (closed || passive?.signal?.aborted) {
313
+ follow.close();
314
+ finish('conversation follow aborted');
125
315
  }
126
- });
127
- follow.socket.once('close', () => finish());
128
- follow.socket.once('error', error => finish(error.message));
129
- return { close: () => { follow.close(); finish(); } };
316
+ else {
317
+ follow.socket.once('close', () => finish());
318
+ follow.socket.once('error', error => finish(error.message));
319
+ if (follow.socket.destroyed)
320
+ finish();
321
+ }
322
+ return { close: () => finish() };
323
+ }
324
+ catch (error) {
325
+ finish(error.message);
326
+ throw error;
327
+ }
130
328
  }
131
329
  async respondPermission(request) {
132
330
  await this.call('respond_permission', request);
@@ -1,3 +1,4 @@
1
+ import { socketPath } from '../socket-path.js';
1
2
  import { createHash } from 'node:crypto';
2
3
  import { existsSync, readFileSync } from 'node:fs';
3
4
  import { join } from 'node:path';
@@ -35,7 +36,8 @@ export class SupervisorOursTools {
35
36
  const identity = legacy
36
37
  ? legacySupervisorIdentity(role, identityName, selected.temporary, descriptor.generation)
37
38
  : { name: identityName, cid: descriptor.cid, generation: descriptor.generation };
38
- if (legacy && descriptor.socket !== join(selected.dir, '.ours-bridge', `g${descriptor.generation}.sock`))
39
+ const legacySocket = join(selected.dir, '.ours-bridge', `g${descriptor.generation}.sock`);
40
+ if (legacy && descriptor.socket !== legacySocket && descriptor.socket !== socketPath(legacySocket))
39
41
  throw new FleetError('capability_unavailable', 'legacy supervisor socket does not match the selected agent');
40
42
  if ((!legacy && (descriptor.role !== role || descriptor.identity !== identityName))
41
43
  || !/^[a-f0-9]{64}$/i.test(identity.cid ?? '') || !Number.isSafeInteger(descriptor.generation) || descriptor.generation < 1)
@@ -71,16 +73,8 @@ export class SupervisorOursTools {
71
73
  verifySelection();
72
74
  return;
73
75
  }
74
- const actual = await client.callTool({ name: 'current_identity', arguments: {} });
75
- const blocks = actual.content;
76
- const lines = blocks?.length === 1 && blocks[0].type === 'text' ? blocks[0].text?.split('\n') : undefined;
77
- const prefix = `Bound to "${identity.name}" (${identity.cid})`;
78
- const suffix = lines?.[0].startsWith(prefix) ? lines[0].slice(prefix.length) : undefined;
79
- const roleSuffix = typeof suffix === 'string' && /^ — role "[^"\r\n]+" under root "[^"\r\n]+"\.$/.test(suffix);
80
- const temporaryLine = 'TEMPORARY identity owned by the Fleet supervisor for this logical agent instance. Bridge or harness disconnect retains it; terminal supervisor release deletes local state with best-effort peer notices.';
81
- if (actual.isError || !(roleSuffix || (selected.temporary && suffix === '.'))
82
- || (selected.temporary ? lines?.[1] !== temporaryLine : lines?.[1]?.startsWith('TEMPORARY')))
83
- throw new FleetError('capability_unavailable', 'connected supervisor identity does not match legacy ownership proof');
76
+ // The supervisor owns the binding. The authenticated bridge and pinned local
77
+ // assignment are authoritative; current_identity is display text, not a protocol.
84
78
  const fresh = legacySupervisorIdentity(role, identityName, selected.temporary, descriptor.generation);
85
79
  if (!('proof' in identity) || fresh.proof !== identity.proof)
86
80
  throw new FleetError('capability_unavailable', 'legacy supervisor identity changed');