@ours.network/fleet 1.2.0-nightly.5 → 1.2.0-nightly.50

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 (330) hide show
  1. package/README.md +263 -53
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +9 -0
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/harness.js +8 -0
  8. package/dist/agent-ours/runtime.d.ts +6 -0
  9. package/dist/agent-ours/runtime.js +36 -0
  10. package/dist/agent-ours/service.js +33 -13
  11. package/dist/application/codex-models.d.ts +15 -0
  12. package/dist/application/codex-models.js +56 -0
  13. package/dist/application/conversation-history.d.ts +25 -0
  14. package/dist/application/conversation-history.js +189 -0
  15. package/dist/application/fleet-query-service.d.ts +1 -1
  16. package/dist/application/fleet-query-service.js +2 -2
  17. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  18. package/dist/application/legacy-supervisor-identity.js +43 -0
  19. package/dist/application/model-catalog.d.ts +1 -1
  20. package/dist/application/model-catalog.js +5 -2
  21. package/dist/application/preset-provenance.d.ts +19 -0
  22. package/dist/application/preset-provenance.js +59 -0
  23. package/dist/application/role-creation-service.js +1 -1
  24. package/dist/application/role-removal-service.js +2 -0
  25. package/dist/application/role-repository.js +2 -2
  26. package/dist/application/room-layout-definitions.d.ts +45 -0
  27. package/dist/application/room-layout-definitions.js +130 -0
  28. package/dist/application/session-control.d.ts +30 -0
  29. package/dist/application/session-control.js +72 -3
  30. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  31. package/dist/application/supervisor-ours-tools.js +113 -0
  32. package/dist/application/task-layouts.d.ts +91 -0
  33. package/dist/application/task-layouts.js +249 -0
  34. package/dist/application/task-room-service.d.ts +99 -3
  35. package/dist/application/task-room-service.js +296 -31
  36. package/dist/atomic-file.d.ts +5 -0
  37. package/dist/atomic-file.js +62 -1
  38. package/dist/briefing.d.ts +2 -0
  39. package/dist/briefing.js +8 -1
  40. package/dist/build-info.json +7 -4
  41. package/dist/capabilities.d.ts +6 -1
  42. package/dist/capabilities.js +8 -0
  43. package/dist/cli.js +376 -34
  44. package/dist/client-profile.d.ts +5 -17
  45. package/dist/client-profile.js +4 -127
  46. package/dist/config.d.ts +28 -0
  47. package/dist/config.js +41 -6
  48. package/dist/creation.js +2 -2
  49. package/dist/daemon-recovery.d.ts +1 -8
  50. package/dist/daemon-recovery.js +8 -113
  51. package/dist/docs.d.ts +3 -3
  52. package/dist/docs.js +126 -22
  53. package/dist/doctor.d.ts +1 -1
  54. package/dist/doctor.js +55 -60
  55. package/dist/erased-resources.d.ts +9 -0
  56. package/dist/erased-resources.js +87 -0
  57. package/dist/fleet-command-audit.d.ts +7 -0
  58. package/dist/fleet-command-audit.js +26 -9
  59. package/dist/harness/claude-code.js +9 -1
  60. package/dist/harness/claude-runtime.d.ts +12 -0
  61. package/dist/harness/claude-runtime.js +43 -0
  62. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  63. package/dist/harness/codex-app-server-proxy.js +41 -5
  64. package/dist/harness/codex-reasoning.d.ts +5 -0
  65. package/dist/harness/codex-reasoning.js +12 -0
  66. package/dist/harness/codex-runtime.d.ts +11 -1
  67. package/dist/harness/codex-runtime.js +31 -4
  68. package/dist/harness/codex.d.ts +3 -2
  69. package/dist/harness/codex.js +20 -11
  70. package/dist/init-wizard.d.ts +2 -0
  71. package/dist/init-wizard.js +38 -19
  72. package/dist/isolation/policy.js +2 -0
  73. package/dist/isolation/types.d.ts +5 -0
  74. package/dist/managed-cli.d.ts +253 -0
  75. package/dist/managed-cli.js +923 -0
  76. package/dist/minimal-setup.d.ts +2 -0
  77. package/dist/minimal-setup.js +44 -0
  78. package/dist/monitor.d.ts +1 -34
  79. package/dist/monitor.js +24 -151
  80. package/dist/notifications/fleet-producer.d.ts +19 -0
  81. package/dist/notifications/fleet-producer.js +208 -0
  82. package/dist/notifications/outbox.d.ts +46 -0
  83. package/dist/notifications/outbox.js +158 -0
  84. package/dist/notifications/target-cleanup.d.ts +8 -0
  85. package/dist/notifications/target-cleanup.js +41 -0
  86. package/dist/ops.js +5 -1
  87. package/dist/owner-channel/channel.d.ts +3 -0
  88. package/dist/owner-channel/channel.js +34 -6
  89. package/dist/owner-channel/commands.d.ts +4 -0
  90. package/dist/owner-channel/commands.js +8 -0
  91. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  92. package/dist/owner-channel/ours-client.js +2 -17
  93. package/dist/paths.d.ts +2 -0
  94. package/dist/paths.js +2 -0
  95. package/dist/preset-migration.js +25 -0
  96. package/dist/resolved-plan.js +1 -0
  97. package/dist/rooms-tasks/add-member.d.ts +48 -0
  98. package/dist/rooms-tasks/add-member.js +83 -0
  99. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  100. package/dist/rooms-tasks/archived-absence.js +48 -0
  101. package/dist/rooms-tasks/cli.js +34 -5
  102. package/dist/rooms-tasks/close.d.ts +5 -2
  103. package/dist/rooms-tasks/close.js +208 -28
  104. package/dist/rooms-tasks/config.d.ts +1 -1
  105. package/dist/rooms-tasks/config.js +11 -4
  106. package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
  107. package/dist/rooms-tasks/cowork-adapter.js +8 -121
  108. package/dist/rooms-tasks/deletion.d.ts +1 -1
  109. package/dist/rooms-tasks/deletion.js +74 -5
  110. package/dist/rooms-tasks/erasure.d.ts +6 -0
  111. package/dist/rooms-tasks/erasure.js +169 -0
  112. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  113. package/dist/rooms-tasks/external-worker.js +3 -4
  114. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  115. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  116. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  117. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  118. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  119. package/dist/rooms-tasks/layout-cli.js +107 -0
  120. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  121. package/dist/rooms-tasks/layout-config.js +89 -0
  122. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  123. package/dist/rooms-tasks/layout-control.js +52 -0
  124. package/dist/rooms-tasks/layout-service.d.ts +42 -0
  125. package/dist/rooms-tasks/layout-service.js +185 -0
  126. package/dist/rooms-tasks/layout.d.ts +114 -0
  127. package/dist/rooms-tasks/layout.js +250 -0
  128. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  129. package/dist/rooms-tasks/live-readiness.js +85 -0
  130. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  131. package/dist/rooms-tasks/member-startup.js +2 -0
  132. package/dist/rooms-tasks/provision.d.ts +14 -0
  133. package/dist/rooms-tasks/provision.js +173 -46
  134. package/dist/rooms-tasks/room-state.d.ts +4 -2
  135. package/dist/rooms-tasks/room-state.js +53 -8
  136. package/dist/rooms-tasks/task-state.d.ts +42 -7
  137. package/dist/rooms-tasks/task-state.js +153 -23
  138. package/dist/rooms-tasks/types.d.ts +28 -0
  139. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  140. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  141. package/dist/rooms-tasks/workspace.d.ts +26 -0
  142. package/dist/rooms-tasks/workspace.js +195 -0
  143. package/dist/runner.d.ts +19 -3
  144. package/dist/runner.js +128 -45
  145. package/dist/session/acp.d.ts +9 -1
  146. package/dist/session/acp.js +31 -6
  147. package/dist/session/arbiter.d.ts +5 -0
  148. package/dist/session/arbiter.js +28 -2
  149. package/dist/session/codex-app-server.d.ts +5 -0
  150. package/dist/session/codex-app-server.js +36 -3
  151. package/dist/session/control.d.ts +12 -1
  152. package/dist/session/control.js +68 -3
  153. package/dist/session/conversation-normalizer.d.ts +2 -1
  154. package/dist/session/conversation-normalizer.js +1 -1
  155. package/dist/session/conversation-store.d.ts +1 -1
  156. package/dist/session/conversation-store.js +7 -11
  157. package/dist/session/conversation-types.d.ts +7 -3
  158. package/dist/session/task-notice.d.ts +14 -0
  159. package/dist/session/task-notice.js +13 -0
  160. package/dist/session/types.d.ts +6 -0
  161. package/dist/setup-tunnel-preflight.d.ts +34 -0
  162. package/dist/setup-tunnel-preflight.js +62 -0
  163. package/dist/socket-path.d.ts +2 -0
  164. package/dist/socket-path.js +22 -0
  165. package/dist/spawn.js +8 -1
  166. package/dist/startup-readiness.d.ts +13 -0
  167. package/dist/startup-readiness.js +38 -0
  168. package/dist/subscriptions/cli.d.ts +91 -0
  169. package/dist/subscriptions/cli.js +223 -0
  170. package/dist/subscriptions/launch.d.ts +37 -0
  171. package/dist/subscriptions/launch.js +93 -0
  172. package/dist/subscriptions/login.d.ts +75 -0
  173. package/dist/subscriptions/login.js +182 -0
  174. package/dist/subscriptions/observed.d.ts +7 -0
  175. package/dist/subscriptions/observed.js +44 -0
  176. package/dist/subscriptions/probe.d.ts +10 -0
  177. package/dist/subscriptions/probe.js +73 -0
  178. package/dist/subscriptions/service.d.ts +102 -0
  179. package/dist/subscriptions/service.js +364 -0
  180. package/dist/subscriptions/store.d.ts +71 -0
  181. package/dist/subscriptions/store.js +140 -0
  182. package/dist/supervisor/launchd.js +2 -1
  183. package/dist/supervisor/systemd.js +9 -23
  184. package/dist/temp-idle.d.ts +17 -0
  185. package/dist/temp-idle.js +42 -0
  186. package/dist/temp-lifecycle.d.ts +6 -1
  187. package/dist/temp-lifecycle.js +29 -13
  188. package/dist/tunnel-setup-completion.d.ts +7 -0
  189. package/dist/tunnel-setup-completion.js +13 -0
  190. package/dist/tunnel-setup-input.d.ts +19 -0
  191. package/dist/tunnel-setup-input.js +124 -0
  192. package/dist/web/agent-attachments.d.ts +29 -0
  193. package/dist/web/agent-attachments.js +162 -0
  194. package/dist/web/auth.d.ts +29 -1
  195. package/dist/web/auth.js +82 -7
  196. package/dist/web/available-port.d.ts +2 -0
  197. package/dist/web/available-port.js +23 -0
  198. package/dist/web/control.d.ts +3 -3
  199. package/dist/web/control.js +10 -8
  200. package/dist/web/fleet-config-service.d.ts +4 -1
  201. package/dist/web/fleet-config-service.js +46 -15
  202. package/dist/web/prefix-gateway.d.ts +32 -0
  203. package/dist/web/prefix-gateway.js +234 -0
  204. package/dist/web/runtime.d.ts +3 -1
  205. package/dist/web/runtime.js +116 -31
  206. package/dist/web/server.d.ts +16 -0
  207. package/dist/web/server.js +588 -53
  208. package/dist/web/service.d.ts +11 -2
  209. package/dist/web/service.js +30 -4
  210. package/dist/web/workspace-devices.d.ts +36 -0
  211. package/dist/web/workspace-devices.js +101 -0
  212. package/dist/web/workspace-tunnel.d.ts +1 -0
  213. package/dist/web/workspace-tunnel.js +34 -0
  214. package/dist/workspace-enrollment.d.ts +82 -0
  215. package/dist/workspace-enrollment.js +566 -0
  216. package/dist/workspace-replacement.d.ts +66 -0
  217. package/dist/workspace-replacement.js +220 -0
  218. package/docs/live-voice.md +13 -0
  219. package/docs/room-layouts.md +227 -0
  220. package/docs/task-workspaces.md +130 -0
  221. package/docs/validation/managed-cli-permissions.md +421 -0
  222. package/examples/room-layouts/fresh.yaml +27 -0
  223. package/examples/room-layouts/scoped.yaml +21 -0
  224. package/examples/room-layouts/shared.yaml +19 -0
  225. package/package.json +15 -18
  226. package/presets/README.md +78 -0
  227. package/presets/brain-catalog.json +8 -2
  228. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  229. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  230. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  231. package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
  232. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  233. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  234. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  235. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  236. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  237. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  238. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  239. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  240. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  241. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  242. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  243. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  244. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  245. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  246. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  247. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  248. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  249. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  250. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  251. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  252. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  253. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  254. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  255. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  256. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  257. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  258. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  259. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  260. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  261. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  262. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  263. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  264. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  265. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  266. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  267. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  268. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  269. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  270. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  271. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  272. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  273. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  274. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  275. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  276. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  277. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  278. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  279. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  280. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  281. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  282. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  283. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  284. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  285. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  286. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  287. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  288. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  289. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  290. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  291. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  292. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  293. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  294. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  295. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  296. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  297. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  298. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  299. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  300. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  301. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  302. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  303. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  304. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  305. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  306. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  307. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  308. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  309. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  310. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  311. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  312. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  313. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  314. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  315. package/presets/fleet/roles/Coordinator.yaml +34 -11
  316. package/presets/fleet/roles/Engineer.yaml +40 -0
  317. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  318. package/presets/fleet/room_layouts/pair.yaml +24 -0
  319. package/presets/fleet/room_layouts/single.yaml +17 -0
  320. package/presets/fleet/room_layouts/team.yaml +30 -0
  321. package/presets/fleet/room_templates/engineering.yaml +19 -0
  322. package/presets/manifest.json +2 -2
  323. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  324. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  325. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  326. package/dist/web-app/icons/ours-fleet.svg +0 -4
  327. package/dist/web-app/index.html +0 -17
  328. package/dist/web-app/manifest.webmanifest +0 -15
  329. package/dist/web-app/offline.html +0 -18
  330. package/dist/web-app/sw.js +0 -51
@@ -1,3 +1,4 @@
1
+ import { socketPath as privateSocketPath } from '../socket-path.js';
1
2
  import { randomBytes, randomUUID } from 'node:crypto';
2
3
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
3
4
  import { join } from 'node:path';
@@ -11,19 +12,31 @@ import { atomicPrivateWrite, binderKey } from './state.js';
11
12
  import { AgentOursRuntime } from './runtime.js';
12
13
  import { startMcpEndpoint } from './mcp-endpoint.js';
13
14
  export const privateRuntimeRoot = () => join(stateRoot(), 'private-ours');
15
+ function roomSecretPath(role) {
16
+ const startup = role.roomMemberStartup;
17
+ return join(privateRuntimeRoot(), 'room-inputs', binderKey(startup.room_identity_cid, JSON.stringify([role.name, startup.invite_id])) + '.json');
18
+ }
19
+ function matchesRoomSecret(secret, role) {
20
+ const startup = role.roomMemberStartup;
21
+ return secret.room_id === startup.room_id && secret.room_identity_cid === startup.room_identity_cid
22
+ && secret.invite_id === startup.invite_id && secret.identity_name === role.identity
23
+ && secret.role === startup.role;
24
+ }
14
25
  /** Only trusted launch orchestration may write this descriptor, never the child. */
15
26
  export function storeRoomSecret(role) {
16
27
  const startup = role.roomMemberStartup;
17
28
  if (!startup?.invite)
18
29
  return;
30
+ if (startup.identity_name !== role.identity)
31
+ throw Error('ROOM_SECRET_MISMATCH');
19
32
  const root = join(privateRuntimeRoot(), 'room-inputs');
20
33
  mkdirSync(root, { recursive: true, mode: 0o700 });
21
- const path = join(root, binderKey(startup.room_identity_cid, role.name) + '.json');
34
+ // Failed attempts retain private evidence. A new invite must never overwrite
35
+ // or consume a previous attempt's descriptor.
36
+ const path = roomSecretPath(role);
22
37
  if (existsSync(path)) {
23
38
  const old = JSON.parse(readFileSync(path, 'utf8'));
24
- if (old.room_id !== startup.room_id ||
25
- old.invite_id !== startup.invite_id ||
26
- old.identity_name !== startup.identity_name)
39
+ if (!matchesRoomSecret(old, role) || old.invite !== startup.invite)
27
40
  throw Error('ROOM_SECRET_COLLISION');
28
41
  return;
29
42
  }
@@ -144,7 +157,9 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
144
157
  let room;
145
158
  if (role.roomMemberStartup) {
146
159
  const startup = role.roomMemberStartup;
147
- const path = join(root, 'room-inputs', binderKey(startup.room_identity_cid, role.name) + '.json');
160
+ const legacyPath = join(root, 'room-inputs', binderKey(startup.room_identity_cid, role.name) + '.json');
161
+ const currentPath = roomSecretPath(role);
162
+ const path = existsSync(currentPath) ? currentPath : legacyPath;
148
163
  const cowork = createCoworkAdapter();
149
164
  room = {
150
165
  id: startup.room_id,
@@ -153,9 +168,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
153
168
  action: startup.invite_id,
154
169
  redeem: async (attached) => {
155
170
  const secret = JSON.parse(readFileSync(path, 'utf8'));
156
- if (secret.room_id !== startup.room_id ||
157
- secret.invite_id !== startup.invite_id ||
158
- secret.identity_name !== role.identity)
171
+ if (!matchesRoomSecret(secret, role))
159
172
  throw Error('ROOM_SECRET_MISMATCH');
160
173
  return attached.addContact({ invite: secret.invite });
161
174
  },
@@ -195,7 +208,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
195
208
  await identities.add(role.identity);
196
209
  const bridgeDir = join(stateDir, '.ours-bridge');
197
210
  mkdirSync(bridgeDir, { recursive: true, mode: 0o700 });
198
- const capability = randomBytes(32).toString('hex'), socket = join(bridgeDir, `g${generation}.sock`);
211
+ const capability = randomBytes(32).toString('hex'), socket = privateSocketPath(join(bridgeDir, `g${generation}.sock`));
199
212
  const endpoint = await startMcpEndpoint({
200
213
  socket,
201
214
  capability,
@@ -207,7 +220,7 @@ export async function prepareManagedAgent(role, stateDir, temporary) {
207
220
  });
208
221
  partialEndpoint = endpoint;
209
222
  const descriptor = join(bridgeDir, 'descriptor.json');
210
- atomicPrivateWrite(descriptor, { socket, capability, generation });
223
+ atomicPrivateWrite(descriptor, { socket, capability, generation, role: role.name, identity: role.identity, cid: runtime.snapshot.cid });
211
224
  return {
212
225
  runtime,
213
226
  descriptor,
@@ -255,8 +268,10 @@ export async function preparePermanentAssignment(role) {
255
268
  endpoint: profile.endpoint,
256
269
  expectedInstanceId: profile.expectedInstanceId,
257
270
  credentialPath: profile.credentialPath,
258
- sessionMode: 'local',
259
- requiredCapabilities: ['local-pid-v1', 'root-first-identities-v1'],
271
+ sessionMode: 'external',
272
+ leaseToken: randomUUID(),
273
+ requiredCapabilities: ['external-sessions-v1', 'root-first-identities-v1'],
274
+ env: {},
260
275
  });
261
276
  try {
262
277
  const rows = await client.listIdentities(), row = rows.find((r) => r.name === role.identity);
@@ -283,7 +298,12 @@ export async function preparePermanentAssignment(role) {
283
298
  return 'verified';
284
299
  }
285
300
  finally {
286
- await client.close();
301
+ try {
302
+ await client.releaseLease();
303
+ }
304
+ finally {
305
+ await client.close();
306
+ }
287
307
  }
288
308
  }
289
309
  export function readRoomReadiness(roomCid, name) {
@@ -0,0 +1,15 @@
1
+ import { type OfferedModels } from '../subscriptions/cli.js';
2
+ export interface CodexModelDiscoveryDeps {
3
+ /** The Codex executable ACP sessions will run, and its version. */
4
+ runtime(): Promise<{
5
+ executable: string;
6
+ version: string;
7
+ }>;
8
+ ask(executable: string, env: NodeJS.ProcessEnv): Promise<OfferedModels>;
9
+ now(): number;
10
+ }
11
+ /**
12
+ * The models offered to the active Codex account by the runtime that sessions will actually use.
13
+ * An answer is reused only for the same executable, version, profile and sign-in.
14
+ */
15
+ export declare function createCodexModelDiscovery(overrides?: Partial<CodexModelDiscoveryDeps>): () => Promise<OfferedModels>;
@@ -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,25 @@
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): 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>;
@@ -0,0 +1,189 @@
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 = ['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) {
12
+ const file = await open(path, 'r');
13
+ try {
14
+ let position = (await file.stat()).size;
15
+ let carry = Buffer.alloc(0);
16
+ while (position > 0) {
17
+ const size = Math.min(256 * 1024, position);
18
+ position -= size;
19
+ const chunk = Buffer.alloc(size);
20
+ await file.read(chunk, 0, size, position);
21
+ let buffer = Buffer.concat([chunk, carry]);
22
+ let end = buffer.length;
23
+ for (let i = buffer.length - 1; i >= 0; i--) {
24
+ if (buffer[i] !== 10)
25
+ continue;
26
+ if (end > i + 1 && !visit(buffer.toString('utf8', i + 1, end)))
27
+ return false;
28
+ end = i;
29
+ }
30
+ carry = buffer.subarray(0, end);
31
+ }
32
+ if (carry.length && !visit(carry.toString('utf8')))
33
+ return false;
34
+ return true;
35
+ }
36
+ finally {
37
+ await file.close();
38
+ }
39
+ }
40
+ /** The last event cursor in a segment, read from its tail; undefined when none is readable. */
41
+ async function lastSeq(path) {
42
+ let seq;
43
+ await readLinesBackward(path, line => { try {
44
+ const value = Number(JSON.parse(line).seq);
45
+ if (Number.isSafeInteger(value)) {
46
+ seq = value;
47
+ return false;
48
+ }
49
+ }
50
+ catch { } return true; });
51
+ return seq;
52
+ }
53
+ /**
54
+ * The newest `limit` visible events before `before` (or the newest overall),
55
+ * read backwards from the ledger so opening a long session costs one page.
56
+ * `nextCursor` is the newest ledger position seen, for forward `after` paging.
57
+ */
58
+ export async function conversationTailPage(stateDir, request, snapshot, resumedSessionId) {
59
+ const before = request.before === undefined ? Infinity : Number(request.before);
60
+ if (before !== Infinity && (!Number.isSafeInteger(before) || before < 0))
61
+ throw new Error('before must be a ledger cursor');
62
+ const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
63
+ let files = [];
64
+ try {
65
+ files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort().reverse();
66
+ }
67
+ catch { }
68
+ const events = [];
69
+ let newest;
70
+ let hasOlder = false;
71
+ const pending = new Set(snapshot.pendingPermissionIds);
72
+ let capabilities;
73
+ const context = [];
74
+ // Context only accompanies the newest page; older pages stop as soon as they are full.
75
+ const wantsContext = () => before === Infinity && (!capabilities || pending.size > 0);
76
+ const generation = `"sessionGeneration":${JSON.stringify(snapshot.sessionGeneration)}`;
77
+ for (const file of files) {
78
+ const more = await readLinesBackward(join(stateDir, '.conversation', file), line => {
79
+ if (hasOlder) {
80
+ // The page is full; only the current generation's context kinds matter. Generations are
81
+ // appended in order, so the first line of an older one ends the search.
82
+ if (!line.includes(generation))
83
+ return !line.includes('"sessionGeneration":') && wantsContext();
84
+ if (!line.includes('"capabilities.updated"') && !line.includes('"permission.requested"'))
85
+ return wantsContext();
86
+ }
87
+ let event;
88
+ try {
89
+ event = JSON.parse(line);
90
+ }
91
+ catch {
92
+ return true;
93
+ }
94
+ const seq = Number(event.seq);
95
+ if (!Number.isSafeInteger(seq))
96
+ return true;
97
+ newest ??= seq;
98
+ if (seq < before && conversationEventVisible(event, snapshot.sessionGeneration, resumedSessionId)) {
99
+ const inPage = !hasOlder && events.length < limit;
100
+ if (event.sessionGeneration === snapshot.sessionGeneration) {
101
+ if (event.kind === 'capabilities.updated' && !capabilities) {
102
+ capabilities = event;
103
+ if (!inPage)
104
+ context.push(event);
105
+ }
106
+ if (event.kind === 'permission.requested' && event.permissionId && pending.delete(event.permissionId) && !inPage)
107
+ context.push(event);
108
+ }
109
+ if (inPage)
110
+ events.push(event);
111
+ else
112
+ hasOlder = true;
113
+ }
114
+ return !hasOlder || wantsContext();
115
+ });
116
+ if (!more)
117
+ break;
118
+ }
119
+ events.reverse();
120
+ context.reverse();
121
+ return { events, snapshot, hasOlder, context, hasMore: false,
122
+ ...(events.length ? { olderCursor: String(events[0].seq) } : {}),
123
+ ...(before === Infinity && newest !== undefined ? { nextCursor: String(newest) } : {}) };
124
+ }
125
+ /** Read the existing ACP session ledger without opening its writer/recovery store. */
126
+ export async function resumedConversationPage(stateDir, request, live, expectedSessionId) {
127
+ let sessionId;
128
+ let files;
129
+ try {
130
+ sessionId = (await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim();
131
+ files = (await readdir(join(stateDir, '.conversation'))).filter(n => /^events-\d{6}\.jsonl$/.test(n)).sort();
132
+ }
133
+ catch {
134
+ return live;
135
+ }
136
+ if (!sessionId || sessionId !== expectedSessionId || !files.length)
137
+ return live;
138
+ const after = Number(request.after ?? 0);
139
+ if (!Number.isSafeInteger(after) || after < 0)
140
+ return live;
141
+ const limit = Math.min(1000, Math.max(1, Number.isFinite(request.limit) ? Math.floor(request.limit) : 200));
142
+ const events = [];
143
+ let next = String(after);
144
+ let first;
145
+ let hasMore = false;
146
+ scan: for (const file of files) {
147
+ // Forward polling asks for the newest few events: skip whole segments that end at or before the cursor.
148
+ const end = await lastSeq(join(stateDir, '.conversation', file));
149
+ if (end !== undefined && end <= after)
150
+ continue;
151
+ const input = createReadStream(join(stateDir, '.conversation', file), { encoding: 'utf8' });
152
+ const lines = createInterface({ input, crlfDelay: Infinity });
153
+ try {
154
+ for await (const line of lines) {
155
+ let event;
156
+ try {
157
+ event = JSON.parse(line);
158
+ }
159
+ catch {
160
+ continue;
161
+ }
162
+ const cursor = Number(event.seq);
163
+ if (!Number.isSafeInteger(cursor) || cursor <= after)
164
+ continue;
165
+ const historical = ['fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'].includes(event.kind);
166
+ const visible = event.source !== 'agent_replay' && (event.sessionGeneration === live.snapshot.sessionGeneration || (historical && event.acpSessionId === sessionId));
167
+ if (!visible) {
168
+ next = String(cursor);
169
+ continue;
170
+ }
171
+ if (events.length === limit) {
172
+ hasMore = true;
173
+ break scan;
174
+ }
175
+ first ??= String(cursor);
176
+ events.push(event);
177
+ next = String(cursor);
178
+ }
179
+ }
180
+ finally {
181
+ lines.close();
182
+ input.destroy();
183
+ }
184
+ }
185
+ // If a fresh/resumed session changed during the read, use the live page only.
186
+ if ((await readFile(join(stateDir, '.acp-session-id'), 'utf8')).trim() !== sessionId)
187
+ return live;
188
+ return { ...live, events, firstAvailableCursor: first, nextCursor: next, hasMore };
189
+ }
@@ -24,7 +24,7 @@ export declare class FleetQueryService {
24
24
  private readonly options;
25
25
  private readonly control;
26
26
  constructor(options: FleetQueryOptions);
27
- list(): Promise<Array<{
27
+ list(includeTemporary?: boolean): Promise<Array<{
28
28
  role: RoleRecord;
29
29
  status: RoleStatus;
30
30
  capabilities: RoleCapabilities;
@@ -75,9 +75,9 @@ export class FleetQueryService {
75
75
  this.options = options;
76
76
  this.control = options.control ?? controlRequest;
77
77
  }
78
- async list() {
78
+ async list(includeTemporary = false) {
79
79
  const roles = (await this.options.repository.list())
80
- .filter(role => role.configured && role.lifetime === 'permanent');
80
+ .filter(role => (role.configured && role.lifetime === 'permanent') || (includeTemporary && role.lifetime === 'temporary'));
81
81
  return Promise.all(roles.map(async (role) => {
82
82
  const status = await this.status(role);
83
83
  return { role, status, capabilities: roleCapabilities(role, status, this.options.capabilityContext) };
@@ -0,0 +1,7 @@
1
+ /** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
2
+ export declare function legacySupervisorIdentity(role: string, name: string, temporary: boolean, generation: number): {
3
+ name: string;
4
+ cid: string;
5
+ generation: number;
6
+ proof: string;
7
+ };
@@ -0,0 +1,43 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { stateRoot } from '../paths.js';
5
+ import { binderKey } from '../agent-ours/state.js';
6
+ import { FleetError } from './errors.js';
7
+ /** Read-only compatibility proof for supervisors predating descriptor identity metadata. */
8
+ export function legacySupervisorIdentity(role, name, temporary, generation) {
9
+ const root = join(stateRoot(), 'private-ours');
10
+ try {
11
+ const matches = [];
12
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
13
+ if (!entry.isDirectory() || !/^[a-f0-9]{64}$/.test(entry.name))
14
+ continue;
15
+ const dir = join(root, entry.name);
16
+ const instance = JSON.parse(readFileSync(join(dir, 'instance.json'), 'utf8'));
17
+ if (instance.role !== role)
18
+ continue;
19
+ const state = JSON.parse(readFileSync(join(dir, 'state.json'), 'utf8'));
20
+ const pin = JSON.parse(readFileSync(join(dir, 'identity-pin.json'), 'utf8'));
21
+ if (!Number.isSafeInteger(generation) || generation < 1
22
+ || typeof state.action !== 'string' || !state.action
23
+ || instance.temporary !== temporary || typeof instance.instance !== 'string' || !instance.instance
24
+ || state.version !== 1 || state.instance !== instance.instance
25
+ || state.name !== name || state.lifetime !== (temporary ? 'temporary' : 'permanent')
26
+ || state.generation !== generation || !['READY', 'SERVING'].includes(state.phase)
27
+ || typeof state.daemon !== 'string' || !state.daemon || binderKey(state.daemon, name) !== entry.name
28
+ || !/^[a-f0-9]{64}$/i.test(state.cid ?? '')
29
+ || pin.daemon !== state.daemon || pin.name !== name || pin.cid !== state.cid)
30
+ throw new Error('mismatched legacy ownership proof');
31
+ matches.push({ name, cid: state.cid, generation, proof: createHash('sha256').update(JSON.stringify([
32
+ instance.instance, state.action, state.daemon, name, state.cid, state.lifetime, generation,
33
+ ])).digest('hex') });
34
+ }
35
+ if (matches.length !== 1)
36
+ throw new Error('missing or ambiguous legacy ownership proof');
37
+ return matches[0];
38
+ }
39
+ catch {
40
+ // The private directory also contains ownership material: never echo parse errors.
41
+ throw new FleetError('capability_unavailable', 'legacy supervisor ownership proof is missing, ambiguous, or mismatched');
42
+ }
43
+ }
@@ -14,7 +14,7 @@ export interface HarnessModelCatalog {
14
14
  export declare function codexModelCatalog(path?: string): HarnessModelCatalog;
15
15
  /**
16
16
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
17
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
17
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
18
18
  * not a claim about account entitlement (which Claude validates at launch).
19
19
  */
20
20
  export declare function claudeModelCatalog(): HarnessModelCatalog;
@@ -41,17 +41,20 @@ export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache
41
41
  }
42
42
  /**
43
43
  * Claude Code 2.1 has no typed model-list endpoint. These exact IDs are the
44
- * versioned adapter contract shipped by the installed 2.1 CLI, not aliases and
44
+ * versioned adapter contract verified with CLI 2.1.281, not aliases and
45
45
  * not a claim about account entitlement (which Claude validates at launch).
46
46
  */
47
47
  export function claudeModelCatalog() {
48
48
  const reasoningEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
49
49
  return {
50
50
  models: [
51
+ { id: 'claude-fable-5-1', label: 'Claude Fable 5.1', reasoningEfforts, source: 'claude-adapter-2.1' },
52
+ { id: 'claude-opus-5-5', label: 'Claude Opus 5.5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
+ { id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoningEfforts: [], source: 'claude-adapter-2.1' },
51
54
  { id: 'claude-fable-5', label: 'Claude Fable 5', reasoningEfforts, source: 'claude-adapter-2.1' },
52
55
  { id: 'claude-opus-5', label: 'Claude Opus 5', reasoningEfforts, source: 'claude-adapter-2.1' },
53
56
  { id: 'claude-sonnet-5', label: 'Claude Sonnet 5', reasoningEfforts, source: 'claude-adapter-2.1' },
54
57
  ],
55
- warnings: ['Claude Code exposes no typed model-list endpoint; choices are exact 2.1 adapter IDs and entitlement is validated at launch.'],
58
+ warnings: ['Claude Code exposes no typed model-list endpoint; choices require a current Claude runtime (verified with 2.1.281); older bundled SDKs need CLAUDE_CODE_EXECUTABLE. Entitlement is validated at launch.'],
56
59
  };
57
60
  }
@@ -0,0 +1,19 @@
1
+ export declare const PROVENANCE_KINDS: {
2
+ readonly role: "roles";
3
+ readonly brain: "brains";
4
+ readonly template: "agent_templates";
5
+ readonly layout: "room_layouts";
6
+ };
7
+ export type ProvenanceKind = keyof typeof PROVENANCE_KINDS;
8
+ export type ProvenanceStatus = 'predefined' | 'changed' | 'custom';
9
+ export interface ProvenanceEntry {
10
+ status: ProvenanceStatus;
11
+ preset?: string;
12
+ }
13
+ export type Provenance = Record<ProvenanceKind, Record<string, ProvenanceEntry>>;
14
+ export declare class PresetProvenance {
15
+ private configPath?;
16
+ private presetRoot;
17
+ constructor(configPath?: string | undefined, presetRoot?: string);
18
+ read(): Provenance;
19
+ }
@@ -0,0 +1,59 @@
1
+ /** Where each configured definition came from, compared with the packaged presets.
2
+ * A definition is `predefined` when it has the same value as the shipped preset of
3
+ * the same name, `changed` when a preset of that name exists but the value differs,
4
+ * and `custom` otherwise. Values are compared after parsing, so the formatting the
5
+ * configuration editor writes does not count as a change. Preset text is returned
6
+ * so users can see exactly what ships; it contains no host data.
7
+ */
8
+ import { isDeepStrictEqual } from 'node:util';
9
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { basename, extname, join } from 'node:path';
11
+ import { parse } from 'yaml';
12
+ import { defaultConfigPath } from '../paths.js';
13
+ import { splitRootFor } from '../config.js';
14
+ import { packagedPresetRoot } from '../preset-bootstrap.js';
15
+ export const PROVENANCE_KINDS = { role: 'roles', brain: 'brains', template: 'agent_templates', layout: 'room_layouts' };
16
+ const MAX_BYTES = 256 * 1024;
17
+ /** Regular, bounded YAML files of one directory, by definition name. Anything else is skipped. */
18
+ function yamlFiles(dir) {
19
+ const files = new Map();
20
+ if (!existsSync(dir) || !lstatSync(dir).isDirectory())
21
+ return files;
22
+ for (const file of readdirSync(dir).sort()) {
23
+ if (!/\.ya?ml$/.test(file))
24
+ continue;
25
+ const path = join(dir, file), stat = lstatSync(path);
26
+ if (!stat.isFile() || stat.size > MAX_BYTES)
27
+ continue;
28
+ files.set(basename(file, extname(file)), readFileSync(path, 'utf8'));
29
+ }
30
+ return files;
31
+ }
32
+ const parsed = (text) => { try {
33
+ return parse(text);
34
+ }
35
+ catch {
36
+ return Symbol('unparseable');
37
+ } };
38
+ export class PresetProvenance {
39
+ configPath;
40
+ presetRoot;
41
+ constructor(configPath, presetRoot = join(packagedPresetRoot(), 'fleet')) {
42
+ this.configPath = configPath;
43
+ this.presetRoot = presetRoot;
44
+ }
45
+ read() {
46
+ const split = splitRootFor(this.configPath ?? defaultConfigPath());
47
+ const result = {};
48
+ for (const [kind, dir] of Object.entries(PROVENANCE_KINDS)) {
49
+ const presets = yamlFiles(join(this.presetRoot, dir)), installed = yamlFiles(join(split, dir));
50
+ result[kind] = Object.fromEntries([...installed].map(([name, text]) => {
51
+ const preset = presets.get(name);
52
+ if (preset === undefined)
53
+ return [name, { status: 'custom' }];
54
+ return [name, { status: isDeepStrictEqual(parsed(text), parsed(preset)) ? 'predefined' : 'changed', preset }];
55
+ }));
56
+ }
57
+ return result;
58
+ }
59
+ }
@@ -138,7 +138,7 @@ export class RoleCreationService {
138
138
  reasoningEffort: resolved.effort,
139
139
  cwd, lifetime: request.lifetime,
140
140
  permissions: resolvePermissions(defaults.permissions, request.permissions),
141
- monitor: resolveMonitorConfig(defaults.monitor, request.monitor),
141
+ monitor: resolveMonitorConfig(defaults.monitor, request.monitor, { harness: resolved.harness }),
142
142
  };
143
143
  const warnings = [];
144
144
  if (request.permissions.approval === 'allow')
@@ -1,3 +1,4 @@
1
+ import { retireNotificationTarget } from '../notifications/target-cleanup.js';
1
2
  import { cpSync, existsSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
2
3
  import { basename, dirname, join } from 'node:path';
3
4
  import { loadConfig, ROLE_NAME_RE } from '../config.js';
@@ -87,6 +88,7 @@ export class RoleRemovalService {
87
88
  await this.options.ops.backend.uninstall(preview.role);
88
89
  if (existsSync(sourceState))
89
90
  renameSync(sourceState, join(recoveryPath, 'orphan-state'));
91
+ await retireNotificationTarget(`/fleet/chats?chat=${encodeURIComponent(preview.role)}`).catch(() => { });
90
92
  this.options.ops.log(`removed orphaned backend/state '${preview.role}'`);
91
93
  }
92
94
  return { ...preview, removed: true, recoveryPath };
@@ -1,7 +1,7 @@
1
1
  import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
2
2
  import { join, relative } from 'node:path';
3
3
  import { parse } from 'yaml';
4
- import { loadConfig, resolveMonitorConfig, resolvePermissions, ROLE_NAME_RE, } from '../config.js';
4
+ import { loadConfig, resolveSnapshotMonitor, resolvePermissions, ROLE_NAME_RE, } from '../config.js';
5
5
  import { agentsRoot, tmpRoot } from '../paths.js';
6
6
  import { readSpawnLineage } from '../web/topology.js';
7
7
  const MAX_SNAPSHOT_BYTES = 256 * 1024;
@@ -54,7 +54,7 @@ function snapshot(path, name) {
54
54
  session, identity: typeof raw.identity === 'string' ? raw.identity : name,
55
55
  permissions: resolvePermissions(undefined, raw.permissions),
56
56
  permissionsDeclared: raw.permissions !== undefined,
57
- monitor: resolveMonitorConfig(undefined, raw.monitor),
57
+ monitor: resolveSnapshotMonitor(raw.monitor),
58
58
  sourceFile: '(temp)',
59
59
  },
60
60
  };