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

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 +592 -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
@@ -0,0 +1,158 @@
1
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { readPrivateFile } from '@ours.network/sdk/connector';
4
+ import { readClientProfile } from '../client-profile.js';
5
+ function defaultProfile() {
6
+ try {
7
+ return readClientProfile();
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ /** The installer delivers the producer credential beside the profile credential, bound to that server installation. */
14
+ export const PRODUCER_CREDENTIAL_FILE = 'notifications-producer.json';
15
+ const loopback = (url) => ['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname);
16
+ export function producerConfig(env = process.env, profile = defaultProfile, warn = () => { }) {
17
+ const origin = env.OURS_NOTIFICATIONS_ORIGIN, token = env.OURS_NOTIFICATIONS_PRODUCER_TOKEN;
18
+ if (origin || token) {
19
+ if (!origin || !token || token.length < 32)
20
+ throw new Error('notifications producer requires origin and scoped token');
21
+ const url = new URL(origin);
22
+ if (!['http:', 'https:'].includes(url.protocol) || url.origin !== origin || url.username || url.password)
23
+ throw new Error('invalid notification service origin');
24
+ if (url.protocol === 'http:' && !loopback(url))
25
+ throw new Error('notification credentials require HTTPS or loopback');
26
+ return { origin, token };
27
+ }
28
+ const selected = profile();
29
+ if (!selected)
30
+ return;
31
+ const file = join(dirname(selected.credentialPath), PRODUCER_CREDENTIAL_FILE);
32
+ if (!existsSync(file))
33
+ return;
34
+ // Reasons are fixed text: parser and file errors can quote the secret-bearing contents.
35
+ let reason = 'it is not a private owner-only file';
36
+ try {
37
+ const text = readPrivateFile(file, 4096).toString('utf8');
38
+ reason = 'it is not valid producer JSON';
39
+ const bound = JSON.parse(text);
40
+ reason = 'it belongs to another server installation';
41
+ if (!bound || bound.schema !== 1 || bound.serverUrl !== selected.serverUrl || bound.expectedInstanceId !== selected.expectedInstanceId)
42
+ throw new Error();
43
+ reason = 'its credential is invalid';
44
+ if (typeof bound.token !== 'string' || bound.token.length < 32)
45
+ throw new Error();
46
+ reason = 'the server requires HTTPS or loopback';
47
+ const server = new URL(selected.serverUrl);
48
+ if (server.protocol !== 'https:' && !(server.protocol === 'http:' && loopback(server)))
49
+ throw new Error();
50
+ reason = 'the client credential is unreadable';
51
+ const gatewayCredential = readPrivateFile(selected.credentialPath, 4096).toString('utf8').trim();
52
+ return { origin: selected.serverUrl.replace(/\/$/, '') + '/notifications', token: bound.token, gatewayCredential };
53
+ }
54
+ catch {
55
+ warn(`Notifications are not produced: ${file} is unusable (${reason}). Re-run the installer to repair it.`);
56
+ return;
57
+ }
58
+ }
59
+ /** Durable producer queue. Service dedupe makes crash-after-acceptance replay safe. */
60
+ export class NotificationOutbox {
61
+ file;
62
+ config;
63
+ project;
64
+ warn;
65
+ entries;
66
+ cursor;
67
+ checkpoint;
68
+ running;
69
+ retired = false;
70
+ timer;
71
+ constructor(file, config, project, warn = () => { }) {
72
+ this.file = file;
73
+ this.config = config;
74
+ this.project = project;
75
+ this.warn = warn;
76
+ mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
77
+ const saved = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { entries: [] };
78
+ this.entries = Array.isArray(saved) ? saved : saved.entries;
79
+ this.cursor = saved.cursor;
80
+ this.checkpoint = saved.checkpoint;
81
+ if (!Array.isArray(this.entries))
82
+ throw new Error('invalid notification outbox');
83
+ this.timer = setInterval(() => { void this.drain(); }, 3000);
84
+ this.timer.unref();
85
+ }
86
+ persist(entries, cursor = this.cursor, checkpoint = this.checkpoint) {
87
+ const tmp = this.file + '.tmp', fd = openSync(tmp, 'w', 0o600);
88
+ try {
89
+ writeFileSync(fd, JSON.stringify({ entries, cursor, checkpoint }));
90
+ fsyncSync(fd);
91
+ }
92
+ finally {
93
+ closeSync(fd);
94
+ }
95
+ renameSync(tmp, this.file);
96
+ const dir = openSync(dirname(this.file), 'r');
97
+ try {
98
+ fsyncSync(dir);
99
+ }
100
+ finally {
101
+ closeSync(dir);
102
+ }
103
+ this.entries = entries;
104
+ this.cursor = cursor;
105
+ this.checkpoint = checkpoint;
106
+ }
107
+ advance(cursor) { this.persist(this.entries, cursor); }
108
+ saveCheckpoint(checkpoint) { this.persist(this.entries, this.cursor, checkpoint); }
109
+ enqueue(id, value, cursor = this.cursor) {
110
+ if (this.retired)
111
+ return false;
112
+ if (this.entries.some(e => e.id === id)) {
113
+ if (cursor !== this.cursor)
114
+ this.advance(cursor);
115
+ return true;
116
+ }
117
+ if (this.entries.length >= 2000)
118
+ return false;
119
+ this.persist([...this.entries, { id, value }], cursor);
120
+ return true;
121
+ }
122
+ drain() {
123
+ if (this.retired)
124
+ return Promise.resolve();
125
+ return this.running ??= this.drainOnce().finally(() => { this.running = undefined; });
126
+ }
127
+ async drainOnce() {
128
+ for (const entry of [...this.entries].slice(0, 32)) {
129
+ if (this.retired)
130
+ break;
131
+ try {
132
+ const value = entry.payload ?? await this.project(entry.value, entry.id);
133
+ // Freeze the canonical projection before any network attempt. A crash
134
+ // after service acceptance must replay the identical dedupe payload.
135
+ if (!entry.payload)
136
+ this.persist(this.entries.map(e => e.id === entry.id ? { ...e, payload: value } : e));
137
+ const response = await fetch(this.config.origin + '/api/v1/send', {
138
+ method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000),
139
+ headers: { ...(this.config.gatewayCredential
140
+ ? { 'X-Ours-Api-Token': this.config.gatewayCredential, 'X-Ours-Notifications-Producer': this.config.token }
141
+ : { Authorization: `Bearer ${this.config.token}` }), 'Content-Type': 'application/json' },
142
+ body: JSON.stringify(value),
143
+ });
144
+ await response.body?.cancel();
145
+ if (!response.ok)
146
+ throw new Error('notification service rejected event');
147
+ this.persist(this.entries.filter(e => e.id !== entry.id));
148
+ }
149
+ catch {
150
+ this.warn('Notification producer delivery pending; retrying from durable outbox');
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ /** Wait for the current send before clearing queued sends and retiring its target. */
156
+ async retire() { this.retired = true; clearInterval(this.timer); await this.running; this.persist([]); }
157
+ async close() { clearInterval(this.timer); await this.running; }
158
+ }
@@ -0,0 +1,8 @@
1
+ import { type ProducerConfig } from './outbox.js';
2
+ /** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
3
+ export declare function retireNotificationTarget(url: string, config?: ProducerConfig | undefined, dir?: string, request?: typeof fetch): Promise<void>;
4
+ /** Persisted retries also run from the ordinary web notification producer after restart. */
5
+ export declare function drainNotificationTargetCleanup(config: ProducerConfig, dir?: string, request?: typeof fetch, options?: {
6
+ retain?: boolean;
7
+ beforeDelete?: (url: string) => Promise<boolean>;
8
+ }): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { replaceFileAtomically } from '../atomic-file.js';
5
+ import { stateRoot } from '../paths.js';
6
+ import { producerConfig } from './outbox.js';
7
+ const cleanupDir = () => join(stateRoot(), 'notifications', 'removed-targets');
8
+ /** Queue only after the owning lifecycle has established deletion, never on a failed inventory read. */
9
+ export async function retireNotificationTarget(url, config = producerConfig(), dir = cleanupDir(), request = fetch) {
10
+ if (!config)
11
+ return;
12
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
13
+ replaceFileAtomically(join(dir, createHash('sha256').update(url).digest('hex') + '.json'), JSON.stringify({ url }) + '\n', 0o600);
14
+ await drainNotificationTargetCleanup(config, dir, request, { retain: true });
15
+ }
16
+ /** Persisted retries also run from the ordinary web notification producer after restart. */
17
+ export async function drainNotificationTargetCleanup(config, dir = cleanupDir(), request = fetch, options = {}) {
18
+ if (!existsSync(dir))
19
+ return;
20
+ for (const file of readdirSync(dir).filter(file => /^[a-f0-9]{64}\.json$/.test(file)).slice(0, 32)) {
21
+ try {
22
+ const path = join(dir, file), value = JSON.parse(readFileSync(path, 'utf8'));
23
+ if (typeof value.url !== 'string' || !value.url.startsWith('/fleet/chats?'))
24
+ continue;
25
+ if (options.beforeDelete && !(await options.beforeDelete(value.url))) {
26
+ rmSync(path, { force: true });
27
+ continue;
28
+ }
29
+ const response = await request(config.origin + '/api/v1/delete-target', { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10000),
30
+ headers: { ...(config.gatewayCredential ? { 'X-Ours-Api-Token': config.gatewayCredential, 'X-Ours-Notifications-Producer': config.token } : { Authorization: `Bearer ${config.token}` }), 'Content-Type': 'application/json' }, body: JSON.stringify(value) });
31
+ await response.body?.cancel();
32
+ if (!response.ok)
33
+ break;
34
+ if (!options.retain)
35
+ rmSync(path, { force: true });
36
+ }
37
+ catch {
38
+ break; /* No credentials or response bodies are included in logs. */
39
+ }
40
+ }
41
+ }
package/dist/ops.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { retireNotificationTarget } from './notifications/target-cleanup.js';
1
2
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, unlinkSync, } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { randomUUID, createHash } from 'node:crypto';
@@ -5,6 +6,7 @@ import { agentDir, watchdogsRoot } from './paths.js';
5
6
  import { findRole } from './config.js';
6
7
  import { getAdapter } from './harness/registry.js';
7
8
  import { generateBriefing } from './briefing.js';
9
+ import { managedCliBriefing } from './managed-cli.js';
8
10
  import { resetRestartLedger } from './runner.js';
9
11
  import { archiveTempState, stopTempSupervisor, tempSupervisorLiveness, } from './temp-lifecycle.js';
10
12
  import { realExec } from './exec.js';
@@ -28,7 +30,7 @@ export function applyRole(role, opts = {}) {
28
30
  // brought up from so the supervised process can reload the SAME file instead of
29
31
  // silently falling back to the default ~/fleet.yaml. Temp roles snapshot their
30
32
  // whole resolved role into role.yaml instead and don't need this.
31
- if (!opts.temp)
33
+ if (!opts.temp || opts.configPath)
32
34
  writeFileSync(join(dir, '.config-path'), (opts.configPath ?? '') + '\n');
33
35
  writeFileSync(join(dir, '.identity'), role.identity + '\n');
34
36
  if (role.cwd)
@@ -43,6 +45,7 @@ export function applyRole(role, opts = {}) {
43
45
  routinesPath: join(dir, 'ROUTINES.md'), briefingBody,
44
46
  identityGuarantee: opts.identityGuarantee,
45
47
  temporaryIdentity: opts.temp === true,
48
+ managedCli: managedCliBriefing(role, opts.configPath, opts.temp === true),
46
49
  }));
47
50
  if (opts.fresh)
48
51
  for (const f of ['.booted', '.session-id', '.exit-status'])
@@ -255,6 +258,7 @@ export async function rmRole(cfg, name, deps) {
255
258
  rmSync(agentDir(name), { recursive: true, force: true });
256
259
  if (generatedSource && existsSync(role.sourceFile)) {
257
260
  unlinkSync(role.sourceFile);
261
+ await retireNotificationTarget(`/fleet/chats?chat=${encodeURIComponent(name)}`).catch(() => { });
258
262
  deps.log(`removed ${role.sourceFile}`);
259
263
  }
260
264
  deps.log(`removed '${name}' (its ours identity is left intact)`);
@@ -14,6 +14,8 @@ export interface OwnerChannelOptions {
14
14
  harness: string;
15
15
  config: OwnerChannelConfig;
16
16
  session: AgentSession;
17
+ /** Ordinary owner input must not cancel the initial briefing turn. */
18
+ startupPending?: () => boolean;
17
19
  stateDir: string;
18
20
  env?: Record<string, string>;
19
21
  log(line: string): void;
@@ -269,6 +271,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
269
271
  private attachmentGroups;
270
272
  private handleAttachmentGroup;
271
273
  private handle;
274
+ private ownerPromptPolicy;
272
275
  /**
273
276
  * Deterministic command path: the message never becomes an agent prompt.
274
277
  * Authorization already happened — the managed-agent relay branch and the
@@ -883,8 +883,15 @@ export class OwnerChannel {
883
883
  const recovered = [];
884
884
  for (const claim of this.messageRecovery.list()) {
885
885
  const item = await this.client.getHistoryItem(claim.wireId);
886
- if (!item)
887
- throw new Error(`journaled owner message ${claim.wireId} is missing from persistent history`);
886
+ if (!item) {
887
+ // The daemon has definitively lost this body. Retrying the claim would
888
+ // block every later owner message, including after a supervisor restart.
889
+ // Keep transient lookup errors retryable; only an explicit absence skips.
890
+ this.messageRecovery.pruneHandled(wireId => wireId === claim.wireId);
891
+ this.options.log(`[${this.options.role}] skipped journaled owner message ${claim.wireId} `
892
+ + `(seq=${claim.seq}): missing from persistent history`);
893
+ continue;
894
+ }
888
895
  recovered.push(this.historyMessage(item, claim));
889
896
  }
890
897
  const listed = await this.client.listIncomingMessages();
@@ -1123,6 +1130,13 @@ export class OwnerChannel {
1123
1130
  const retrieved = unread.length
1124
1131
  ? parseRetrievedAttachments(await this.client.getFiles(unread.map(file => file.wireId)), unread)
1125
1132
  : [];
1133
+ // getFiles returns daemon-local paths, which need not exist in Fleet's
1134
+ // filesystem (for example with an HTTP daemon in a container). Fetch
1135
+ // through the bound client and keep the daemon's integrity metadata for
1136
+ // admission below; never trust or remap the returned filesystem path.
1137
+ for (const file of retrieved) {
1138
+ file.path = await writeRecoveredAttachment(requestDir, file.wireId, await this.client.fetchFile(file.wireId));
1139
+ }
1126
1140
  for (const file of historyRecovered) {
1127
1141
  if (!group.recovery)
1128
1142
  throw new Error('unexpected read attachment without recovery route');
@@ -1136,8 +1150,7 @@ export class OwnerChannel {
1136
1150
  await mkdir(outbox, { recursive: true, mode: 0o700 });
1137
1151
  const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
1138
1152
  const queued = await queueSessionPrompt(this.options.session, this.ownerAttachmentPrompt(sender, originWireId, requestId, admitted, group.caption), {
1139
- interrupt: this.options.config.interrupt,
1140
- ...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
1153
+ ...this.ownerPromptPolicy(),
1141
1154
  origin: { kind: 'owner', requestId,
1142
1155
  ...(group.caption ? { displayText: String(group.caption.text ?? '') } : {}) },
1143
1156
  });
@@ -1263,8 +1276,7 @@ export class OwnerChannel {
1263
1276
  const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
1264
1277
  try {
1265
1278
  queued = await queueSessionPrompt(this.options.session, this.ownerPrompt(sender, text, wireId), {
1266
- interrupt: this.options.config.interrupt,
1267
- ...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
1279
+ ...this.ownerPromptPolicy(),
1268
1280
  origin: { kind: 'owner', requestId, displayText: text },
1269
1281
  });
1270
1282
  }
@@ -1309,6 +1321,14 @@ export class OwnerChannel {
1309
1321
  this.completionTasks.add(task);
1310
1322
  return true;
1311
1323
  }
1324
+ ownerPromptPolicy() {
1325
+ if (this.options.startupPending?.())
1326
+ return { interrupt: false, steer: true };
1327
+ return {
1328
+ interrupt: this.options.config.interrupt,
1329
+ ...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
1330
+ };
1331
+ }
1312
1332
  /**
1313
1333
  * Deterministic command path: the message never becomes an agent prompt.
1314
1334
  * Authorization already happened — the managed-agent relay branch and the
@@ -1378,6 +1398,7 @@ export class OwnerChannel {
1378
1398
  await this.fleetOps.provisionTask(task.task_id);
1379
1399
  return outcome;
1380
1400
  }),
1401
+ observeTaskReadiness: taskId => new TaskRoomApplicationService(this.options.configPath).observeTaskReadiness(taskId),
1381
1402
  taskProvisioningOutcome: taskId => new TaskRoomApplicationService(this.options.configPath).taskProvisioningOutcome(taskId),
1382
1403
  listTasks: filter => new TaskRoomApplicationService(this.options.configPath).listTasks(filter),
1383
1404
  groupedTasks: filter => new TaskRoomApplicationService(this.options.configPath).groupedTasks(filter),
@@ -1762,6 +1783,13 @@ export class OwnerChannel {
1762
1783
  const retrieved = unread.length
1763
1784
  ? parseRetrievedAttachments(await this.client.getFiles(unread.map(file => file.wireId)), unread)
1764
1785
  : [];
1786
+ // getFiles returns daemon-local paths, which need not exist in Fleet's
1787
+ // filesystem (for example with an HTTP daemon in a container). Fetch
1788
+ // through the bound client and keep the daemon's integrity metadata for
1789
+ // admission below; never trust or remap the returned filesystem path.
1790
+ for (const file of retrieved) {
1791
+ file.path = await writeRecoveredAttachment(requestDir, file.wireId, await this.client.fetchFile(file.wireId));
1792
+ }
1765
1793
  for (const file of historyRecovered) {
1766
1794
  if (!group.recovery)
1767
1795
  throw new Error('unexpected read attachment without recovery route');
@@ -1,3 +1,4 @@
1
+ import type { TaskReadinessIssue } from '../rooms-tasks/live-readiness.js';
1
2
  import type { InterruptOutcome, SessionEvent, SessionSnapshot } from '../session/types.js';
2
3
  import type { RoomOrchestrationRecord, TaskOutcome, TaskRecord, TaskTerminalIntent, TaskListRecord } from '../rooms-tasks/types.js';
3
4
  import { type OwnerCommentsState } from './notices.js';
@@ -58,6 +59,9 @@ export interface OwnerCommandContext {
58
59
  terminalTask(taskId: string, kind: TaskTerminalIntent['kind'], outcome?: TaskOutcome): Promise<void>;
59
60
  createTask(input: Omit<CreateTaskRequest, 'actor'>): Promise<TaskRecord>;
60
61
  startTask(taskId: string): Promise<TaskProvisioningOutcome | TaskRecord>;
62
+ observeTaskReadiness?(taskId: string): Promise<{
63
+ state: 'ready';
64
+ } | TaskReadinessIssue>;
61
65
  taskProvisioningOutcome?(taskId: string): TaskProvisioningOutcome;
62
66
  listTasks(filter?: {
63
67
  state?: TaskState | TaskState[];
@@ -47,6 +47,11 @@ function isKnownOwnerRoomState(message) {
47
47
  ].some(pattern => pattern.test(message));
48
48
  }
49
49
  function ownerTaskFailure(error) {
50
+ if (error instanceof TaskRoomApplicationError && error.code === 'task_not_ready')
51
+ return {
52
+ kind: 'state', detail: error.message,
53
+ action: 'Ask Fleet Coordinator to inspect room and member status and choose supported recovery.',
54
+ };
50
55
  if (error instanceof TaskListError)
51
56
  return {
52
57
  kind: error.code === 'list_not_found' ? 'not_found'
@@ -301,6 +306,7 @@ export const ownerCommands = [
301
306
  const trailingLines = argLines.slice(1).join('\n').trim() || undefined;
302
307
  const showTask = async (id) => {
303
308
  const { task: t } = ctx.getTask(id);
309
+ const readiness = t.state === 'active' ? await ctx.observeTaskReadiness?.(id) : undefined;
304
310
  await ctx.reply(renderMarkdownResult({
305
311
  icon: '📋', title: 'Task details',
306
312
  fields: [
@@ -308,6 +314,8 @@ export const ownerCommands = [
308
314
  { label: 'Title', value: t.title },
309
315
  { label: 'Status', value: taskStatus(t.state), kind: 'markdown' },
310
316
  { label: 'List', value: t.list_name ?? 'default', kind: 'code' },
317
+ ...(readiness ? [{ label: 'Readiness', value: readiness.state },
318
+ ...('reason' in readiness ? [{ label: 'Reason', value: readiness.reason }] : [])] : []),
311
319
  ...(t.blocked ? [{ label: 'Blocked', value: t.blocked.reason }] : []),
312
320
  ...(t.template ? [{ label: 'Template', value: `${t.template.name}@${t.template.version}`, kind: 'code' }] : []),
313
321
  ...(t.room_id ? [{ label: 'Room', value: t.room_id, kind: 'code' }] : []),
@@ -1,6 +1,6 @@
1
+ import { redactErasedContent, writePrivacyFilteredLedger } from '../erased-resources.js';
1
2
  import { createHash } from 'node:crypto';
2
3
  import { chmodSync, existsSync, readFileSync } from 'node:fs';
3
- import { replaceFileAtomically } from '../atomic-file.js';
4
4
  import { lifecycleEventDigestBasis, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
5
5
  const DIGEST = /^[a-f0-9]{64}$/u;
6
6
  const LIMIT = 5_000;
@@ -63,7 +63,7 @@ export class FleetLifecycleOutbox {
63
63
  }
64
64
  pending() {
65
65
  this.assertHealthy();
66
- return this.entries.filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
66
+ return redactErasedContent(this.entries).filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
67
67
  }
68
68
  finish(digest, delivery) {
69
69
  this.assertHealthy();
@@ -82,6 +82,7 @@ export class FleetLifecycleOutbox {
82
82
  throw new Error(this.corruptReason);
83
83
  }
84
84
  persist() {
85
- replaceFileAtomically(this.path, `${JSON.stringify({ version: 1, entries: this.entries })}\n`, 0o600);
85
+ this.entries = redactErasedContent(this.entries);
86
+ writePrivacyFilteredLedger(this.path, { version: 1, entries: this.entries });
86
87
  }
87
88
  }
@@ -1,7 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
- import { extname, join } from 'node:path';
4
- import { readDaemonConfig, resolveEndpoint } from '../monitor.js';
3
+ import { extname } from 'node:path';
5
4
  import { readClientProfile } from '../client-profile.js';
6
5
  import { OursError, attachOursClient, } from '@ours.network/sdk/client';
7
6
  /** Any failure of a daemon operation. Never carries a message body or a token. */
@@ -131,24 +130,10 @@ export class OursSdkClient {
131
130
  return;
132
131
  const environment = { ...process.env, ...this.env };
133
132
  const profile = readClientProfile(environment);
134
- // Reuse existing connection configuration. Identity selection checks the
135
- // daemon capability before reading the client's protected token delivery file.
136
- const endpoint = !profile && environment.OURS_DAEMON_ID
137
- ? resolveEndpoint(environment, false) : undefined;
138
- const token = endpoint ? environment.OURS_API_TOKEN?.trim() || readDaemonConfig(environment).apiToken : undefined;
139
- const options = profile ? {
133
+ const options = {
140
134
  endpoint: profile.endpoint, expectedInstanceId: profile.expectedInstanceId,
141
135
  credentialPath: profile.credentialPath,
142
136
  sessionMode: 'external', leaseToken: this.leaseToken, env: {},
143
- } : endpoint ? {
144
- endpoint: endpoint.origin, expectedInstanceId: environment.OURS_DAEMON_ID,
145
- sessionMode: 'external', leaseToken: this.leaseToken,
146
- ...(token ? { token } : { credentialPath: join(endpoint.stateDir, 'daemon-token') }),
147
- } : {
148
- env: environment,
149
- leaseToken: this.leaseToken,
150
- clientPid: process.pid,
151
- fetch: notificationDeadlineFetch(this.deps.fetch ?? globalThis.fetch, this.deps.notificationRequestDeadlineMs ?? NOTIFICATION_REQUEST_DEADLINE_MS),
152
137
  };
153
138
  this.client = await (this.deps.attachClient?.(options) ?? attachOursClient(options));
154
139
  this.terminalReleasePending = true;
package/dist/paths.d.ts CHANGED
@@ -11,6 +11,8 @@ export declare const home: () => string;
11
11
  */
12
12
  export declare function deriveXdgRuntimeDir(env?: NodeJS.ProcessEnv, uid?: number | undefined, exists?: (p: string) => boolean): string | undefined;
13
13
  export declare const stateRoot: () => string;
14
+ /** The Owner invitation file that tunnel setup writes itself after enrollment. */
15
+ export declare const workspaceOwnerInvite: () => string;
14
16
  export declare const agentsRoot: () => string;
15
17
  export declare const tmpRoot: () => string;
16
18
  export declare const logsRoot: () => string;
package/dist/paths.js CHANGED
@@ -21,6 +21,8 @@ export function deriveXdgRuntimeDir(env = process.env, uid = process.getuid?.(),
21
21
  return env.XDG_RUNTIME_DIR;
22
22
  }
23
23
  export const stateRoot = () => join(home(), '.ours-fleet');
24
+ /** The Owner invitation file that tunnel setup writes itself after enrollment. */
25
+ export const workspaceOwnerInvite = () => join(stateRoot(), 'workspace', 'owner.invite');
24
26
  export const agentsRoot = () => join(stateRoot(), 'agents');
25
27
  export const tmpRoot = () => join(stateRoot(), 'tmp');
26
28
  export const logsRoot = () => join(stateRoot(), 'logs');
@@ -67,10 +67,21 @@ const V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS = Object.freeze({
67
67
  'agent_templates/Developer.yaml': 'fdd953aab72d042e62780dd273598745aae59521b39edf4876d7676bb7189c83',
68
68
  'agent_templates/Critic.yaml': '3e98a28f17876061cfadf984351aa59fdbe19629e514eff52c74546545c64a91',
69
69
  });
70
+ // The packaged Coordinator role of revisions 8 and 9 addressed a named owner channel that a
71
+ // fresh installation does not have. Untouched copies adopt the current role; edited ones stay.
72
+ const OWNER_CHANNEL_COORDINATOR_FINGERPRINTS = Object.freeze({
73
+ 'roles/Coordinator.yaml': [
74
+ '8c8ad28d9bb9ddf9dba8c5c9c7859f25edd8fa10ebf833e2340124a6e49583ec',
75
+ 'f0d8b169ad0a18051b57e5280b93cf01e05faa5c12f4e12bdf89973edf2ef58a',
76
+ ],
77
+ });
70
78
  const CURRENT_ROLE_DEFAULTS = new Set([
71
79
  'roles/Coordinator.yaml', 'roles/LocalCoordinator.yaml', 'roles/Developer.yaml', 'roles/Critic.yaml',
80
+ 'roles/Engineer.yaml',
72
81
  'agent_templates/LocalCoordinator.yaml', 'agent_templates/Developer.yaml', 'agent_templates/Critic.yaml',
82
+ 'agent_templates/Engineer.yaml',
73
83
  'room_templates/single.yaml', 'room_templates/pair.yaml', 'room_templates/team.yaml',
84
+ 'room_templates/engineering.yaml',
74
85
  'agents/FleetCoordinator.yaml',
75
86
  ]);
76
87
  export const legacyStarterMigrationManifest = Object.freeze({
@@ -193,6 +204,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
193
204
  ...Object.keys(V3_ROLE_DEFAULT_FINGERPRINTS),
194
205
  ...Object.keys(V4_ROLE_DEFAULT_FINGERPRINTS),
195
206
  ...Object.keys(V5_ROLE_DEFAULT_FINGERPRINTS),
207
+ ...Object.keys(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS),
196
208
  ]);
197
209
  for (const relative of known) {
198
210
  const path = join(root, relative);
@@ -206,6 +218,7 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
206
218
  V4_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
207
219
  V5_ROLE_DEFAULT_FINGERPRINTS[relative],
208
220
  V5_GENERATED_ROLE_DEFAULT_FINGERPRINTS[relative],
221
+ ...(OWNER_CHANNEL_COORDINATOR_FINGERPRINTS[relative] ?? []),
209
222
  ].some(expected => expected !== undefined && actual === expected);
210
223
  if (recognized)
211
224
  (CURRENT_ROLE_DEFAULTS.has(relative) ? replacements : removals).push(path);
@@ -258,6 +271,18 @@ export function migratePackagedRoleDefaults(configuration, options = {}, hooks =
258
271
  unlinkSync(target);
259
272
  copyFileSync(join(source, relative), target, constants.COPYFILE_EXCL);
260
273
  chmodSync(target, 0o600);
274
+ // New Engineer seats use the existing Developer's selected brain. This
275
+ // preserves wizard/custom provider choices without copying permissions.
276
+ if (relative === 'agent_templates/Engineer.yaml' && classified.additions.includes(path)) {
277
+ const developer = join(root, 'agent_templates/Developer.yaml');
278
+ if (existsSync(developer)) {
279
+ const value = parseFleetDocument(developer, readFileSync(developer, 'utf8'), 'strict').value;
280
+ if (value.brain !== undefined && JSON.stringify(value.brain) !== JSON.stringify({ ref: 'claude-default' })) {
281
+ const bytes = readFileSync(target, 'utf8').replace('brain: { ref: claude-default }', () => `brain: ${JSON.stringify(value.brain)}`);
282
+ writeFileSync(target, bytes, { mode: 0o600 });
283
+ }
284
+ }
285
+ }
261
286
  fsyncPath(target);
262
287
  }
263
288
  for (const kind of ['roles', 'agent_templates', 'room_templates', 'agents'])
@@ -103,6 +103,7 @@ export function resolvedRolePlan(role, manifestDir = dirname(role.sourceFile)) {
103
103
  isolation: role.isolation ?? null,
104
104
  worklog: role.worklog ?? null,
105
105
  authProxy: role.auth_proxy ?? null,
106
+ managedCli: role.managed_cli ?? [],
106
107
  oversee: role.oversee ?? [],
107
108
  harnessOptions: redactSensitive(role.harness_options ?? null),
108
109
  env: {
@@ -0,0 +1,48 @@
1
+ import type { FleetConfig } from '../config.js';
2
+ import { TaskStateError } from './task-state.js';
3
+ import { provisionMembers } from './provision.js';
4
+ import type { CoworkAdapter } from './cowork-adapter.js';
5
+ export declare class MemberAdditionRejected extends TaskStateError {
6
+ readonly accepted = false;
7
+ }
8
+ export interface AddMemberRequest {
9
+ requestId: string;
10
+ slot: string;
11
+ role: string;
12
+ brain: string;
13
+ agentTemplate: string;
14
+ }
15
+ export declare function memberAddition(taskId: string, requestId: string): {
16
+ requestId: string;
17
+ taskId: string;
18
+ roleId: string;
19
+ state: "running" | "succeeded" | "attention" | "failed";
20
+ error?: string;
21
+ } | {
22
+ state: string;
23
+ error: string;
24
+ requestId: string;
25
+ taskId: string;
26
+ roleId: string;
27
+ };
28
+ export declare function requestMemberAddition(input: {
29
+ taskId: string;
30
+ request: AddMemberRequest;
31
+ cfg: FleetConfig;
32
+ cowork: CoworkAdapter;
33
+ configPath?: string;
34
+ binPath: string;
35
+ provision?: typeof provisionMembers;
36
+ }): Promise<{
37
+ requestId: string;
38
+ taskId: string;
39
+ roleId: string;
40
+ state: "running" | "succeeded" | "attention" | "failed";
41
+ error?: string;
42
+ } | {
43
+ state: string;
44
+ error: string;
45
+ requestId: string;
46
+ taskId: string;
47
+ roleId: string;
48
+ }>;