@ours.network/fleet 1.2.0-nightly.8 → 1.2.0-nightly.80

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 (423) hide show
  1. package/README.md +279 -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 +19 -0
  11. package/dist/agent-ours/mcp-endpoint.js +98 -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 +24 -1
  15. package/dist/agent-ours/service.js +97 -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 +34 -0
  19. package/dist/application/conversation-history.js +294 -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 +1 -1
  34. package/dist/application/role-repository.js +30 -7
  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 +97 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +400 -37
  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 +207 -19
  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 +7 -0
  74. package/dist/fleet-command-audit.js +24 -8
  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 +43 -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 +65 -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 +63 -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-service-retirement.d.ts +11 -0
  180. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  181. package/dist/rooms-tasks/task-state.d.ts +43 -5
  182. package/dist/rooms-tasks/task-state.js +244 -22
  183. package/dist/rooms-tasks/types.d.ts +35 -0
  184. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  185. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  186. package/dist/rooms-tasks/workspace.d.ts +35 -0
  187. package/dist/rooms-tasks/workspace.js +325 -0
  188. package/dist/runner.d.ts +33 -4
  189. package/dist/runner.js +410 -62
  190. package/dist/session/acp.d.ts +75 -2
  191. package/dist/session/acp.js +345 -82
  192. package/dist/session/arbiter.d.ts +98 -0
  193. package/dist/session/arbiter.js +272 -3
  194. package/dist/session/codex-app-server.d.ts +45 -1
  195. package/dist/session/codex-app-server.js +225 -32
  196. package/dist/session/control.d.ts +17 -3
  197. package/dist/session/control.js +219 -23
  198. package/dist/session/conversation-normalizer.d.ts +2 -1
  199. package/dist/session/conversation-normalizer.js +1 -1
  200. package/dist/session/conversation-observation.d.ts +25 -0
  201. package/dist/session/conversation-observation.js +100 -0
  202. package/dist/session/conversation-replay.d.ts +50 -0
  203. package/dist/session/conversation-replay.js +203 -0
  204. package/dist/session/conversation-store.d.ts +15 -1
  205. package/dist/session/conversation-store.js +213 -26
  206. package/dist/session/conversation-types.d.ts +57 -4
  207. package/dist/session/prompt-batch.d.ts +79 -0
  208. package/dist/session/prompt-batch.js +120 -0
  209. package/dist/session/task-notice.d.ts +14 -0
  210. package/dist/session/task-notice.js +13 -0
  211. package/dist/session/types.d.ts +42 -1
  212. package/dist/setup-tunnel-preflight.d.ts +34 -0
  213. package/dist/setup-tunnel-preflight.js +62 -0
  214. package/dist/socket-path.d.ts +2 -0
  215. package/dist/socket-path.js +22 -0
  216. package/dist/spawn.d.ts +5 -1
  217. package/dist/spawn.js +40 -4
  218. package/dist/startup-prompt.d.ts +15 -0
  219. package/dist/startup-prompt.js +71 -0
  220. package/dist/startup-readiness.d.ts +32 -0
  221. package/dist/startup-readiness.js +101 -0
  222. package/dist/startup-telemetry.d.ts +16 -0
  223. package/dist/startup-telemetry.js +46 -0
  224. package/dist/subscriptions/cli.d.ts +91 -0
  225. package/dist/subscriptions/cli.js +223 -0
  226. package/dist/subscriptions/launch.d.ts +37 -0
  227. package/dist/subscriptions/launch.js +93 -0
  228. package/dist/subscriptions/login.d.ts +77 -0
  229. package/dist/subscriptions/login.js +215 -0
  230. package/dist/subscriptions/observed.d.ts +7 -0
  231. package/dist/subscriptions/observed.js +44 -0
  232. package/dist/subscriptions/probe.d.ts +10 -0
  233. package/dist/subscriptions/probe.js +73 -0
  234. package/dist/subscriptions/service.d.ts +102 -0
  235. package/dist/subscriptions/service.js +364 -0
  236. package/dist/subscriptions/store.d.ts +71 -0
  237. package/dist/subscriptions/store.js +143 -0
  238. package/dist/supervisor/adoption.d.ts +8 -0
  239. package/dist/supervisor/adoption.js +78 -0
  240. package/dist/supervisor/catalog.d.ts +62 -0
  241. package/dist/supervisor/catalog.js +254 -0
  242. package/dist/supervisor/fleet.d.ts +8 -0
  243. package/dist/supervisor/fleet.js +89 -0
  244. package/dist/supervisor/index.js +3 -6
  245. package/dist/supervisor/launchd.d.ts +1 -1
  246. package/dist/supervisor/launchd.js +26 -13
  247. package/dist/supervisor/legacy.d.ts +35 -0
  248. package/dist/supervisor/legacy.js +524 -0
  249. package/dist/supervisor/manager.d.ts +15 -0
  250. package/dist/supervisor/manager.js +270 -0
  251. package/dist/supervisor/registration-refresh.d.ts +45 -0
  252. package/dist/supervisor/registration-refresh.js +266 -0
  253. package/dist/supervisor/scope.d.ts +4 -0
  254. package/dist/supervisor/scope.js +10 -0
  255. package/dist/supervisor/systemd.d.ts +2 -2
  256. package/dist/supervisor/systemd.js +19 -33
  257. package/dist/task-supervision.d.ts +9 -0
  258. package/dist/task-supervision.js +145 -0
  259. package/dist/temp-idle.d.ts +17 -0
  260. package/dist/temp-idle.js +42 -0
  261. package/dist/temp-lifecycle.d.ts +23 -8
  262. package/dist/temp-lifecycle.js +123 -66
  263. package/dist/temp-supervisor-recovery.d.ts +1 -1
  264. package/dist/temp-supervisor-recovery.js +2 -2
  265. package/dist/tunnel-setup-completion.d.ts +7 -0
  266. package/dist/tunnel-setup-completion.js +13 -0
  267. package/dist/tunnel-setup-input.d.ts +19 -0
  268. package/dist/tunnel-setup-input.js +130 -0
  269. package/dist/watchdog/query.d.ts +3 -1
  270. package/dist/watchdog/query.js +2 -1
  271. package/dist/web/agent-attachments.d.ts +29 -0
  272. package/dist/web/agent-attachments.js +162 -0
  273. package/dist/web/auth.d.ts +65 -2
  274. package/dist/web/auth.js +201 -11
  275. package/dist/web/available-port.d.ts +2 -0
  276. package/dist/web/available-port.js +23 -0
  277. package/dist/web/control.d.ts +3 -3
  278. package/dist/web/control.js +10 -8
  279. package/dist/web/conversation-stream-codec.d.ts +23 -0
  280. package/dist/web/conversation-stream-codec.js +110 -0
  281. package/dist/web/conversation-stream.d.ts +26 -0
  282. package/dist/web/conversation-stream.js +239 -0
  283. package/dist/web/events.d.ts +2 -0
  284. package/dist/web/events.js +9 -0
  285. package/dist/web/fleet-config-service.d.ts +7 -1
  286. package/dist/web/fleet-config-service.js +61 -16
  287. package/dist/web/prefix-gateway.d.ts +32 -0
  288. package/dist/web/prefix-gateway.js +248 -0
  289. package/dist/web/resource-views.d.ts +135 -0
  290. package/dist/web/resource-views.js +486 -0
  291. package/dist/web/runtime.d.ts +3 -1
  292. package/dist/web/runtime.js +132 -34
  293. package/dist/web/server.d.ts +19 -0
  294. package/dist/web/server.js +821 -68
  295. package/dist/web/service.d.ts +11 -2
  296. package/dist/web/service.js +30 -4
  297. package/dist/web/workspace-devices.d.ts +107 -0
  298. package/dist/web/workspace-devices.js +277 -0
  299. package/dist/web/workspace-tunnel.d.ts +1 -0
  300. package/dist/web/workspace-tunnel.js +34 -0
  301. package/dist/workspace-enrollment.d.ts +83 -0
  302. package/dist/workspace-enrollment.js +605 -0
  303. package/dist/workspace-replacement.d.ts +66 -0
  304. package/dist/workspace-replacement.js +238 -0
  305. package/docs/acp-files.md +146 -0
  306. package/docs/conversation-stream.md +216 -0
  307. package/docs/fleet-migration.md +54 -0
  308. package/docs/live-voice.md +13 -0
  309. package/docs/room-layouts.md +227 -0
  310. package/docs/self-reminders.md +374 -0
  311. package/docs/task-workspaces.md +225 -0
  312. package/docs/validation/managed-cli-permissions.md +421 -0
  313. package/examples/room-layouts/fresh.yaml +27 -0
  314. package/examples/room-layouts/scoped.yaml +21 -0
  315. package/examples/room-layouts/shared.yaml +19 -0
  316. package/package.json +24 -20
  317. package/presets/README.md +78 -0
  318. package/presets/brain-catalog.json +8 -2
  319. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  320. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  321. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  322. package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -2
  323. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  324. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  325. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  326. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  327. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  328. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  329. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  330. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  331. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  332. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  333. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  334. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  335. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  336. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  337. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  338. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  339. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  340. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  341. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  342. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  343. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  344. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  345. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  351. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  352. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  353. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  354. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  384. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  385. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  386. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  387. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  390. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  391. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  392. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  393. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  394. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  396. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  397. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  398. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  399. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  400. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  406. package/presets/fleet/roles/Coordinator.yaml +12 -11
  407. package/presets/fleet/roles/Critic.yaml +13 -0
  408. package/presets/fleet/roles/Developer.yaml +13 -0
  409. package/presets/fleet/roles/Engineer.yaml +53 -0
  410. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  411. package/presets/fleet/room_layouts/pair.yaml +24 -0
  412. package/presets/fleet/room_layouts/single.yaml +17 -0
  413. package/presets/fleet/room_layouts/team.yaml +30 -0
  414. package/presets/fleet/room_templates/engineering.yaml +19 -0
  415. package/presets/manifest.json +2 -2
  416. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  417. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  418. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  419. package/dist/web-app/icons/ours-fleet.svg +0 -4
  420. package/dist/web-app/index.html +0 -17
  421. package/dist/web-app/manifest.webmanifest +0 -15
  422. package/dist/web-app/offline.html +0 -18
  423. package/dist/web-app/sw.js +0 -51
@@ -0,0 +1,56 @@
1
+ import { statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { resolveBundledAcpAgent } from '../harness/acp-agent.js';
4
+ import { probeCodexRuntime } from '../harness/codex-runtime.js';
5
+ import { codexOfferedModels, profileEnv } from '../subscriptions/cli.js';
6
+ import { profileHome, readSubscriptionState } from '../subscriptions/store.js';
7
+ const KNOWN_FOR_MS = 5 * 60_000;
8
+ const UNKNOWN_FOR_MS = 10_000;
9
+ /** When the profile's sign-in last changed; a new sign-in must not reuse another account's answer. */
10
+ function signInStamp(home) {
11
+ try {
12
+ return statSync(join(home, 'auth.json')).mtimeMs;
13
+ }
14
+ catch {
15
+ return 0;
16
+ }
17
+ }
18
+ /**
19
+ * The models offered to the active Codex account by the runtime that sessions will actually use.
20
+ * An answer is reused only for the same executable, version, profile and sign-in.
21
+ */
22
+ export function createCodexModelDiscovery(overrides = {}) {
23
+ const deps = {
24
+ runtime: () => probeCodexRuntime(resolveBundledAcpAgent('@agentclientprotocol/codex-acp', 'codex-acp', 'codex-acp'), process.env),
25
+ ask: codexOfferedModels,
26
+ now: Date.now,
27
+ ...overrides,
28
+ };
29
+ let cached;
30
+ let pending;
31
+ return async () => {
32
+ let key, executable, env;
33
+ try {
34
+ const profileId = readSubscriptionState().providers.codex.activeProfileId;
35
+ const profile = profileHome('codex', profileId);
36
+ const runtime = await deps.runtime();
37
+ executable = runtime.executable;
38
+ env = profileEnv('CODEX_HOME', profileId, profile);
39
+ key = [runtime.executable, runtime.version, profileId, signInStamp(profile)].join('\n');
40
+ }
41
+ catch (error) {
42
+ return { state: 'unknown', reason: error instanceof Error ? error.message : String(error) };
43
+ }
44
+ if (cached && cached.key === key && cached.until > deps.now())
45
+ return cached.value;
46
+ if (pending?.key === key)
47
+ return pending.answer;
48
+ const answer = deps.ask(executable, env).then(value => {
49
+ cached = { key, until: deps.now() + (value.state === 'known' ? KNOWN_FOR_MS : UNKNOWN_FOR_MS), value };
50
+ return value;
51
+ }).finally(() => { if (pending?.key === key)
52
+ pending = undefined; });
53
+ pending = { key, answer };
54
+ return answer;
55
+ };
56
+ }
@@ -0,0 +1,34 @@
1
+ import type { ConversationEventV1 } from '../session/conversation-types.js';
2
+ import type { ConversationPageView } from './session-control.js';
3
+ /** Whether a ledger event belongs in the conversation: the live generation, or history of the resumed ACP session. */
4
+ export declare function conversationEventVisible(event: ConversationEventV1, sessionGeneration: string, resumedSessionId?: string): boolean;
5
+ export interface ConversationTailPage extends ConversationPageView {
6
+ /** Pass as `before` to read the next older page. */
7
+ olderCursor?: string;
8
+ hasOlder: boolean;
9
+ /** Latest page only: older events the view still needs, the current session's latest capabilities and still-pending permission requests. */
10
+ context: ConversationEventV1[];
11
+ }
12
+ /**
13
+ * The newest `limit` visible events before `before` (or the newest overall),
14
+ * read backwards from the ledger so opening a long session costs one page.
15
+ * `nextCursor` is the newest ledger position seen, for forward `after` paging.
16
+ */
17
+ export declare function conversationTailPage(stateDir: string, request: {
18
+ before?: string;
19
+ limit?: number;
20
+ }, snapshot: ConversationPageView['snapshot'], resumedSessionId?: string, signal?: AbortSignal): Promise<ConversationTailPage>;
21
+ /** Read the existing ACP session ledger without opening its writer/recovery store. */
22
+ export declare function resumedConversationPage(stateDir: string, request: {
23
+ after?: string;
24
+ limit?: number;
25
+ }, live: ConversationPageView, expectedSessionId: string): Promise<ConversationPageView>;
26
+ /** Download authorization requires an actual published attachment, never an orphan copy. */
27
+ export declare function conversationHasAttachment(stateDir: string, attachment: import('../file-delivery/types.js').DeliveredFile): Promise<boolean>;
28
+ /** Passive, bounded last assistant text for directory previews. Never opens the
29
+ * ledger writer/recovery path or acquires a session/controller. An incomplete
30
+ * newest append is not committed yet; completed malformed records still fail. */
31
+ export declare function conversationPreview(stateDir: string, signal: AbortSignal): Promise<{
32
+ text: string;
33
+ at: string;
34
+ }>;
@@ -0,0 +1,294 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { open, readFile, readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ const HISTORICAL_KINDS = ['file.attached', 'fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'];
6
+ /** Whether a ledger event belongs in the conversation: the live generation, or history of the resumed ACP session. */
7
+ export function conversationEventVisible(event, sessionGeneration, resumedSessionId) {
8
+ return event.source !== 'agent_replay' && (event.sessionGeneration === sessionGeneration || (!!resumedSessionId && HISTORICAL_KINDS.includes(event.kind) && event.acpSessionId === resumedSessionId));
9
+ }
10
+ /** Newest-first lines of a file, read in chunks from its end; `visit` returns false to stop. */
11
+ async function readLinesBackward(path, visit, signal) {
12
+ signal?.throwIfAborted();
13
+ const file = await open(path, 'r');
14
+ try {
15
+ let position = (await file.stat()).size;
16
+ let carry = Buffer.alloc(0);
17
+ while (position > 0) {
18
+ signal?.throwIfAborted();
19
+ const size = Math.min(256 * 1024, position);
20
+ position -= size;
21
+ const chunk = Buffer.alloc(size);
22
+ await file.read(chunk, 0, size, position);
23
+ signal?.throwIfAborted();
24
+ let buffer = Buffer.concat([chunk, carry]);
25
+ let end = buffer.length;
26
+ for (let i = buffer.length - 1; i >= 0; i--) {
27
+ if (buffer[i] !== 10)
28
+ continue;
29
+ if (end > i + 1 && !visit(buffer.toString('utf8', i + 1, end)))
30
+ return false;
31
+ end = i;
32
+ }
33
+ carry = buffer.subarray(0, end);
34
+ }
35
+ if (carry.length && !visit(carry.toString('utf8')))
36
+ return false;
37
+ return true;
38
+ }
39
+ finally {
40
+ await file.close();
41
+ }
42
+ }
43
+ /** The last event cursor in a segment, read from its tail; undefined when none is readable. */
44
+ async function lastSeq(path) {
45
+ let seq;
46
+ await readLinesBackward(path, line => { try {
47
+ const value = Number(JSON.parse(line).seq);
48
+ if (Number.isSafeInteger(value)) {
49
+ seq = value;
50
+ return false;
51
+ }
52
+ }
53
+ catch { } return true; });
54
+ return seq;
55
+ }
56
+ /**
57
+ * The newest `limit` visible events before `before` (or the newest overall),
58
+ * read backwards from the ledger so opening a long session costs one page.
59
+ * `nextCursor` is the newest ledger position seen, for forward `after` paging.
60
+ */
61
+ export async function conversationTailPage(stateDir, request, snapshot, resumedSessionId, signal) {
62
+ signal?.throwIfAborted();
63
+ const before = request.before === undefined ? Infinity : Number(request.before);
64
+ if (before !== Infinity && (!Number.isSafeInteger(before) || before < 0))
65
+ throw new Error('before must be a ledger cursor');
66
+ const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
67
+ let files = [];
68
+ try {
69
+ files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort().reverse();
70
+ }
71
+ catch { }
72
+ const events = [];
73
+ let newest;
74
+ let hasOlder = false;
75
+ const observation = snapshot;
76
+ const barrier = observation.ledgerBarrier;
77
+ // Current supervisors capture these sanitized inventories together with the
78
+ // ledger barrier. The browser uses them directly, including authoritative
79
+ // empty arrays. Looking for their old ledger events would scan the entire
80
+ // generation on every open. Legacy/incomplete snapshots still need context.
81
+ const complete = !snapshot.observationFailure && Number.isSafeInteger(barrier) && barrier >= 0
82
+ && Number.isSafeInteger(observation.stateRevision) && observation.stateRevision >= 0;
83
+ const needsCapabilities = !complete || !Array.isArray(observation.commands);
84
+ const pending = new Set(complete && Array.isArray(observation.pendingPermissions)
85
+ && snapshot.pendingPermissionIds.every(id => observation.pendingPermissions.some(permission => permission.permissionId === id))
86
+ ? [] : snapshot.pendingPermissionIds);
87
+ let capabilities;
88
+ const context = [];
89
+ // Context only accompanies the newest page; older pages stop as soon as they are full.
90
+ const wantsContext = () => before === Infinity && ((needsCapabilities && !capabilities) || pending.size > 0);
91
+ const generation = `"sessionGeneration":${JSON.stringify(snapshot.sessionGeneration)}`;
92
+ for (const file of files) {
93
+ const more = await readLinesBackward(join(stateDir, '.conversation', file), line => {
94
+ if (hasOlder) {
95
+ // The page is full; only the current generation's context kinds matter. Generations are
96
+ // appended in order, so the first line of an older one ends the search.
97
+ if (!line.includes(generation))
98
+ return !line.includes('"sessionGeneration":') && wantsContext();
99
+ if (!line.includes('"capabilities.updated"') && !line.includes('"permission.requested"'))
100
+ return wantsContext();
101
+ }
102
+ let event;
103
+ try {
104
+ event = JSON.parse(line);
105
+ }
106
+ catch {
107
+ return true;
108
+ }
109
+ const seq = Number(event.seq);
110
+ if (!Number.isSafeInteger(seq))
111
+ return true;
112
+ if (barrier !== undefined && seq > barrier)
113
+ return true;
114
+ newest ??= seq;
115
+ if (seq < before && conversationEventVisible(event, snapshot.sessionGeneration, resumedSessionId)) {
116
+ const inPage = !hasOlder && events.length < limit;
117
+ if (event.sessionGeneration === snapshot.sessionGeneration) {
118
+ if (event.kind === 'capabilities.updated' && !capabilities) {
119
+ capabilities = event;
120
+ if (!inPage)
121
+ context.push(event);
122
+ }
123
+ if (event.kind === 'permission.requested' && event.permissionId && pending.delete(event.permissionId) && !inPage)
124
+ context.push(event);
125
+ }
126
+ if (inPage)
127
+ events.push(event);
128
+ else
129
+ hasOlder = true;
130
+ }
131
+ return !hasOlder || wantsContext();
132
+ }, signal);
133
+ if (!more)
134
+ break;
135
+ }
136
+ events.reverse();
137
+ context.reverse();
138
+ return { events, snapshot, hasOlder, context, hasMore: false,
139
+ ...(events.length ? { olderCursor: String(events[0].seq) } : {}),
140
+ ...(before === Infinity && (barrier !== undefined || newest !== undefined) ? { nextCursor: String(barrier ?? newest) } : {}) };
141
+ }
142
+ /** Read the existing ACP session ledger without opening its writer/recovery store. */
143
+ export async function resumedConversationPage(stateDir, request, live, expectedSessionId) {
144
+ let sessionId;
145
+ let files;
146
+ try {
147
+ sessionId = (await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim();
148
+ files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort();
149
+ }
150
+ catch {
151
+ return live;
152
+ }
153
+ if (!sessionId || sessionId !== expectedSessionId || !files.length)
154
+ return live;
155
+ const after = Number(request.after ?? 0);
156
+ if (!Number.isSafeInteger(after) || after < 0)
157
+ return live;
158
+ const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
159
+ const events = [];
160
+ let next = String(after);
161
+ let first;
162
+ let hasMore = false;
163
+ scan: for (const file of files) {
164
+ // Forward polling asks for the newest few events: skip whole segments that end at or before the cursor.
165
+ const end = await lastSeq(join(stateDir, '.conversation', file));
166
+ if (end !== undefined && end <= after)
167
+ continue;
168
+ const input = createReadStream(join(stateDir, '.conversation', file), { encoding: 'utf8' });
169
+ const lines = createInterface({ input, crlfDelay: Infinity });
170
+ try {
171
+ for await (const line of lines) {
172
+ let event;
173
+ try {
174
+ event = JSON.parse(line);
175
+ }
176
+ catch {
177
+ continue;
178
+ }
179
+ const cursor = Number(event.seq);
180
+ if (!Number.isSafeInteger(cursor) || cursor <= after)
181
+ continue;
182
+ const historical = ['file.attached', 'fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'].includes(event.kind);
183
+ const visible = event.source !== 'agent_replay' && (event.sessionGeneration === live.snapshot.sessionGeneration || (historical && event.acpSessionId === sessionId));
184
+ if (!visible) {
185
+ next = String(cursor);
186
+ continue;
187
+ }
188
+ if (events.length === limit) {
189
+ hasMore = true;
190
+ break scan;
191
+ }
192
+ first ??= String(cursor);
193
+ events.push(event);
194
+ next = String(cursor);
195
+ }
196
+ }
197
+ finally {
198
+ lines.close();
199
+ input.destroy();
200
+ }
201
+ }
202
+ // If a fresh/resumed session changed during the read, use the live page only.
203
+ if ((await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim() !== sessionId)
204
+ return live;
205
+ return { ...live, events, firstAvailableCursor: first, nextCursor: next, hasMore };
206
+ }
207
+ /** Download authorization requires an actual published attachment, never an orphan copy. */
208
+ export async function conversationHasAttachment(stateDir, attachment) {
209
+ const dir = join(stateDir, '.conversation');
210
+ const names = (await readdir(dir)).filter(n => /^events-\d+\.jsonl$/.test(n)).sort().reverse();
211
+ let found = false;
212
+ for (const name of names) {
213
+ await readLinesBackward(join(dir, name), line => {
214
+ try {
215
+ const e = JSON.parse(line);
216
+ const a = e.payload.attachment;
217
+ if (e.kind === 'file.attached' && e.source === 'agent' && a?.id === attachment.id
218
+ && e.sessionGeneration === attachment.sessionGeneration && e.acpSessionId === attachment.acpSessionId
219
+ && e.turnId === attachment.turnId && JSON.stringify(a) === JSON.stringify(attachment))
220
+ found = true;
221
+ }
222
+ catch { /* unreadable lines cannot authorize a download */ }
223
+ return !found;
224
+ });
225
+ if (found)
226
+ break;
227
+ }
228
+ return found;
229
+ }
230
+ /** Passive, bounded last assistant text for directory previews. Never opens the
231
+ * ledger writer/recovery path or acquires a session/controller. An incomplete
232
+ * newest append is not committed yet; completed malformed records still fail. */
233
+ export async function conversationPreview(stateDir, signal) {
234
+ signal.throwIfAborted();
235
+ const dir = join(stateDir, '.conversation');
236
+ let files;
237
+ try {
238
+ files = (await readdir(dir)).filter(name => /^events-\d{6}\.jsonl$/.test(name)).sort().reverse();
239
+ }
240
+ catch (error) {
241
+ if (error.code === 'ENOENT')
242
+ return { text: 'No recent agent output', at: '' };
243
+ throw error;
244
+ }
245
+ const events = [];
246
+ for (const name of files) {
247
+ signal.throwIfAborted();
248
+ const file = await open(join(dir, name), 'r');
249
+ try {
250
+ const size = (await file.stat()).size, length = Math.min(size, 2 * 1024 * 1024), buffer = Buffer.alloc(length);
251
+ await file.read(buffer, 0, length, size - length);
252
+ const completeEnd = buffer.lastIndexOf(10) + 1;
253
+ // Only the newest segment can have an in-flight append. If its tail
254
+ // fills the bounded window, we cannot establish the previous preview.
255
+ if (completeEnd < length && (name !== files[0] || (size > length && completeEnd === 0)))
256
+ throw Error('preview ledger has partial record');
257
+ const lines = buffer.toString('utf8', 0, completeEnd).split('\n');
258
+ lines.pop();
259
+ if (size > length)
260
+ lines.shift();
261
+ for (let i = lines.length - 1; i >= 0 && events.length < 200; i--) {
262
+ const event = JSON.parse(lines[i]);
263
+ if (event.schemaVersion !== 1 || !Number.isSafeInteger(event.seq))
264
+ throw Error('preview ledger invalid');
265
+ events.push(event);
266
+ }
267
+ }
268
+ finally {
269
+ await file.close();
270
+ }
271
+ if (events.length >= 200)
272
+ break;
273
+ }
274
+ signal.throwIfAborted();
275
+ const assistant = (event) => ['message.chunk', 'message.replace'].includes(event.kind) && event.payload?.role === 'assistant' && typeof event.payload?.content?.type === 'string';
276
+ const end = events.findIndex(assistant);
277
+ if (end < 0)
278
+ return { text: 'No recent agent output', at: '' };
279
+ const last = events[end], parts = [];
280
+ for (let i = end; i < events.length; i++) {
281
+ const event = events[i];
282
+ if (last.messageId !== undefined) {
283
+ if (!assistant(event) || event.messageId !== last.messageId)
284
+ continue;
285
+ }
286
+ else if (!assistant(event) || event.messageId !== undefined)
287
+ break;
288
+ const content = event.payload.content;
289
+ parts.unshift(content.type === 'text' ? content.text : `[${content.type}]`);
290
+ if (event.kind === 'message.replace')
291
+ break;
292
+ }
293
+ return { text: parts.join('').replace(/\s+/g, ' ').trim().slice(0, 300) || 'No recent agent output', at: last.at ?? '' };
294
+ }
@@ -0,0 +1,62 @@
1
+ import { type FSWatcher } from 'node:fs';
2
+ import { type SessionBackendId } from '../config.js';
3
+ export interface ConversationRoleState {
4
+ roleId: string;
5
+ /** Connection-owned ordering; never a session stateRevision. */
6
+ roleRevision: number;
7
+ roleLifetime: string;
8
+ availability: 'available' | 'unavailable' | 'missing';
9
+ fallbackHarness?: string;
10
+ backend?: SessionBackendId;
11
+ stateDir?: string;
12
+ health: 'healthy' | 'unsupported' | 'failed';
13
+ failure?: 'watch-unsupported' | 'watch-error' | 'watch-closed' | 'role-read-failed' | 'observer-disposed';
14
+ }
15
+ export interface ConversationRoleObservation {
16
+ snapshot(): ConversationRoleState;
17
+ subscribe(listener: (state: ConversationRoleState) => void): {
18
+ snapshot: ConversationRoleState;
19
+ unsubscribe(): void;
20
+ };
21
+ dispose(): void;
22
+ }
23
+ export type ObserveConversationRole = (listener: (state: ConversationRoleState) => void) => ConversationRoleObservation;
24
+ export interface ConversationRoleObserverOptions {
25
+ roleId: string;
26
+ configPath?: string;
27
+ permanentRoot?: string;
28
+ temporaryRoot?: string;
29
+ watch?: (path: string, listener: (event: string, filename: string | null) => void) => Pick<FSWatcher, 'on' | 'close'>;
30
+ subscribeMutations?: (listener: () => void) => () => void;
31
+ }
32
+ /** Native directory notifications are assumed reliable on qualified host/mount setups.
33
+ * No periodic reads or claim of detecting every silently dropped filesystem event.
34
+ */
35
+ export declare class ConversationRoleObserver implements ConversationRoleObservation {
36
+ private readonly options;
37
+ private readonly listener;
38
+ private state;
39
+ private watchers;
40
+ private detach?;
41
+ private disposed;
42
+ private readonly listeners;
43
+ private queued;
44
+ private fingerprint?;
45
+ private readonly configPath;
46
+ private readonly permanent;
47
+ private readonly temporary;
48
+ constructor(options: ConversationRoleObserverOptions, listener: (state: ConversationRoleState) => void);
49
+ snapshot(): ConversationRoleState;
50
+ subscribe(listener: (state: ConversationRoleState) => void): {
51
+ snapshot: ConversationRoleState;
52
+ unsubscribe: () => void;
53
+ };
54
+ private publish;
55
+ dispose(): void;
56
+ private release;
57
+ private fail;
58
+ private schedule;
59
+ private refresh;
60
+ private install;
61
+ private watchFailure;
62
+ }
@@ -0,0 +1,206 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { lstatSync, readFileSync, realpathSync, watch } from 'node:fs';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { parse } from 'yaml';
5
+ import { loadConfig, ROLE_NAME_RE, splitRootFor } from '../config.js';
6
+ import { agentsRoot, defaultConfigPath, tmpRoot } from '../paths.js';
7
+ import { controlSocketPath } from '../session/control.js';
8
+ function identity(path) {
9
+ try {
10
+ const s = lstatSync(path);
11
+ return `${s.dev}:${s.ino}:${s.birthtimeMs}`;
12
+ }
13
+ catch (error) {
14
+ if (error.code === 'ENOENT')
15
+ return;
16
+ throw error;
17
+ }
18
+ }
19
+ /** Native directory notifications are assumed reliable on qualified host/mount setups.
20
+ * No periodic reads or claim of detecting every silently dropped filesystem event.
21
+ */
22
+ export class ConversationRoleObserver {
23
+ options;
24
+ listener;
25
+ state;
26
+ watchers = new Map();
27
+ detach;
28
+ disposed = false;
29
+ listeners = new Set();
30
+ queued = false;
31
+ fingerprint;
32
+ configPath;
33
+ permanent;
34
+ temporary;
35
+ constructor(options, listener) {
36
+ this.options = options;
37
+ this.listener = listener;
38
+ if (!ROLE_NAME_RE.test(options.roleId))
39
+ throw new Error('invalid role id');
40
+ this.configPath = resolve(options.configPath ?? defaultConfigPath());
41
+ this.permanent = resolve(options.permanentRoot ?? agentsRoot(), options.roleId);
42
+ this.temporary = resolve(options.temporaryRoot ?? tmpRoot(), options.roleId);
43
+ this.state = { roleId: options.roleId, roleRevision: 0, roleLifetime: randomUUID(), availability: 'missing', health: 'healthy' };
44
+ this.refresh();
45
+ if (this.state.health === 'healthy')
46
+ this.detach = options.subscribeMutations?.(() => this.schedule());
47
+ }
48
+ snapshot() {
49
+ return this.disposed ? { ...this.state, health: 'failed', failure: 'observer-disposed' } : { ...this.state };
50
+ }
51
+ subscribe(listener) {
52
+ if (!this.disposed)
53
+ this.listeners.add(listener);
54
+ return { snapshot: this.snapshot(), unsubscribe: () => { this.listeners.delete(listener); } };
55
+ }
56
+ publish() {
57
+ this.listener(this.snapshot());
58
+ for (const listener of this.listeners)
59
+ listener(this.snapshot());
60
+ }
61
+ dispose() {
62
+ if (this.disposed)
63
+ return;
64
+ this.disposed = true;
65
+ this.release();
66
+ this.listeners.clear();
67
+ }
68
+ release() {
69
+ const retired = [...this.watchers.values()];
70
+ // Retire before close: native close events may arrive synchronously or later.
71
+ this.watchers.clear();
72
+ for (const watcher of retired)
73
+ watcher.handle.close();
74
+ this.detach?.();
75
+ this.detach = undefined;
76
+ }
77
+ fail(failure) {
78
+ if (this.disposed || this.state.health !== 'healthy')
79
+ return;
80
+ this.state = { ...this.state, roleRevision: this.state.roleRevision + 1, health: failure === 'watch-unsupported' ? 'unsupported' : 'failed', failure };
81
+ this.release();
82
+ this.publish();
83
+ }
84
+ schedule() {
85
+ if (this.queued || this.disposed || this.state.health !== 'healthy')
86
+ return;
87
+ this.queued = true;
88
+ queueMicrotask(() => { this.queued = false; if (!this.disposed && this.state.health === 'healthy')
89
+ this.refresh(); });
90
+ }
91
+ refresh() {
92
+ try {
93
+ // Install parents before reading, including absent children, so atomic rename
94
+ // and recreation cannot strand us watching an unlinked inode.
95
+ this.install();
96
+ if (this.state.health !== 'healthy')
97
+ return;
98
+ let configured;
99
+ const split = splitRootFor(this.configPath);
100
+ if (identity(this.configPath) && identity(split) && identity(join(split, 'agents')))
101
+ configured = loadConfig(this.configPath).roles.find(role => role.name === this.options.roleId);
102
+ const permanentId = identity(this.permanent), temporaryId = identity(this.temporary);
103
+ const stateDir = temporaryId ? realpathSync(this.temporary) : permanentId ? realpathSync(this.permanent) : undefined;
104
+ let temporary;
105
+ if (!configured && temporaryId && identity(join(this.temporary, 'role.yaml'))) {
106
+ const raw = parse(readFileSync(join(this.temporary, 'role.yaml'), 'utf8'));
107
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)
108
+ || (raw.session !== undefined && raw.session !== 'acp' && raw.session !== 'codex-app-server'))
109
+ throw new Error('invalid temporary role snapshot');
110
+ temporary = { session: raw.session ?? 'acp', harness: typeof raw.harness === 'string' ? raw.harness : 'claude-code' };
111
+ }
112
+ const backend = configured?.session ?? temporary?.session;
113
+ const fallbackHarness = configured?.harness ?? temporary?.harness;
114
+ const socket = stateDir ? identity(controlSocketPath(stateDir)) : undefined;
115
+ const tokenPath = stateDir && join(stateDir, '.control-token');
116
+ const token = tokenPath && identity(tokenPath) ? createHash('sha256').update(readFileSync(tokenPath)).digest('hex') : undefined;
117
+ const exists = Boolean(configured || permanentId || temporaryId);
118
+ const availability = !exists ? 'missing' : stateDir && socket && token && backend ? 'available' : 'unavailable';
119
+ const fingerprint = JSON.stringify([Boolean(configured), permanentId, temporaryId, backend, socket, token]);
120
+ const lifetime = this.fingerprint === fingerprint ? this.state.roleLifetime : randomUUID();
121
+ this.fingerprint = fingerprint;
122
+ const next = { roleId: this.options.roleId, roleRevision: this.state.roleRevision, roleLifetime: lifetime,
123
+ availability, fallbackHarness, backend, stateDir, health: 'healthy' };
124
+ if (JSON.stringify(next) !== JSON.stringify(this.state)) {
125
+ this.state = { ...next, roleRevision: this.state.roleRevision + 1 };
126
+ this.publish();
127
+ }
128
+ }
129
+ catch {
130
+ this.fail('role-read-failed');
131
+ }
132
+ }
133
+ install() {
134
+ const wanted = new Map();
135
+ const add = (dir, child) => {
136
+ if (child === undefined)
137
+ wanted.set(dir, null);
138
+ else if (wanted.get(dir) !== null) {
139
+ const names = wanted.get(dir) ?? new Set();
140
+ names.add(child);
141
+ wanted.set(dir, names);
142
+ }
143
+ };
144
+ const parents = (path, boundary) => {
145
+ while (path !== boundary && dirname(path) !== path) {
146
+ add(dirname(path), basename(path));
147
+ path = dirname(path);
148
+ }
149
+ };
150
+ const configRoot = splitRootFor(this.configPath);
151
+ const configBoundary = dirname(dirname(this.configPath));
152
+ parents(this.configPath, configBoundary);
153
+ for (const dir of [configRoot, ...['agents', 'brains', 'roles', 'agent_templates'].map(kind => join(configRoot, kind))]) {
154
+ parents(dir, configBoundary);
155
+ add(dir);
156
+ }
157
+ for (const dir of [this.permanent, this.temporary]) {
158
+ parents(dir, dirname(dirname(dirname(dir))));
159
+ for (const name of ['role.yaml', '.control-token', '.control.sock'])
160
+ add(dir, name);
161
+ const socket = controlSocketPath(dir);
162
+ parents(socket, dirname(dirname(socket)));
163
+ }
164
+ for (const [dir, current] of this.watchers) {
165
+ if (!wanted.has(dir) || identity(dir) !== current.identity) {
166
+ this.watchers.delete(dir);
167
+ current.handle.close();
168
+ }
169
+ }
170
+ for (const [dir, names] of wanted) {
171
+ const id = identity(dir);
172
+ if (!id)
173
+ continue;
174
+ const existing = this.watchers.get(dir);
175
+ if (existing) {
176
+ existing.names = names;
177
+ continue;
178
+ }
179
+ try {
180
+ const handle = (this.options.watch ?? ((path, listener) => watch(path, { persistent: false }, listener)))(dir, (_event, filename) => {
181
+ const entry = this.watchers.get(dir);
182
+ if (filename === null || !entry?.names || entry.names.has(String(filename)) || String(filename) === basename(dir))
183
+ this.schedule();
184
+ });
185
+ this.watchers.set(dir, { identity: id, names, handle });
186
+ handle.on('error', (error) => {
187
+ if (this.watchers.get(dir)?.handle === handle)
188
+ this.watchFailure(error);
189
+ });
190
+ handle.on('close', () => {
191
+ if (this.watchers.get(dir)?.handle !== handle)
192
+ return;
193
+ this.watchers.delete(dir);
194
+ this.fail('watch-closed');
195
+ });
196
+ }
197
+ catch (error) {
198
+ this.watchFailure(error);
199
+ return;
200
+ }
201
+ }
202
+ }
203
+ watchFailure(error) {
204
+ this.fail(['ENOSYS', 'ENOTSUP', 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM'].includes(error.code ?? '') ? 'watch-unsupported' : 'watch-error');
205
+ }
206
+ }
@@ -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, strict?: 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() {
79
- const roles = (await this.options.repository.list())
80
- .filter(role => role.configured && role.lifetime === 'permanent');
78
+ async list(includeTemporary = false, strict = false) {
79
+ const roles = (await this.options.repository.list(strict))
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) };