@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
package/dist/doctor.js CHANGED
@@ -1,17 +1,16 @@
1
- import { join } from 'node:path';
2
- import { userInfo, homedir } from 'node:os';
1
+ import { userInfo } from 'node:os';
3
2
  import { existsSync, readFileSync } from 'node:fs';
4
3
  import { attachOursClient, } from '@ours.network/sdk/client';
5
4
  import { realExec } from './exec.js';
6
5
  import { isolationContextFor, loadConfig } from './config.js';
7
6
  import { getAdapter, productionAdapters } from './harness/registry.js';
8
7
  import { analyzeFleetPermissions, formatNative } from './permissions.js';
8
+ import { managedCliReport } from './managed-cli.js';
9
9
  import { resolveBundledAcpAgent } from './harness/acp-agent.js';
10
10
  import { agentDir, deriveXdgRuntimeDir } from './paths.js';
11
11
  import { resolveIsolation, harnessRuntimeDir } from './isolation/policy.js';
12
12
  import { hermesConfiguredProvider } from './harness/hermes-startup.js';
13
13
  import { makeBubblewrapBackend } from './isolation/bubblewrap.js';
14
- import { authResolutionHint, resolveEndpoint, } from './monitor.js';
15
14
  import { readClientProfile } from './client-profile.js';
16
15
  import { readScheduledLoops, storedLoopHealth } from './loops/state.js';
17
16
  import { controlSocketPath } from './session/control.js';
@@ -37,25 +36,21 @@ function resolveMonitorProfiles(roles) {
37
36
  const profiles = [];
38
37
  for (const role of roles.filter(r => r.monitor?.mode === 'fleet')) {
39
38
  const env = { ...process.env, ...(role.env ?? {}) };
40
- const client = readClientProfile(env);
41
- if (client) {
42
- const existing = profiles.find(p => p.client?.endpoint === client.endpoint
43
- && p.client.expectedInstanceId === client.expectedInstanceId
44
- && p.client.credentialPath === client.credentialPath);
45
- if (existing)
46
- existing.roles.push(role.name);
47
- else
48
- profiles.push({ client, roles: [role.name] });
39
+ let client;
40
+ try {
41
+ client = readClientProfile(env);
42
+ }
43
+ catch (error) {
44
+ profiles.push({ roles: [role.name], error: error instanceof Error ? error.message : 'Invalid gateway profile' });
49
45
  continue;
50
46
  }
51
- const endpoint = resolveEndpoint(env);
52
- const token = endpoint.headers['x-ours-api-token'];
53
- const existing = profiles.find(p => p.endpoint?.origin === endpoint.origin
54
- && p.endpoint.headers['x-ours-api-token'] === token);
47
+ const existing = profiles.find(p => p.client?.endpoint === client.endpoint
48
+ && p.client.expectedInstanceId === client.expectedInstanceId
49
+ && p.client.credentialPath === client.credentialPath);
55
50
  if (existing)
56
51
  existing.roles.push(role.name);
57
52
  else
58
- profiles.push({ endpoint, roles: [role.name] });
53
+ profiles.push({ client, roles: [role.name] });
59
54
  }
60
55
  return profiles;
61
56
  }
@@ -156,7 +151,7 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
156
151
  // ── Rooms/tasks checks ────────────────────────────────────────
157
152
  if (loaded.ok && loaded.rooms) {
158
153
  const rooms = loaded.rooms;
159
- // Cowork socket reachability
154
+ // Cowork gateway reachability
160
155
  const coworkConfig = rooms.cowork?.config;
161
156
  try {
162
157
  const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
@@ -165,14 +160,14 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
165
160
  checks.push({
166
161
  name: 'cowork', ok: reachable,
167
162
  detail: reachable
168
- ? 'management socket reachable'
169
- : `management socket unreachable${coworkConfig ? ` (config: ${coworkConfig})` : ''}`,
163
+ ? 'gateway management reachable'
164
+ : `gateway management unreachable${coworkConfig ? ` (config: ${coworkConfig})` : ''}`,
170
165
  });
171
166
  }
172
167
  catch (e) {
173
168
  checks.push({
174
169
  name: 'cowork', ok: false,
175
- detail: `management socket error${coworkConfig ? ` (config: ${coworkConfig})` : ''}: ${e?.message ?? e}`,
170
+ detail: `gateway management error${coworkConfig ? ` (config: ${coworkConfig})` : ''}: ${e?.message ?? e}`,
176
171
  });
177
172
  }
178
173
  // Owner CID shape
@@ -219,17 +214,6 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
219
214
  : `template '${defaultTemplate}' not found`,
220
215
  });
221
216
  }
222
- // Shared-daemon selection coherence
223
- if (rooms.cowork?.config) {
224
- const { existsSync: exists } = await import('node:fs');
225
- const configOk = exists(rooms.cowork.config);
226
- checks.push({
227
- name: 'rooms: cowork config', ok: configOk,
228
- detail: configOk
229
- ? `cowork config at ${rooms.cowork.config}`
230
- : `cowork config not found: ${rooms.cowork.config}`,
231
- });
232
- }
233
217
  // Hard room-management capability check
234
218
  try {
235
219
  const { createCoworkAdapter } = await import('./rooms-tasks/cowork-adapter.js');
@@ -273,20 +257,17 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
273
257
  }
274
258
  catch { /* state dir may not exist yet */ }
275
259
  }
276
- let managedSelection = false;
260
+ let managedSelection = true;
277
261
  try {
278
262
  // Mark explicit selection before validation so a damaged managed profile does
279
263
  // not produce advice to start an unrelated local daemon.
280
- managedSelection = process.env.OURS_CONFIG !== undefined
281
- || existsSync(join(process.env.HOME || homedir(), '.ours-client', 'profile.json'));
264
+ managedSelection = true;
282
265
  const selected = readClientProfile(process.env);
283
266
  managedSelection = selected !== undefined;
284
- const client = await attachDaemon(selected ? {
267
+ const client = await attachDaemon({
285
268
  endpoint: selected.endpoint, expectedInstanceId: selected.expectedInstanceId,
286
269
  credentialPath: selected.credentialPath, env: {}, sessionMode: 'external',
287
270
  leaseToken: `ours-fleet-doctor-${process.pid}`,
288
- } : {
289
- env: process.env, leaseToken: `ours-fleet-doctor-${process.pid}`, clientPid: process.pid,
290
271
  });
291
272
  let daemonVersion;
292
273
  try {
@@ -368,6 +349,32 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
368
349
  + `grants only ${analysis.capabilities.join(', ') || '(nothing)'}`,
369
350
  });
370
351
  }
352
+ // Managed CLI policy. Three separate rows on purpose: what Fleet generated
353
+ // (static), what the running session loaded, and what the supervisor audit
354
+ // ledger actually recorded. The first two never stand in for the third.
355
+ if (roles.some(role => role.managed_cli?.length)) {
356
+ const report = managedCliReport({ roles }, opts.configPath, { write: false, platform });
357
+ for (const role of report.roles.filter(item => item.setup.state !== 'not-declared')) {
358
+ checks.push({
359
+ name: `managed CLI setup: ${role.role}`,
360
+ ok: ['prepared', 'generated-at-launch', 'not-required'].includes(role.setup.state),
361
+ detail: `static: ${role.setup.state} — ${role.setup.detail}`
362
+ + (['missing', 'stale'].includes(role.setup.state) ? '; run: ours-fleet managed-cli setup' : '')
363
+ + (role.warnings.length ? `; ${role.warnings.join('; ')}` : ''),
364
+ });
365
+ if (role.session_policy !== 'not-applicable')
366
+ checks.push({
367
+ name: `managed CLI session: ${role.role}`, ok: true,
368
+ detail: role.session_policy === 'restart-required'
369
+ ? 'the last launch loaded a different policy — restart this agent to load the current one'
370
+ : role.session_policy === 'never-launched' ? 'not launched since setup; the policy loads at its next start'
371
+ : 'the last launch loaded the current policy',
372
+ });
373
+ checks.push({
374
+ name: `managed CLI observed: ${role.role}`, ok: true, detail: role.observed.summary,
375
+ });
376
+ }
377
+ }
371
378
  for (const role of roles.filter(candidate => candidate.auth_proxy !== undefined)) {
372
379
  const proxy = role.auth_proxy;
373
380
  const credentialKeys = Object.keys(role.env ?? {}).filter(key => /^(ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|AUTHORIZATION)$/i.test(key)
@@ -448,14 +455,18 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
448
455
  // shared-mode misconfig (401) surfaces here rather than as a silent deaf monitor.
449
456
  const monitorProfiles = resolveMonitorProfiles(roles);
450
457
  for (const profile of monitorProfiles) {
451
- const { endpoint, client: selected } = profile;
458
+ const selected = profile.client;
452
459
  const checkName = monitorProfiles.length === 1
453
460
  ? 'monitor: daemon API'
454
461
  : `monitor: daemon API (${profile.roles.join(', ')})`;
455
- let ok = false, detail;
462
+ let ok = false, detail = 'gateway profile unavailable';
463
+ if (profile.error) {
464
+ checks.push({ name: checkName, ok: false, detail: profile.error });
465
+ continue;
466
+ }
456
467
  try {
457
468
  if (selected) {
458
- const daemon = await attachOursClient({
469
+ const daemon = await attachDaemon({
459
470
  endpoint: selected.endpoint,
460
471
  expectedInstanceId: selected.expectedInstanceId,
461
472
  credentialPath: selected.credentialPath,
@@ -464,7 +475,7 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
464
475
  });
465
476
  try {
466
477
  const [info, identities] = await Promise.all([
467
- daemon.version(), daemon.identities(),
478
+ daemon.version(), daemon.identities?.(),
468
479
  ]);
469
480
  if (info.name !== 'ours' || !Array.isArray(identities))
470
481
  throw new Error('selected daemon metadata is invalid');
@@ -477,27 +488,11 @@ export async function doctor(opts = {}, exec = realExec, platform = process.plat
477
488
  checks.push({ name: checkName, ok, detail });
478
489
  continue;
479
490
  }
480
- const live = await fetchImpl(`${endpoint.origin}/state-dir`, {});
481
- if (!live.ok) {
482
- detail = `daemon on :${endpoint.port} answered /state-dir with HTTP ${live.status} — not the ours daemon?`;
483
- }
484
- else {
485
- const auth = await fetchImpl(`${endpoint.origin}/identities`, { headers: endpoint.headers });
486
- if (auth.status === 401)
487
- detail = `reachable on :${endpoint.port} but the API token was rejected (401) — ` +
488
- authResolutionHint(endpoint);
489
- else if (!auth.ok)
490
- detail = `reachable on :${endpoint.port} but /identities returned HTTP ${auth.status}`;
491
- else {
492
- ok = true;
493
- detail = `reachable on :${endpoint.port}, authorized — supervisor wake stream available`;
494
- }
495
- }
496
491
  }
497
492
  catch (e) {
498
- const target = selected?.endpoint ?? `:${endpoint.port}`;
493
+ const target = selected.endpoint;
499
494
  detail = `unreachable on ${target} — monitored roles run degraded until it is up ` +
500
- `(start it: ours-daemon start) [${e?.message ?? e}]`;
495
+ `(check the configured gateway and issued credential) [${e?.message ?? e}]`;
501
496
  }
502
497
  checks.push({ name: checkName, ok, detail });
503
498
  }
@@ -0,0 +1,9 @@
1
+ export declare const erasedArg: (value: string) => string;
2
+ export declare function redactErasedContent<T>(value: T): T;
3
+ /** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
4
+ export declare function writePrivacyFilteredLedger(path: string, value: unknown): void;
5
+ /** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
6
+ export declare function eraseResourcePresentations(resources: ReadonlyArray<{
7
+ kind: 'task' | 'room' | 'agent';
8
+ id: string;
9
+ }>): void;
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { replaceFileAtomically, withSynchronousFileLock } from './atomic-file.js';
5
+ import { stateRoot } from './paths.js';
6
+ const registryPath = () => join(stateRoot(), 'erased-resources.json');
7
+ const lockPath = () => join(stateRoot(), 'locks', 'audit-erasure');
8
+ const ledgerNames = ['.fleet-command-audit.json', '.owner-channel-command-audit.json', '.owner-channel-lifecycle-outbox.json'];
9
+ export const erasedArg = (value) => `[erased:${createHash('sha256').update(value).digest('hex')}]`;
10
+ function registry() {
11
+ if (!existsSync(registryPath()))
12
+ return new Set();
13
+ const value = JSON.parse(readFileSync(registryPath(), 'utf8'));
14
+ if (!Array.isArray(value) || value.some(v => typeof v !== 'string'))
15
+ throw new Error('Invalid erasure registry');
16
+ return new Set(value);
17
+ }
18
+ /** Preserve delivery/deduplication metadata while removing owned presentation content. */
19
+ function redact(value, erased) {
20
+ if (Array.isArray(value))
21
+ return value.map(item => redact(item, erased));
22
+ if (!value || typeof value !== 'object')
23
+ return value;
24
+ const next = { ...value };
25
+ const kind = value.kind === 'lifecycle_failure' ? value.resource?.toLowerCase() : value.kind === 'agent_started' ? 'agent' : value.kind;
26
+ if (typeof value.id === 'string' && erased.has(`${kind}:${value.id}`)) {
27
+ for (const key of ['title', 'reason', 'roomName', 'label', 'template', 'configuration', 'model', 'permissions'])
28
+ delete next[key];
29
+ if (kind === 'task')
30
+ next.agents = [];
31
+ if (kind === 'room') {
32
+ delete next.name;
33
+ next.participants = [];
34
+ }
35
+ if (kind === 'agent') {
36
+ next.name = 'erased';
37
+ next.brain = '[erased]';
38
+ next.role = '[erased]';
39
+ next.inherited = [];
40
+ }
41
+ }
42
+ if (typeof value.roomId === 'string' && erased.has(`room:${value.roomId}`)) {
43
+ delete next.roomName;
44
+ if (kind === 'task')
45
+ next.agents = [];
46
+ }
47
+ const resources = value.outcome?.resourceIds;
48
+ const ownsAttempt = resources && Object.entries(resources).some(([key, id]) => erased.has(`${key}:${id}`));
49
+ const ownsPresentation = value.outcome?.presentations?.some((p) => erased.has(`${p.kind === 'agent_started' ? 'agent' : p.kind}:${p.id}`));
50
+ if ((ownsAttempt || ownsPresentation) && Array.isArray(value.argv) && !value.erased) {
51
+ next.argv = value.argv.map(erasedArg);
52
+ next.erased = true;
53
+ }
54
+ for (const key of Object.keys(next))
55
+ if (typeof next[key] === 'object')
56
+ next[key] = redact(next[key], erased);
57
+ return next;
58
+ }
59
+ export function redactErasedContent(value) { return redact(value, registry()); }
60
+ /** Every cooperative writer filters stale in-memory snapshots under the same lock as erasure. */
61
+ export function writePrivacyFilteredLedger(path, value) {
62
+ withSynchronousFileLock(lockPath(), () => replaceFileAtomically(path, JSON.stringify(redact(value, registry())) + '\n'));
63
+ }
64
+ /** Content-free resource IDs persist to prevent active writers resurrecting erased labels. */
65
+ export function eraseResourcePresentations(resources) {
66
+ withSynchronousFileLock(lockPath(), () => {
67
+ const erased = registry();
68
+ for (const resource of resources)
69
+ erased.add(`${resource.kind}:${resource.id}`);
70
+ replaceFileAtomically(registryPath(), JSON.stringify([...erased]));
71
+ for (const root of [join(stateRoot(), 'agents'), join(stateRoot(), 'tmp'), join(stateRoot(), 'recovery', 'temporary')]) {
72
+ if (!existsSync(root))
73
+ continue;
74
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
75
+ if (!entry.isDirectory() || entry.isSymbolicLink())
76
+ continue;
77
+ for (const name of ledgerNames) {
78
+ const path = join(root, entry.name, name);
79
+ if (!existsSync(path) || !lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
80
+ continue;
81
+ const old = JSON.parse(readFileSync(path, 'utf8'));
82
+ replaceFileAtomically(path, JSON.stringify(redact(old, erased)) + '\n');
83
+ }
84
+ }
85
+ }
86
+ });
87
+ }
@@ -113,6 +113,7 @@ export interface FleetCommandClassification {
113
113
  decision: FleetCommandDecision;
114
114
  }
115
115
  export interface FleetAuditAttempt {
116
+ erased?: boolean;
116
117
  version: 1;
117
118
  correlationId: string;
118
119
  requestId: string;
@@ -144,6 +145,12 @@ export declare const fleetProxyTopLevelInventory: Readonly<{
144
145
  }>;
145
146
  /** Classify before Commander parsing. Unknown and internal paths fail closed. */
146
147
  export declare function classifyFleetArgv(argv: readonly string[]): FleetCommandClassification;
148
+ /**
149
+ * The pinned configuration of a managed entry (src/managed-cli.ts) is recorded
150
+ * as a fingerprint, not as its path: diagnostics must tell which configuration a
151
+ * row ran under without the ledger holding the path itself.
152
+ */
153
+ export declare const managedPinMarker: (configuration: string) => string;
147
154
  /** Faithful ordered argv with deterministic structural redaction. */
148
155
  export declare function redactFleetArgv(argv: readonly string[]): string[];
149
156
  /** Durable write-once attempt ledger; contains redacted argv only. */
@@ -1,7 +1,7 @@
1
+ import { erasedArg, redactErasedContent, writePrivacyFilteredLedger } from './erased-resources.js';
1
2
  import { existsSync, readFileSync } from 'node:fs';
2
- import { randomUUID } from 'node:crypto';
3
+ import { createHash, randomUUID } from 'node:crypto';
3
4
  import { Buffer } from 'node:buffer';
4
- import { replaceFileAtomically } from './atomic-file.js';
5
5
  import { isSensitiveConfigKey } from './sensitive-config.js';
6
6
  import { mandatoryConfigurationFits } from './lifecycle-summary.js';
7
7
  import { MARKDOWN_MAX_BYTES, MARKDOWN_MAX_CODE_POINTS, markdownCode, markdownProse, } from './rooms-tasks/markdown.js';
@@ -91,7 +91,7 @@ export function recordFleetAuditPresentation(value) {
91
91
  : value.kind === 'lifecycle_failure'
92
92
  ? { label: fleetPresentationLabel(value.label) }
93
93
  : {};
94
- const presentation = structuredClone({ ...value, ...labels, eventId: basis });
94
+ const presentation = redactErasedContent(structuredClone({ ...value, ...labels, eventId: basis }));
95
95
  const digest = lifecycleEventDigestBasis(presentation);
96
96
  if (!collection.presentations.some(existing => lifecycleEventDigestBasis(existing) === digest))
97
97
  collection.presentations.push(presentation);
@@ -117,6 +117,7 @@ export function consumeFleetAuditCollection() {
117
117
  const SAFE_READ = new Set(['docs', 'version', 'config', 'ls', 'peek', 'logs', 'status', 'doctor']);
118
118
  const AGENT_SURFACES = {
119
119
  spawn: new Set(['<none>']),
120
+ ours: new Set(['tools', 'call']),
120
121
  template: new Set(['list', 'show', 'validate']),
121
122
  task: new Set(['create', 'list', 'lists', 'list-create', 'list-rename', 'list-delete', 'move',
122
123
  'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', 'work', 'finish']),
@@ -134,7 +135,8 @@ export const fleetProxyTopLevelInventory = Object.freeze({
134
135
  ], denied: [...DENIED],
135
136
  hidden: ['_run', '_run-temp', '_run-temp-worker', '_run-watchdog', '_run-watchdogs'], aliases: ['man'],
136
137
  });
137
- const globalValueOptions = new Set(['-c', '--configuration']);
138
+ // `--managed-configuration <file>` is the pinned managed entry form (src/managed-cli.ts).
139
+ const globalValueOptions = new Set(['-c', '--configuration', '--managed-configuration']);
138
140
  /** Classify before Commander parsing. Unknown and internal paths fail closed. */
139
141
  export function classifyFleetArgv(argv) {
140
142
  let command = '';
@@ -185,11 +187,17 @@ export function classifyFleetArgv(argv) {
185
187
  return { command, route: 'supervisor-proxy/public', decision: 'allow' };
186
188
  }
187
189
  const marker = (value) => value === '' ? '[REDACTED:empty]' : '[REDACTED:value]';
190
+ /**
191
+ * The pinned configuration of a managed entry (src/managed-cli.ts) is recorded
192
+ * as a fingerprint, not as its path: diagnostics must tell which configuration a
193
+ * row ran under without the ledger holding the path itself.
194
+ */
195
+ export const managedPinMarker = (configuration) => `[PIN:${createHash('sha256').update(configuration).digest('hex').slice(0, 16)}]`;
188
196
  const sensitiveValueFlags = new Set([
189
197
  '--identity', '--invite', '--token', '--api-token', '--password', '--password-file',
190
198
  '--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file', '--loops-file',
191
- '--configuration', '-c', '--public-invite', '--public-invite-file', '--invite-file',
192
- '--summary-file', '--text', '--message', '--summary', '--reason', '--goal', '--cwd',
199
+ '--configuration', '-c', '--managed-configuration', '--public-invite', '--public-invite-file', '--invite-file',
200
+ '--args-file', '--summary-file', '--text', '--message', '--summary', '--reason', '--goal', '--cwd',
193
201
  '--identity-cid', '--owner-cid', '--contact-cid', '--codex-config', '--add-dir',
194
202
  ]);
195
203
  function redactUrl(value) {
@@ -248,6 +256,11 @@ export function redactFleetArgv(argv) {
248
256
  result[index] = `${equal[1]}=${marker(equal[2])}`;
249
257
  continue;
250
258
  }
259
+ if (index === 0 && arg === '--managed-configuration' && result.length > 1) {
260
+ result[1] = managedPinMarker(result[1]);
261
+ index++;
262
+ continue;
263
+ }
251
264
  if (sensitiveValueFlags.has(arg) && index + 1 < result.length) {
252
265
  result[index + 1] = marker(result[index + 1]);
253
266
  index++;
@@ -304,11 +317,12 @@ export class FleetCommandAuditStore {
304
317
  if (recovered)
305
318
  this.persist();
306
319
  }
307
- list() { return this.attempts.map(item => structuredClone(item)); }
320
+ list() { return redactErasedContent(this.attempts).map(item => structuredClone(item)); }
308
321
  begin(requestId, caller, argv) {
322
+ this.attempts = redactErasedContent(this.attempts);
309
323
  const existing = this.attempts.find(item => item.caller === caller && item.requestId === requestId);
310
324
  if (existing) {
311
- if (JSON.stringify(existing.argv) !== JSON.stringify(redactFleetArgv(argv)))
325
+ if (JSON.stringify(existing.argv) !== JSON.stringify(existing.erased ? redactFleetArgv(argv).map(erasedArg) : redactFleetArgv(argv)))
312
326
  throw new Error('fleet command request ID was reused with different argv');
313
327
  return structuredClone(existing);
314
328
  }
@@ -328,6 +342,8 @@ export class FleetCommandAuditStore {
328
342
  return structuredClone(attempt);
329
343
  }
330
344
  finish(correlationId, caller, outcome) {
345
+ this.attempts = redactErasedContent(this.attempts);
346
+ outcome = redactErasedContent({ outcome }).outcome;
331
347
  const attempt = this.owned(correlationId, caller);
332
348
  if (!attempt.outcome)
333
349
  attempt.outcome = { ...outcome, completedAt: this.deps.now().toISOString(), delivery: 'sending' };
@@ -359,7 +375,8 @@ export class FleetCommandAuditStore {
359
375
  return attempt;
360
376
  }
361
377
  persist() {
362
- replaceFileAtomically(this.path, `${JSON.stringify({ version: 1, attempts: this.attempts })}\n`, 0o600);
378
+ this.attempts = redactErasedContent(this.attempts);
379
+ writePrivacyFilteredLedger(this.path, { version: 1, attempts: this.attempts });
363
380
  }
364
381
  }
365
382
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
@@ -6,7 +6,8 @@ import { realExec } from '../exec.js';
6
6
  import { registerAdapter } from './registry.js';
7
7
  import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
8
8
  import { harnessRuntimeDir } from '../isolation/policy.js';
9
- import { bundledAcpAgent } from './acp-agent.js';
9
+ import { bundledAcpAgent, resolveBundledAcpAgent } from './acp-agent.js';
10
+ import { hostClaude } from './claude-runtime.js';
10
11
  import { ClaudeCodeAgentSessionAdapter } from './claude-code-session.js';
11
12
  const OPTION_KEYS = [
12
13
  'plugins', 'mem_palace', 'mem_palace_midsession_autosave', 'permission_mode', 'effort',
@@ -283,6 +284,13 @@ export function makeClaudeCodeAdapter(exec = realExec, transport) {
283
284
  };
284
285
  if (!memPalace)
285
286
  env.MEMPALACE_DISABLED = 'true';
287
+ // The bundled adapter runs the Claude Code shipped with its SDK unless told otherwise. A host
288
+ // Claude Code at least as new as that copy is the one the person updates, so sessions run it.
289
+ if (!customAcpCommand(role)) {
290
+ const host = hostClaude(resolveBundledAcpAgent('@agentclientprotocol/claude-agent-acp', 'claude-agent-acp', 'claude-agent-acp').manifestPath, { ...process.env, ...role.env }, dirs.runCwd);
291
+ if (host)
292
+ env.CLAUDE_CODE_EXECUTABLE = host.path;
293
+ }
286
294
  // Per-role harness runtime home. Created before sandbox entry so the
287
295
  // bind has something to mount; harmless for un-isolated roles.
288
296
  // Only a role that declares `isolation:` gets a sandbox, and only a
@@ -0,0 +1,12 @@
1
+ /** The Claude Code version shipped inside the adapter's own SDK dependency. */
2
+ export declare function bundledClaudeVersion(adapterManifest: string): string | undefined;
3
+ /**
4
+ * The host's own Claude Code, when ACP sessions should run it instead of the copy bundled with the adapter.
5
+ * The person signs in to, updates and checks the host Claude Code, and a newer one knows models the bundled
6
+ * one does not. It is used only when it is at least as new as the bundled copy; an explicit
7
+ * CLAUDE_CODE_EXECUTABLE, in the role or the service environment, always decides instead.
8
+ */
9
+ export declare function hostClaude(adapterManifest: string | undefined, env: NodeJS.ProcessEnv, cwd?: string, version?: (path: string, env: NodeJS.ProcessEnv) => string | undefined): {
10
+ path: string;
11
+ version: string;
12
+ } | undefined;
@@ -0,0 +1,43 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { codexVersionAtLeast, executableOnPath } from './codex-runtime.js';
6
+ const versionOf = (text) => text.match(/\b(\d+\.\d+\.\d+)\b/)?.[1];
7
+ function installedClaudeVersion(path, env) {
8
+ const result = spawnSync(path, ['--version'], { env, encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] });
9
+ return result.status === 0 ? versionOf(result.stdout ?? '') : undefined;
10
+ }
11
+ /** The Claude Code version shipped inside the adapter's own SDK dependency. */
12
+ export function bundledClaudeVersion(adapterManifest) {
13
+ try {
14
+ const sdk = dirname(createRequire(adapterManifest).resolve('@anthropic-ai/claude-agent-sdk'));
15
+ const version = JSON.parse(readFileSync(join(sdk, 'package.json'), 'utf8')).claudeCodeVersion;
16
+ return typeof version === 'string' ? version : undefined;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ /**
23
+ * The host's own Claude Code, when ACP sessions should run it instead of the copy bundled with the adapter.
24
+ * The person signs in to, updates and checks the host Claude Code, and a newer one knows models the bundled
25
+ * one does not. It is used only when it is at least as new as the bundled copy; an explicit
26
+ * CLAUDE_CODE_EXECUTABLE, in the role or the service environment, always decides instead.
27
+ */
28
+ export function hostClaude(adapterManifest, env, cwd = process.cwd(), version = installedClaudeVersion) {
29
+ if (env.CLAUDE_CODE_EXECUTABLE !== undefined || !adapterManifest)
30
+ return undefined;
31
+ const bundled = bundledClaudeVersion(adapterManifest);
32
+ if (!bundled)
33
+ return undefined;
34
+ let path;
35
+ try {
36
+ path = executableOnPath('claude', env, cwd);
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ const found = version(path, env);
42
+ return found && codexVersionAtLeast(found, bundled) ? { path, version: found } : undefined;
43
+ }
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ type JsonObject = Record<string, unknown>;
2
3
  interface TerminalRecoveryOptions {
3
4
  sendToCodex(line: string): void;
4
5
  emitToClient(line: string): void;
@@ -39,6 +40,8 @@ export declare class CodexTerminalRecovery {
39
40
  private finishTurn;
40
41
  }
41
42
  /** Transform one app-server NDJSON request; malformed input passes through. */
42
- export declare function rewriteCodexAppServerRequest(line: string, approval: string, expectedSandbox: string, disableInheritedMcp?: boolean): string;
43
+ export declare function rewriteCodexAppServerRequest(line: string, approval: string, expectedSandbox: string, disableInheritedMcp?: boolean, managedOursTools?: JsonObject): string;
44
+ /** ACP only carries transport fields; read policy from Fleet's complete config. */
45
+ export declare function managedCodexToolPolicy(env: NodeJS.ProcessEnv): JsonObject | undefined;
43
46
  export declare function runCodexAppServerProxy(): void;
44
47
  export {};
@@ -300,9 +300,9 @@ function sandboxMode(policy) {
300
300
  * Fleet keeps the preset's sandbox policy, but restores the independently
301
301
  * configured approval policy on the request Codex actually executes.
302
302
  */
303
- function rewriteMessage(value, approval, expectedSandbox, disableInheritedMcp) {
303
+ function rewriteMessage(value, approval, expectedSandbox, disableInheritedMcp, managedOursTools) {
304
304
  if (Array.isArray(value))
305
- return value.map(candidate => rewriteMessage(candidate, approval, expectedSandbox, disableInheritedMcp));
305
+ return value.map(candidate => rewriteMessage(candidate, approval, expectedSandbox, disableInheritedMcp, managedOursTools));
306
306
  if (!isObject(value) || !isObject(value.params))
307
307
  return value;
308
308
  if (value.method === 'turn/start') {
@@ -318,17 +318,52 @@ function rewriteMessage(value, approval, expectedSandbox, disableInheritedMcp) {
318
318
  params: { ...value.params, config: { ...config, mcp_servers: {} } },
319
319
  };
320
320
  }
321
+ if (managedOursTools
322
+ && (value.method === 'thread/start' || value.method === 'thread/resume')) {
323
+ const config = isObject(value.params.config) ? value.params.config : {};
324
+ const servers = isObject(config.mcp_servers) ? config.mcp_servers : {};
325
+ const ours = servers.ours;
326
+ // Policy cannot create or re-enable a connector that ACP did not inject.
327
+ if (!isObject(ours) || ours.enabled === false)
328
+ return value;
329
+ const tools = isObject(ours.tools) ? { ...ours.tools } : {};
330
+ for (const [name, policy] of Object.entries(managedOursTools)) {
331
+ if (isObject(policy))
332
+ tools[name] = {
333
+ ...(isObject(tools[name]) ? tools[name] : {}), ...policy,
334
+ };
335
+ }
336
+ return { ...value, params: { ...value.params, config: { ...config,
337
+ mcp_servers: { ...servers, ours: { ...ours, tools } },
338
+ } } };
339
+ }
321
340
  return value;
322
341
  }
323
342
  /** Transform one app-server NDJSON request; malformed input passes through. */
324
- export function rewriteCodexAppServerRequest(line, approval, expectedSandbox, disableInheritedMcp = false) {
343
+ export function rewriteCodexAppServerRequest(line, approval, expectedSandbox, disableInheritedMcp = false, managedOursTools) {
325
344
  try {
326
- return JSON.stringify(rewriteMessage(JSON.parse(line), approval, expectedSandbox, disableInheritedMcp));
345
+ return JSON.stringify(rewriteMessage(JSON.parse(line), approval, expectedSandbox, disableInheritedMcp, managedOursTools));
327
346
  }
328
347
  catch {
329
348
  return line;
330
349
  }
331
350
  }
351
+ /** ACP only carries transport fields; read policy from Fleet's complete config. */
352
+ export function managedCodexToolPolicy(env) {
353
+ if (env.FLEET_OURS_MANAGED !== '1')
354
+ return undefined;
355
+ try {
356
+ const config = JSON.parse(env.CODEX_CONFIG ?? '{}');
357
+ const servers = isObject(config) ? config.mcp_servers : undefined;
358
+ const ours = isObject(servers) ? servers.ours : undefined;
359
+ return isObject(ours) && ours.enabled !== false && isObject(ours.tools)
360
+ ? ours.tools : undefined;
361
+ }
362
+ catch {
363
+ // Invalid config never grants tool approval. ACP owns config diagnostics.
364
+ return undefined;
365
+ }
366
+ }
332
367
  function requiredChoice(name, allowed) {
333
368
  const value = process.env[name];
334
369
  if (value && allowed.has(value))
@@ -355,6 +390,7 @@ export function runCodexAppServerProxy() {
355
390
  const approval = requiredChoice(APPROVAL_ENV, APPROVAL_POLICIES);
356
391
  const expectedSandbox = requiredChoice(SANDBOX_ENV, SANDBOX_MODES);
357
392
  const disableInheritedMcp = process.env[DISABLE_INHERITED_MCP_ENV] === '1';
393
+ const managedOursTools = managedCodexToolPolicy(process.env);
358
394
  const launch = realCodexLaunch();
359
395
  const env = { ...process.env };
360
396
  delete env.CODEX_PATH;
@@ -450,7 +486,7 @@ export function runCodexAppServerProxy() {
450
486
  input.on('line', line => {
451
487
  if (finished)
452
488
  return;
453
- const rewritten = rewriteCodexAppServerRequest(line, approval, expectedSandbox, disableInheritedMcp);
489
+ const rewritten = rewriteCodexAppServerRequest(line, approval, expectedSandbox, disableInheritedMcp, managedOursTools);
454
490
  recovery.observeClientLine(rewritten);
455
491
  writeToChild(rewritten);
456
492
  });
@@ -0,0 +1,5 @@
1
+ /** Efforts supported by Fleet's Codex Brain contract. Keep native eligibility in sync. */
2
+ export declare const CODEX_REASONING_EFFORTS: readonly ["low", "medium", "high", "xhigh", "max", "ultra"];
3
+ export declare function isCodexReasoningEffort(value: unknown): value is string;
4
+ /** Only inspected model tuning may bypass the managed CLI native-config guard. */
5
+ export declare function isManagedCliCodexConfig(config: unknown): boolean;
@@ -0,0 +1,12 @@
1
+ /** Efforts supported by Fleet's Codex Brain contract. Keep native eligibility in sync. */
2
+ export const CODEX_REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
3
+ export function isCodexReasoningEffort(value) {
4
+ return typeof value === 'string' && CODEX_REASONING_EFFORTS.includes(value);
5
+ }
6
+ /** Only inspected model tuning may bypass the managed CLI native-config guard. */
7
+ export function isManagedCliCodexConfig(config) {
8
+ if (config == null)
9
+ return true;
10
+ return typeof config === 'object' && !Array.isArray(config)
11
+ && Object.entries(config).every(([key, value]) => key === 'model_reasoning_effort' && isCodexReasoningEffort(value));
12
+ }