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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +280 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
  11. package/dist/agent-ours/mcp-endpoint.js +101 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +27 -1
  15. package/dist/agent-ours/service.js +107 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +34 -0
  19. package/dist/application/conversation-history.js +294 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +1 -1
  34. package/dist/application/role-repository.js +30 -7
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +120 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +402 -38
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +227 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +13 -0
  74. package/dist/fleet-command-audit.js +31 -9
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +55 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +182 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +66 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-review.d.ts +113 -0
  180. package/dist/rooms-tasks/task-review.js +561 -0
  181. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  182. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  183. package/dist/rooms-tasks/task-state.d.ts +51 -5
  184. package/dist/rooms-tasks/task-state.js +358 -50
  185. package/dist/rooms-tasks/task-tools.d.ts +35 -0
  186. package/dist/rooms-tasks/task-tools.js +134 -0
  187. package/dist/rooms-tasks/types.d.ts +78 -0
  188. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  189. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  190. package/dist/rooms-tasks/workspace.d.ts +35 -0
  191. package/dist/rooms-tasks/workspace.js +325 -0
  192. package/dist/runner.d.ts +33 -4
  193. package/dist/runner.js +410 -62
  194. package/dist/session/acp.d.ts +75 -2
  195. package/dist/session/acp.js +345 -82
  196. package/dist/session/arbiter.d.ts +98 -0
  197. package/dist/session/arbiter.js +272 -3
  198. package/dist/session/codex-app-server.d.ts +45 -1
  199. package/dist/session/codex-app-server.js +225 -32
  200. package/dist/session/control.d.ts +17 -3
  201. package/dist/session/control.js +219 -23
  202. package/dist/session/conversation-normalizer.d.ts +2 -1
  203. package/dist/session/conversation-normalizer.js +1 -1
  204. package/dist/session/conversation-observation.d.ts +25 -0
  205. package/dist/session/conversation-observation.js +100 -0
  206. package/dist/session/conversation-replay.d.ts +50 -0
  207. package/dist/session/conversation-replay.js +203 -0
  208. package/dist/session/conversation-store.d.ts +15 -1
  209. package/dist/session/conversation-store.js +213 -26
  210. package/dist/session/conversation-types.d.ts +57 -4
  211. package/dist/session/prompt-batch.d.ts +79 -0
  212. package/dist/session/prompt-batch.js +120 -0
  213. package/dist/session/task-notice.d.ts +14 -0
  214. package/dist/session/task-notice.js +13 -0
  215. package/dist/session/types.d.ts +42 -1
  216. package/dist/setup-tunnel-preflight.d.ts +34 -0
  217. package/dist/setup-tunnel-preflight.js +62 -0
  218. package/dist/socket-path.d.ts +2 -0
  219. package/dist/socket-path.js +22 -0
  220. package/dist/spawn.d.ts +5 -1
  221. package/dist/spawn.js +40 -4
  222. package/dist/startup-prompt.d.ts +15 -0
  223. package/dist/startup-prompt.js +71 -0
  224. package/dist/startup-readiness.d.ts +32 -0
  225. package/dist/startup-readiness.js +101 -0
  226. package/dist/startup-telemetry.d.ts +16 -0
  227. package/dist/startup-telemetry.js +46 -0
  228. package/dist/subscriptions/cli.d.ts +91 -0
  229. package/dist/subscriptions/cli.js +223 -0
  230. package/dist/subscriptions/launch.d.ts +37 -0
  231. package/dist/subscriptions/launch.js +93 -0
  232. package/dist/subscriptions/login.d.ts +77 -0
  233. package/dist/subscriptions/login.js +215 -0
  234. package/dist/subscriptions/observed.d.ts +7 -0
  235. package/dist/subscriptions/observed.js +44 -0
  236. package/dist/subscriptions/probe.d.ts +10 -0
  237. package/dist/subscriptions/probe.js +73 -0
  238. package/dist/subscriptions/service.d.ts +102 -0
  239. package/dist/subscriptions/service.js +364 -0
  240. package/dist/subscriptions/store.d.ts +71 -0
  241. package/dist/subscriptions/store.js +143 -0
  242. package/dist/supervisor/adoption.d.ts +8 -0
  243. package/dist/supervisor/adoption.js +78 -0
  244. package/dist/supervisor/catalog.d.ts +62 -0
  245. package/dist/supervisor/catalog.js +254 -0
  246. package/dist/supervisor/fleet.d.ts +8 -0
  247. package/dist/supervisor/fleet.js +89 -0
  248. package/dist/supervisor/index.js +3 -6
  249. package/dist/supervisor/launchd.d.ts +1 -1
  250. package/dist/supervisor/launchd.js +26 -13
  251. package/dist/supervisor/legacy.d.ts +35 -0
  252. package/dist/supervisor/legacy.js +524 -0
  253. package/dist/supervisor/manager.d.ts +15 -0
  254. package/dist/supervisor/manager.js +270 -0
  255. package/dist/supervisor/registration-refresh.d.ts +45 -0
  256. package/dist/supervisor/registration-refresh.js +266 -0
  257. package/dist/supervisor/scope.d.ts +4 -0
  258. package/dist/supervisor/scope.js +10 -0
  259. package/dist/supervisor/systemd.d.ts +2 -2
  260. package/dist/supervisor/systemd.js +19 -33
  261. package/dist/task-supervision.d.ts +9 -0
  262. package/dist/task-supervision.js +145 -0
  263. package/dist/temp-idle.d.ts +17 -0
  264. package/dist/temp-idle.js +42 -0
  265. package/dist/temp-lifecycle.d.ts +23 -8
  266. package/dist/temp-lifecycle.js +123 -66
  267. package/dist/temp-supervisor-recovery.d.ts +1 -1
  268. package/dist/temp-supervisor-recovery.js +2 -2
  269. package/dist/tunnel-setup-completion.d.ts +7 -0
  270. package/dist/tunnel-setup-completion.js +13 -0
  271. package/dist/tunnel-setup-input.d.ts +19 -0
  272. package/dist/tunnel-setup-input.js +130 -0
  273. package/dist/watchdog/query.d.ts +3 -1
  274. package/dist/watchdog/query.js +2 -1
  275. package/dist/web/agent-attachments.d.ts +29 -0
  276. package/dist/web/agent-attachments.js +162 -0
  277. package/dist/web/auth.d.ts +65 -2
  278. package/dist/web/auth.js +201 -11
  279. package/dist/web/available-port.d.ts +2 -0
  280. package/dist/web/available-port.js +23 -0
  281. package/dist/web/control.d.ts +3 -3
  282. package/dist/web/control.js +10 -8
  283. package/dist/web/conversation-stream-codec.d.ts +23 -0
  284. package/dist/web/conversation-stream-codec.js +110 -0
  285. package/dist/web/conversation-stream.d.ts +26 -0
  286. package/dist/web/conversation-stream.js +239 -0
  287. package/dist/web/events.d.ts +2 -0
  288. package/dist/web/events.js +9 -0
  289. package/dist/web/fleet-config-service.d.ts +7 -1
  290. package/dist/web/fleet-config-service.js +61 -16
  291. package/dist/web/prefix-gateway.d.ts +32 -0
  292. package/dist/web/prefix-gateway.js +248 -0
  293. package/dist/web/resource-views.d.ts +135 -0
  294. package/dist/web/resource-views.js +494 -0
  295. package/dist/web/runtime.d.ts +3 -1
  296. package/dist/web/runtime.js +132 -34
  297. package/dist/web/server.d.ts +19 -0
  298. package/dist/web/server.js +893 -71
  299. package/dist/web/service.d.ts +11 -2
  300. package/dist/web/service.js +30 -4
  301. package/dist/web/workspace-devices.d.ts +107 -0
  302. package/dist/web/workspace-devices.js +277 -0
  303. package/dist/web/workspace-tunnel.d.ts +1 -0
  304. package/dist/web/workspace-tunnel.js +34 -0
  305. package/dist/workspace-enrollment.d.ts +83 -0
  306. package/dist/workspace-enrollment.js +605 -0
  307. package/dist/workspace-replacement.d.ts +66 -0
  308. package/dist/workspace-replacement.js +238 -0
  309. package/docs/acp-files.md +146 -0
  310. package/docs/conversation-stream.md +216 -0
  311. package/docs/fleet-migration.md +54 -0
  312. package/docs/live-voice.md +13 -0
  313. package/docs/room-layouts.md +227 -0
  314. package/docs/self-reminders.md +374 -0
  315. package/docs/task-progress-review.md +143 -0
  316. package/docs/task-workspaces.md +225 -0
  317. package/docs/validation/managed-cli-permissions.md +421 -0
  318. package/examples/room-layouts/fresh.yaml +27 -0
  319. package/examples/room-layouts/scoped.yaml +21 -0
  320. package/examples/room-layouts/shared.yaml +19 -0
  321. package/package.json +25 -20
  322. package/presets/README.md +78 -0
  323. package/presets/brain-catalog.json +8 -2
  324. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  325. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  326. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  327. package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
  328. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  329. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  330. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  331. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  332. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  333. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  334. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  335. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  336. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  337. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  338. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  340. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  341. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  342. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  343. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  344. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  345. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  351. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  352. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  353. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  354. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  387. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  396. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  397. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  398. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  399. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  400. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  406. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  407. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  408. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  409. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  410. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  411. package/presets/fleet/roles/Coordinator.yaml +21 -13
  412. package/presets/fleet/roles/Critic.yaml +24 -0
  413. package/presets/fleet/roles/Developer.yaml +32 -0
  414. package/presets/fleet/roles/Engineer.yaml +72 -0
  415. package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
  416. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  417. package/presets/fleet/room_layouts/pair.yaml +24 -0
  418. package/presets/fleet/room_layouts/single.yaml +17 -0
  419. package/presets/fleet/room_layouts/team.yaml +30 -0
  420. package/presets/fleet/room_templates/engineering.yaml +19 -0
  421. package/presets/manifest.json +2 -2
  422. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  423. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  424. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  425. package/dist/web-app/icons/ours-fleet.svg +0 -4
  426. package/dist/web-app/index.html +0 -17
  427. package/dist/web-app/manifest.webmanifest +0 -15
  428. package/dist/web-app/offline.html +0 -18
  429. package/dist/web-app/sw.js +0 -51
package/dist/runner.js CHANGED
@@ -1,22 +1,33 @@
1
+ import { readProvenance } from './creation.js';
2
+ import { firstManagedSessionMayStart, managedRecoveryPath, readManagedRecovery, writeManagedRecovery } from './managed-recovery.js';
3
+ import { StartupTelemetry } from './startup-telemetry.js';
4
+ import { managedStartupPrompt, managedTaskPrompt, worklogRecordsPriorWork } from './startup-prompt.js';
5
+ import { createLayoutControl } from './rooms-tasks/layout-control.js';
6
+ import { TemporaryChatIdle } from './temp-idle.js';
7
+ import { taskSupervisorMayRun, taskMemberRetirementProof } from './task-supervision.js';
1
8
  import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
2
9
  import { prepareManagedHarness } from './agent-ours/harness.js';
10
+ import { prepareManagedCliLaunch } from './managed-cli.js';
3
11
  import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, realpathSync } from 'node:fs';
4
12
  import { join } from 'node:path';
5
13
  import { fileURLToPath } from 'node:url';
6
14
  import { randomUUID } from 'node:crypto';
7
15
  import { parse } from 'yaml';
8
16
  import { agentDir, stateRoot } from './paths.js';
9
- import { loadConfig, findRole, isolationContextFor, resolveMonitorConfig, resolvePermissions, } from './config.js';
17
+ import { loadConfig, findRole, isolationContextFor, resolveSnapshotMonitor, resolvePermissions, } from './config.js';
10
18
  import { getAdapter } from './harness/registry.js';
11
19
  import { createMonitor, probeIdentityPresence, } from './monitor.js';
12
- import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
20
+ import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, acquireSelectedDaemonHealth, } from './daemon-recovery.js';
13
21
  import { realExec } from './exec.js';
14
22
  import { resolveIsolation } from './isolation/policy.js';
23
+ import { pinSubscriptionForLaunch } from './subscriptions/launch.js';
15
24
  import { selectIsolationBackend } from './isolation/registry.js';
16
25
  import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
17
26
  import { resolveLaunchRuntime } from './isolation/runtime.js';
18
27
  import { controlRequest, RoleControlServer } from './session/control.js';
19
28
  import { ACP_CANCEL_DEADLINE_EXCEEDED, CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED, classifyShellStatus, } from './session/types.js';
29
+ import { sessionBackendCapabilities } from './session/types.js';
30
+ import { bindRoleReminders } from './reminders/runtime.js';
20
31
  import { effectiveModelForRole, modelRecoveryHeld, reconcileModelRecovery, recordModelFailure, classifyFailureText, } from './model-recovery.js';
21
32
  import { rotateWorklog } from './worklog.js';
22
33
  import { reconcilePermanentRoleIdentities } from './creation.js';
@@ -27,7 +38,7 @@ import { ScheduledLoopManager, } from './loops/manager.js';
27
38
  import { FLEET_PROXY_CALLER_ENV, FLEET_PROXY_STATE_DIR_ENV, } from './fleet-proxy.js';
28
39
  import { effectivePermissionMode } from './permissions.js';
29
40
  import { assertModelPinReachesChild, effectiveRoleModel, repinModelEnv } from './model-env.js';
30
- import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, } from './temp-lifecycle.js';
41
+ import { archiveTempState, markTempSupervisorActive, requestedTempStopReason, readTempSupervisor, } from './temp-lifecycle.js';
31
42
  import { FleetCommandAuditStore } from './fleet-command-audit.js';
32
43
  export const SUPERVISOR_RECYCLE_REQUIRED = 'OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED';
33
44
  export class SupervisorRecycleRequiredError extends Error {
@@ -37,6 +48,7 @@ export class SupervisorRecycleRequiredError extends Error {
37
48
  this.name = 'SupervisorRecycleRequiredError';
38
49
  }
39
50
  }
51
+ const defaultProbeGeneration = (env) => probeDaemonGeneration((url, init) => globalThis.fetch(url, init), env);
40
52
  const defaultDeps = () => ({
41
53
  prepareAgentOurs: prepareManagedAgent,
42
54
  releaseAgentOurs: releaseManagedAgent,
@@ -53,7 +65,7 @@ const defaultDeps = () => ({
53
65
  now: () => Date.now(),
54
66
  log: line => process.stderr.write(line + '\n'),
55
67
  fetch: (url, init) => globalThis.fetch(url, init),
56
- probeGeneration: env => probeDaemonGeneration((url, init) => globalThis.fetch(url, init), env),
68
+ probeGeneration: defaultProbeGeneration,
57
69
  createMonitor: opts => createMonitor(opts),
58
70
  createOwnerChannel: opts => new OwnerChannel(opts),
59
71
  startAgentSession: (adapter, options) => adapter.start(options),
@@ -75,12 +87,16 @@ const defaultDeps = () => ({
75
87
  });
76
88
  const MONITOR_OWNER_FILE = '.monitor-owner';
77
89
  const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
78
- function localFleetAuditor(stateDir, caller, log) {
90
+ export function localFleetAuditor(stateDir, caller, log, session) {
91
+ const bindings = new Map();
79
92
  const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
80
93
  return {
81
94
  async begin(requestId, argv) {
82
95
  let attempt = store.begin(requestId, caller, argv);
83
96
  if (attempt.invocation === 'sending') {
97
+ const binding = session.taskNoticeBinding?.();
98
+ if (binding)
99
+ bindings.set(attempt.correlationId, binding);
84
100
  log(`[${caller}] fleet proxy command ${attempt.correlationId} `
85
101
  + `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
86
102
  attempt = store.invocation(attempt.correlationId, caller, 'delivered');
@@ -94,6 +110,19 @@ function localFleetAuditor(stateDir, caller, log) {
94
110
  ...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
95
111
  ...(input.presentations ? { presentations: input.presentations } : {}),
96
112
  });
113
+ const binding = bindings.get(attempt.correlationId);
114
+ if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
115
+ for (const p of attempt.outcome.presentations ?? [])
116
+ if (p.kind === 'task' && p.operation === 'create') {
117
+ try {
118
+ session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
119
+ }
120
+ catch {
121
+ log(`[${caller}] task-created conversation notice could not be persisted`);
122
+ }
123
+ }
124
+ bindings.delete(attempt.correlationId);
125
+ }
97
126
  if (attempt.outcome?.delivery === 'sending')
98
127
  attempt = store.outcome(attempt.correlationId, caller, 'delivered');
99
128
  return attempt;
@@ -300,6 +329,11 @@ const carriedForward = (previous) => ({
300
329
  ...(previous.abruptTerminations ? { abruptTerminations: previous.abruptTerminations } : {}),
301
330
  ...(previous.supervisorStartedAt ? { supervisorStartedAt: previous.supervisorStartedAt } : {}),
302
331
  });
332
+ function unrefTimer(fn, ms) {
333
+ const timer = setTimeout(fn, ms);
334
+ timer.unref?.();
335
+ return () => clearTimeout(timer);
336
+ }
303
337
  /** Bounded exponential backoff for the nth consecutive immediate failure. */
304
338
  export function backoffFor(consecutiveFailures) {
305
339
  if (consecutiveFailures <= 0)
@@ -414,7 +448,7 @@ export function loadTempRole(name) {
414
448
  // Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
415
449
  // with no monitor block keeps the historical native/no-supervisor behavior.
416
450
  if (role.monitor)
417
- role.monitor = resolveMonitorConfig(undefined, role.monitor);
451
+ role.monitor = resolveSnapshotMonitor(role.monitor);
418
452
  role.__temp = true;
419
453
  return role;
420
454
  }
@@ -427,7 +461,7 @@ export function loadTempRole(name) {
427
461
  * with "no such role". An empty/missing marker means "use the default", same as
428
462
  * no -c was ever given.
429
463
  */
430
- function resolveConfigPath(dir, explicit) {
464
+ export function resolveConfigPath(dir, explicit) {
431
465
  if (explicit)
432
466
  return explicit;
433
467
  const marker = join(dir, '.config-path');
@@ -448,6 +482,8 @@ export function isRecoverableTempStartupCancellation(temp, result) {
448
482
  /** One session lifecycle. `runSupervised` (or a one-shot caller) drives it. */
449
483
  export async function runOnce(name, opts = {}, partialDeps = {}) {
450
484
  const deps = { ...defaultDeps(), ...partialDeps };
485
+ const startupTelemetry = new StartupTelemetry(deps.now, line => deps.log(`[${name}] ${line}`));
486
+ startupTelemetry.mark('supervisor_started');
451
487
  const temp = opts.temp === true;
452
488
  const dir = agentDir(name, temp);
453
489
  const configPath = temp ? opts.configPath : resolveConfigPath(dir, opts.configPath);
@@ -486,14 +522,43 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
486
522
  deps.log(`[${name}] worklog rotation deferred: concurrent modification detected`);
487
523
  else if (rotation.rotated)
488
524
  deps.log(`[${name}] worklog rotated: ${rotation.beforeBytes} -> ${rotation.afterBytes} bytes`);
525
+ const durableTask = Boolean(readTempSupervisor(dir)?.taskOwner);
526
+ const retainedManaged = durableTask || Boolean(deps.suspendOnStop && adapter.supportsResume);
489
527
  const sidFile = join(dir, '.session-id');
528
+ const backendSidFile = join(dir, role.session === 'codex-app-server' ? '.session-id' : '.acp-session-id');
529
+ let recovery = retainedManaged ? readManagedRecovery(dir) : undefined;
530
+ // Native Codex shares .session-id with the runner's pre-launch UUID, so file
531
+ // existence alone is not evidence that its first backend thread was created.
532
+ const firstSessionRetry = retainedManaged && existsSync(join(dir, '.booted'))
533
+ && (!existsSync(backendSidFile) || (role.session === 'codex-app-server' && recovery?.session === 'pending'))
534
+ && firstManagedSessionMayStart(dir);
535
+ if (retainedManaged && existsSync(join(dir, '.booted'))
536
+ && (!existsSync(sidFile) || !existsSync(backendSidFile)) && !firstSessionRetry)
537
+ throw Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
490
538
  if (!existsSync(sidFile))
491
539
  writeFileSync(sidFile, randomUUID() + '\n');
492
540
  const sessionId = readFileSync(sidFile, 'utf8').trim();
493
541
  const bootedFile = join(dir, '.booted');
494
542
  const exitFile = join(dir, '.exit-status');
495
543
  const booted = existsSync(bootedFile);
496
- const mode = booted && adapter.supportsResume ? 'resume' : 'fresh';
544
+ if (durableTask && !adapter.supportsResume)
545
+ throw Error('TASK_HARNESS_RESUME_UNSUPPORTED');
546
+ const mode = booted && adapter.supportsResume && !firstSessionRetry ? 'resume' : 'fresh';
547
+ const requireResume = retainedManaged && mode === 'resume';
548
+ if (retainedManaged && !recovery) {
549
+ // Older admitted sessions have no cursor. Continue once, without replaying
550
+ // their assignment; fresh sessions record intent before the first attempt.
551
+ recovery = { version: 1, session: booted ? 'established' : 'pending', readiness: booted,
552
+ initial: booted ? 'dispatching' : 'pending', active: booted };
553
+ writeManagedRecovery(dir, recovery);
554
+ }
555
+ const recoveryBefore = recovery ? { ...recovery } : undefined;
556
+ const saveRecovery = (update) => {
557
+ if (!recovery)
558
+ return;
559
+ recovery = { ...recovery, ...update };
560
+ writeManagedRecovery(dir, recovery);
561
+ };
497
562
  // Stamp EVERY attempt, not just the first. `.booted` used to be written only
498
563
  // on the fresh path, so after a restart — including one the supervisor never
499
564
  // saw, like an OOM-kill — its mtime still read the original boot and any
@@ -502,10 +567,22 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
502
567
  writeFileSync(bootedFile, `${new Date(deps.now()).toISOString()} ${mode}\n`);
503
568
  const runCwd = role.cwd && existsSync(role.cwd) ? role.cwd : dir;
504
569
  role = { ...role, monitor: { ...role.monitor, mode: 'fleet' } };
505
- const prep = await adapter.prepareSession(role, { stateDir: dir, runCwd });
570
+ // Native policy for Fleet commands run from the harness command sandbox. This
571
+ // is the same step for configured, spawned and task-room agents alike.
572
+ const prep = prepareManagedCliLaunch(role, {
573
+ // A temporary agent has no config path threaded through its detached
574
+ // supervisor; spawn records the one it was created from beside its snapshot.
575
+ stateDir: dir, runCwd, configPath: configPath ?? resolveConfigPath(dir), temp,
576
+ prep: await adapter.prepareSession(role, { stateDir: dir, runCwd }), log: deps.log,
577
+ });
506
578
  const sessionBackend = role.session ?? 'acp';
507
579
  const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
508
580
  let launch = adapter.agentSession.prepareLaunch(role, prep);
581
+ // Resolve the fleet-wide active subscription profile once, now: a later switch
582
+ // never changes this session (the operator restarts agents to move them).
583
+ const subscription = pinSubscriptionForLaunch(role, dir);
584
+ if (subscription?.pin.home)
585
+ deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
509
586
  // Preserve the role's existing isolation policy.
510
587
  let wrappedArgv = launch.argv;
511
588
  if (role.isolation) {
@@ -516,6 +593,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
516
593
  launch = { ...launch, argv: runtime.argv };
517
594
  const ctx = {
518
595
  ...isolationContextFor(role), stateDir: dir, runCwd,
596
+ subscriptionHome: subscription?.writablePath,
519
597
  runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
520
598
  };
521
599
  const policy = resolveIsolation(role.isolation, ctx);
@@ -538,9 +616,16 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
538
616
  if (rprefix.length)
539
617
  wrappedArgv = [...rprefix, ...wrappedArgv];
540
618
  }
619
+ startupTelemetry.mark('identity_prepare_started');
541
620
  const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
621
+ startupTelemetry.mark('identity_ready');
622
+ let layoutRetiring = false;
623
+ // Self-reminders live and die with this attempt's runtime binding: every exit
624
+ // path below stops the dispatcher before that binding is released.
625
+ let reminderManager;
626
+ let selectedHealth;
542
627
  try {
543
- const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
628
+ const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
544
629
  // Start-stagger: space this launch at least start_stagger_ms after the previous
545
630
  // agent launch across the whole host, so a burst of boots (systemd starts every
546
631
  // user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
@@ -556,10 +641,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
556
641
  }
557
642
  }
558
643
  // Prime the supervisor mail monitor's notification cursor at the
559
- // stream tip BEFORE the session launches so no arrival is missed during boot
560
- // (backlog before the tip is the SessionStart hook's job). Native-mode roles
561
- // leave wake ownership to the harness. Temp snapshots predating `monitor:` are
562
- // treated as native (monitor may be undefined on an old role.yaml).
644
+ // stream tip BEFORE the session launches so no arrival is missed during boot.
645
+ // Monitor.run checks the body-free initial unread summary after readiness;
646
+ // managed native hooks do not own backlog delivery.
563
647
  let sessionHandle;
564
648
  let modelRecovery;
565
649
  const resolvedMonitorDeps = monitorDeps(deps, role.env);
@@ -587,8 +671,15 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
587
671
  name, identity: role.identity, agentDir: dir, cfg: role.monitor,
588
672
  deps: resolvedMonitorDeps,
589
673
  }) : null;
674
+ // Injected legacy probes keep their test/embedding contract. Production uses
675
+ // one retained owner for generation and identity health in this process.
676
+ const retainHealth = deps.probeGeneration === defaultProbeGeneration
677
+ && !deps.identityProbeDeps ? acquireSelectedDaemonHealth : undefined;
678
+ selectedHealth = retainHealth?.(resolvedMonitorDeps.env);
590
679
  const daemonObserver = new DaemonGenerationObserver();
591
- const initialDaemon = daemonObserver.observe(await deps.probeGeneration(resolvedMonitorDeps.env));
680
+ const initialDaemon = daemonObserver.observe(selectedHealth
681
+ ? (await selectedHealth.sample()).generation
682
+ : await deps.probeGeneration(resolvedMonitorDeps.env));
592
683
  let sessionStartedWithoutDaemonBaseline = initialDaemon.kind !== 'baseline';
593
684
  if (monitor)
594
685
  await monitor.prime({ resetCursor: resetMonitorCursor });
@@ -599,6 +690,20 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
599
690
  let unsubscribeRecovery;
600
691
  let monitorLoop;
601
692
  let sessionStartupComplete = false;
693
+ let startupRecoveryAllowed = false;
694
+ let successfulTurnObserved = false;
695
+ let monitorMayStart = false;
696
+ let monitorStarted = false;
697
+ const startMonitorAfterStartup = () => {
698
+ if (!monitorStarted && monitorMayStart && sessionStartupComplete && !deps.shouldStop?.()) {
699
+ monitorStarted = true;
700
+ monitorLoop = monitor?.run(pid);
701
+ }
702
+ // Reminder wakes follow the same gate as mail wakes: never into a role
703
+ // whose protected startup turn has not succeeded.
704
+ if (monitorMayStart && sessionStartupComplete && !deps.shouldStop?.())
705
+ reminderManager?.start();
706
+ };
602
707
  let sessionClosed = false;
603
708
  let ownerChannel;
604
709
  let ownerBinder;
@@ -617,23 +722,66 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
617
722
  if (perms.unattended === 'deny')
618
723
  deps.log(`[${name}] permission policy: unattended=deny — with no console attached, ` +
619
724
  `permission requests are automatically denied once each (reject_once) and the turn continues`);
725
+ startupTelemetry.mark('harness_starting');
620
726
  agentSession = await managedService.runtime.startHarness(() => deps.startAgentSession(adapter.agentSession, {
621
727
  managedOurs: managedHarness.ours,
622
728
  role, prep,
623
729
  launch: { ...launch, argv: wrappedArgv, env: managedHarness.env },
624
- cwd: runCwd, stateDir: dir, mode, permissions: perms,
730
+ cwd: runCwd, stateDir: dir, mode, requireResume, deferRecoveredPrompts: true, permissions: perms,
625
731
  permissionMode: effectivePermissionMode(role), log: deps.log,
626
732
  }));
733
+ managedService.setFileDelivery?.(agentSession.sendFileToChat?.bind(agentSession));
734
+ startupTelemetry.mark('harness_ready');
735
+ saveRecovery({ session: 'established' });
627
736
  pid = agentSession.pid;
628
737
  arbiter = new RoleTurnArbiter(agentSession);
629
738
  sessionHandle = arbiter;
630
- unsubscribeRecovery = agentSession.subscribe(event => {
739
+ reminderManager = bindRoleReminders({
740
+ role: name, stateDir: dir, runtime: managedService.setReminders ? managedService.runtime.snapshot : {},
741
+ promptInput: sessionBackendCapabilities(sessionBackend, role.harness).promptInput,
742
+ admitter: arbiter, service: managedService,
743
+ deps: {
744
+ now: deps.now, log: deps.log,
745
+ setTimer: (callback, ms) => setTimeout(callback, ms),
746
+ clearTimer: timer => clearTimeout(timer),
747
+ },
748
+ });
749
+ const unsubscribeFailure = agentSession.subscribe(event => {
631
750
  if (event.kind !== 'error' || !event.text || event.origin?.kind === 'stall-watchdog')
632
751
  return;
633
752
  const evidence = classifyFailureText(event.text, sessionBackend, new Date(deps.now()).toISOString());
634
753
  if (evidence)
635
754
  resolvedMonitorDeps.onFailureEvidence?.(evidence);
636
755
  });
756
+ const unsubscribeTelemetry = agentSession.subscribeConversation?.(event => startupTelemetry.observe(event));
757
+ const unsubscribeStartup = agentSession.subscribeConversation?.(event => {
758
+ if (event.kind !== 'turn.completed' || !('outcome' in event.payload) || event.payload.outcome !== 'completed')
759
+ return;
760
+ successfulTurnObserved = true;
761
+ if (startupRecoveryAllowed) {
762
+ sessionStartupComplete = true;
763
+ startMonitorAfterStartup();
764
+ }
765
+ });
766
+ const activePrompts = new Set();
767
+ const unsubscribeContinuity = agentSession.subscribeConversation?.(event => {
768
+ if (!recovery?.readiness)
769
+ return;
770
+ if (event.kind === 'prompt.started') {
771
+ if (event.promptId)
772
+ activePrompts.add(event.promptId);
773
+ saveRecovery({ active: true });
774
+ }
775
+ else if (event.kind === 'turn.completed' && 'outcome' in event.payload) {
776
+ if (event.promptId)
777
+ activePrompts.delete(event.promptId);
778
+ // A shutdown/failed turn needs continuation; a successful terminal turn
779
+ // proves it may stay idle. Never store prompt bodies in this cursor.
780
+ if (event.payload.outcome === 'completed')
781
+ saveRecovery({ active: activePrompts.size > 0 });
782
+ }
783
+ });
784
+ unsubscribeRecovery = () => { unsubscribeFailure(); unsubscribeStartup?.(); unsubscribeTelemetry?.(); unsubscribeContinuity?.(); };
637
785
  if (role.owner_channel) {
638
786
  try {
639
787
  ownerBinder = await deps.acquireOwnerBinder(dir, name, role.owner_channel.identity);
@@ -670,11 +818,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
670
818
  ...(configPath ? { configPath } : {}),
671
819
  });
672
820
  control = deps.createControlServer(dir, arbiter, deps.log);
821
+ control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
673
822
  control.setFleetAuditor(ownerChannel ? {
674
823
  begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
675
824
  finish: input => ownerChannel.finishFleetCommandAudit(input),
676
825
  present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
677
- } : localFleetAuditor(dir, name, deps.log));
826
+ } : localFleetAuditor(dir, name, deps.log, agentSession));
827
+ control.setLayoutControl?.(createLayoutControl({
828
+ agent: name, temporary: temp, standalone: !role.roomMemberStartup,
829
+ runtime: managedService.runtime, session: arbiter,
830
+ stopping: () => Boolean(deps.shouldStop?.()),
831
+ retire: async () => { layoutRetiring = true; await arbiter.close(); },
832
+ }));
678
833
  await control.start();
679
834
  }
680
835
  catch (error) {
@@ -700,15 +855,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
700
855
  }
701
856
  return event;
702
857
  });
858
+ // Recovered prompts may run the managed Fleet CLI immediately. Start them
859
+ // only now that the control socket, audit and spawn handlers are live, and
860
+ // never for a role being stopped: they stay durable for its next start.
861
+ if (!deps.shouldStop?.())
862
+ agentSession.activateRecoveredPrompts?.();
703
863
  resolvedMonitorDeps.delivery = {
704
864
  // A wake is only delivered when its turn TERMINATES successfully. A
705
865
  // refusal or a cancellation reached the agent and was not acted on, so
706
866
  // the monitor must keep its cursor and try again.
707
867
  submit: async (text, options) => {
708
- // Cancelling the runner-owned startup prompt makes startup look failed
709
- // and closes the session before the wake turn can run. During startup,
710
- // steer into the live turn instead; after it completes, honor the
711
- // configured interrupt policy normally.
868
+ // Delivery starts after the protected startup turn succeeds. Honor the
869
+ // configured interrupt policy for subsequent turns.
712
870
  const policy = options?.interrupt;
713
871
  const interrupt = policy === true && sessionStartupComplete;
714
872
  const promptOptions = {
@@ -716,8 +874,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
716
874
  ...(interrupt ? { interruptSource: 'fleet-monitor' } : {}),
717
875
  origin: { kind: 'fleet-monitor' },
718
876
  };
719
- // Startup is already a protected boundary: as with immediate mode,
720
- // steer rather than waiting on/cancelling the runner-owned first turn.
877
+ // after_tool waits for a safe boundary in subsequent active turns.
721
878
  const result = policy === 'after_tool' && sessionStartupComplete
722
879
  ? await arbiter.submitPromptAfterTool(text, promptOptions)
723
880
  : await arbiter.submitPrompt(text, promptOptions);
@@ -746,17 +903,23 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
746
903
  };
747
904
  },
748
905
  };
749
- const firstPrompt = mode === 'fresh'
750
- ? `Read and follow ${join(dir, 'briefing.md')} now.`
751
- : `Your supervisor has verified your assigned identity and room readiness. Read ${join(dir, 'WORKLOG.md')} and ${join(dir, 'briefing.md')}, then continue using the available ours tools.`;
906
+ // The worklog is continuity input only when it records work. It outlives `.booted`,
907
+ // which a rotation to a fresh conversation clears, so the log itself is the evidence.
908
+ const priorWork = worklogRecordsPriorWork(dir);
909
+ const firstPrompt = managedStartupPrompt(dir, mode, priorWork);
752
910
  // Wait for the first turn's TERMINAL result. An agent that accepts the
753
911
  // startup prompt and then refuses it has not started; logging the role as
754
- // up would hide a role that never read its briefing.
755
- const starting = arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
756
- // Monitoring starts immediately. The delivery adapter above downgrades
757
- // interruption to steering until this startup turn reaches a terminal
758
- // success, so there is neither a deaf gap nor a boot-cancellation loop.
759
- monitorLoop = monitor?.run(pid);
912
+ // up would hide a role that never completed its readiness turn.
913
+ const retainedResume = retainedManaged && mode === 'resume' && recoveryBefore?.readiness === true;
914
+ if (!retainedResume)
915
+ startupTelemetry.mark('startup_submitted');
916
+ // The backend already verified the saved conversation. Resuming a managed
917
+ // member admits new wakes/console input without replaying startup or work.
918
+ const starting = retainedResume
919
+ ? Promise.resolve({ accepted: false, succeeded: true, outcome: 'completed', detail: 'retained backend resumed; no readiness prompt submitted' })
920
+ : arbiter.submitPrompt(firstPrompt, { origin: { kind: 'startup' } });
921
+ // Keep the early cursor baseline; do not deliver wakes into the protected
922
+ // first turn. Steering can pre-empt startup just like cancellation.
760
923
  // Owner traffic and agent replies must be observed during a long first
761
924
  // turn. Ordinary owner input steers until startup has proved successful.
762
925
  if (ownerChannel) {
@@ -778,6 +941,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
778
941
  }
779
942
  }
780
943
  const started = await starting;
944
+ startupTelemetry.finish();
781
945
  // A temporary role's first turn can be the active turn when an ours wake
782
946
  // needs immediate attention. A typed console/monitor cancellation ends
783
947
  // only that turn: the already-live agent session and any queued wake remain
@@ -816,7 +980,38 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
816
980
  else if (interruptedForWake)
817
981
  deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
818
982
  + 'keeping temporary supervisor alive');
819
- sessionStartupComplete = true;
983
+ startupRecoveryAllowed = interruptedForWake;
984
+ sessionStartupComplete = started.succeeded || (interruptedForWake && successfulTurnObserved);
985
+ if (started.succeeded && !retainedResume)
986
+ startupTelemetry.mark('readiness_turn_completed');
987
+ if (started.succeeded)
988
+ saveRecovery({ readiness: true });
989
+ // Readiness is now complete. Configured work (or retained continuity) is
990
+ // admitted as a distinct turn; its terminal result must not block console
991
+ // readiness, mail delivery, or supervisor liveness. Empty fresh agents wait
992
+ // for authenticated console input or the monitor's unread/arrival wake.
993
+ const configuredWork = Boolean(role.mission?.trim() || role.roomMemberStartup?.task?.trim() || role.briefing_file?.trim());
994
+ const initialPending = retainedManaged && recoveryBefore?.initial === 'pending';
995
+ const interruptedWork = retainedManaged && (recoveryBefore?.active || recoveryBefore?.initial === 'dispatching');
996
+ const workPrompt = retainedManaged
997
+ ? initialPending && configuredWork ? managedTaskPrompt(dir, 'fresh', false)
998
+ : interruptedWork ? 'Fleet supervisor restarted. Continue pending work in the restored conversation; do not redo completed steps. '
999
+ + (recoveryBefore?.initial === 'dispatching' ? `If you have not yet received your assignment, read ${join(dir, 'briefing.md')} once before acting. ` : '')
1000
+ + 'Read the continuation log only as needed to recover your next action. If no work remains, end this turn and wait for a concrete task or supervisor wake.' : undefined
1001
+ : configuredWork || booted || priorWork ? managedTaskPrompt(dir, mode, priorWork) : undefined;
1002
+ if (started.succeeded && !deps.shouldStop?.() && workPrompt) {
1003
+ saveRecovery({ initial: 'dispatching', active: true });
1004
+ const queued = await arbiter.queuePrompt(workPrompt, {
1005
+ origin: { kind: 'startup' },
1006
+ });
1007
+ void queued.completion.then(result => {
1008
+ if (result.succeeded)
1009
+ saveRecovery({ initial: 'completed', active: activePrompts.size > 0 });
1010
+ deps.log(`[${name}] configured task turn ${result.outcome}`);
1011
+ }, () => deps.log(`[${name}] configured task turn failed`));
1012
+ }
1013
+ else if (started.succeeded && initialPending && !configuredWork)
1014
+ saveRecovery({ initial: 'completed' });
820
1015
  reloadLoopConfig = async () => {
821
1016
  const nextRole = findRole(loadConfig(configPath), name);
822
1017
  const definitions = nextRole.loops ?? [];
@@ -873,9 +1068,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
873
1068
  }
874
1069
  }
875
1070
  deps.log(`[${name}] up; pid=${pid} cwd=${runCwd} harness=${role.harness} session=${sessionBackend} mode=${mode}`);
876
- // The monitor loop lives exactly as long as the session: it starts once the
877
- // pane pid is known and is stopped when that pid dies (task dies with runner).
878
- monitorLoop ??= monitor?.run(pid);
1071
+ const healthyAt = deps.now();
1072
+ opts.onHealthy?.();
1073
+ // Keep arrivals behind the early prime() cursor; never re-prime at startup.
1074
+ // Recoverable cancellation preserves the supervisor, not monitor readiness.
1075
+ monitorMayStart = true;
1076
+ startMonitorAfterStartup();
879
1077
  recoveryController = new RoleRecoveryController({
880
1078
  role: name, identity: role.identity, stateDir: dir, now: deps.now, sleep: deps.sleep,
881
1079
  log: deps.log,
@@ -902,6 +1100,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
902
1100
  }
903
1101
  },
904
1102
  });
1103
+ let chatIdle;
1104
+ try {
1105
+ if (temp && !role.roomMemberStartup && !readTempSupervisor(dir)?.taskOwner)
1106
+ chatIdle = new TemporaryChatIdle(dir, deps.now());
1107
+ }
1108
+ catch {
1109
+ deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
1110
+ }
905
1111
  const start = deps.now();
906
1112
  let nextLoopReloadAt = deps.now() + 30_000;
907
1113
  let nextIdentityPollAt = deps.now();
@@ -916,7 +1122,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
916
1122
  const now = deps.now();
917
1123
  if (now >= nextDaemonProbeAt) {
918
1124
  nextDaemonProbeAt = now + 2_000;
919
- const observation = daemonObserver.observe(await deps.probeGeneration(resolvedMonitorDeps.env));
1125
+ const observation = daemonObserver.observe(selectedHealth
1126
+ ? (await selectedHealth.sample()).generation
1127
+ : await deps.probeGeneration(resolvedMonitorDeps.env));
920
1128
  if (observation.kind === 'lost')
921
1129
  recoveryController.noteLoss(observation.reason);
922
1130
  if (observation.kind === 'changed' || observation.kind === 'available'
@@ -925,7 +1133,21 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
925
1133
  void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
926
1134
  }
927
1135
  }
1136
+ let idleExpired = false;
1137
+ try {
1138
+ idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
1139
+ }
1140
+ catch {
1141
+ chatIdle = undefined;
1142
+ deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
1143
+ }
1144
+ if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
1145
+ retirementReason = 'idle-timeout';
1146
+ sessionClosed = true;
1147
+ break;
1148
+ }
928
1149
  if (deps.shouldStop?.()) {
1150
+ reminderManager?.stop();
929
1151
  if (temp) {
930
1152
  retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
931
1153
  deps.log(`[${name}] temporary supervisor retirement requested (${retirementReason})`);
@@ -936,21 +1158,25 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
936
1158
  }
937
1159
  if (temp && now >= nextIdentityPollAt) {
938
1160
  nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
939
- const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env);
1161
+ const presence = selectedHealth
1162
+ ? selectedHealth.presence(role.identity, await selectedHealth.sample())
1163
+ : await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
1164
+ const identityObservedAt = deps.now();
940
1165
  if (presence.state === 'present') {
941
1166
  identityObserved = true;
942
1167
  identityAbsentSince = undefined;
943
1168
  }
944
1169
  else if (presence.state === 'absent' && identityObserved) {
945
- identityAbsentSince ??= now;
1170
+ identityAbsentSince ??= identityObservedAt;
946
1171
  // Require a continuous, time-bounded run of authoritative absence.
947
1172
  // The first positive observation is the readiness gate: cold harness
948
1173
  // starts may spend minutes loading the harness and briefing before the
949
1174
  // agent creates/binds its identity, and absence before then is not a
950
1175
  // close event. After readiness, debounce a real disappearance.
951
- if (now - identityAbsentSince >= TEMP_IDENTITY_CLOSE_DEBOUNCE_MS) {
1176
+ if (identityObservedAt - identityAbsentSince >= TEMP_IDENTITY_CLOSE_DEBOUNCE_MS) {
952
1177
  retirementReason = 'identity-closed';
953
1178
  deps.log(`[${name}] temporary identity '${role.identity}' closed; retiring session and supervisor`);
1179
+ reminderManager?.stop();
954
1180
  await sessionHandle.close();
955
1181
  sessionClosed = true;
956
1182
  break;
@@ -973,6 +1199,8 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
973
1199
  }
974
1200
  }
975
1201
  }
1202
+ monitorMayStart = false;
1203
+ reminderManager?.stop();
976
1204
  if (loopManager) {
977
1205
  control?.setLoopManager(undefined);
978
1206
  await loopManager.stop();
@@ -1018,11 +1246,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
1018
1246
  const rotate = (why) => {
1019
1247
  writeFileSync(sidFile, randomUUID() + '\n');
1020
1248
  rmSync(bootedFile, { force: true });
1249
+ rmSync(managedRecoveryPath(dir), { force: true });
1021
1250
  rotated = true;
1022
1251
  deps.log(`[${name}] ${why} -> rotated session-id; next start is FRESH`);
1023
1252
  };
1024
- if (!temp && deps.shouldStop?.())
1025
- deps.log(`[${name}] supervisor stop requested -> next start RESUMES context`);
1253
+ if (readTempSupervisor(dir)?.taskOwner || ((!temp || deps.suspendOnStop) && deps.shouldStop?.()))
1254
+ deps.log(`[${name}] supervisor/context retained -> next start RESUMES context`);
1026
1255
  else if (exitRecord.detail.includes(ACP_CANCEL_DEADLINE_EXCEEDED)
1027
1256
  || exitRecord.detail.includes(CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED))
1028
1257
  // This is a deliberate adapter reclamation, not evidence that resume state
@@ -1053,11 +1282,15 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
1053
1282
  throw new SupervisorRecycleRequiredError();
1054
1283
  return {
1055
1284
  elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
1056
- ...(retirementReason ? { retirementReason } : {}),
1285
+ healthySecs: (deps.now() - healthyAt) / 1000,
1286
+ ...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
1057
1287
  };
1058
1288
  }
1059
1289
  finally {
1060
- await managedService.close(false);
1290
+ selectedHealth?.release();
1291
+ reminderManager?.stop();
1292
+ managedService.setReminders?.(undefined);
1293
+ await managedService.close(layoutRetiring);
1061
1294
  }
1062
1295
  }
1063
1296
  /**
@@ -1072,11 +1305,13 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
1072
1305
  */
1073
1306
  export async function runSupervised(name, opts = {}, partialDeps = {}, attempt = runOnce) {
1074
1307
  const deps = { ...defaultDeps(), ...partialDeps };
1075
- const dir = agentDir(name);
1308
+ const taskMember = opts.temp === true;
1309
+ const dir = agentDir(name, taskMember);
1076
1310
  mkdirSync(dir, { recursive: true });
1077
1311
  let stopping = false;
1078
1312
  const requestStop = () => { stopping = true; };
1079
- const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false);
1313
+ const shouldStop = () => stopping || (partialDeps.shouldStop?.() ?? false)
1314
+ || (taskMember && requestedTempStopReason(dir) === 'operator-stop');
1080
1315
  deps.shouldStop = shouldStop;
1081
1316
  const stamp = () => new Date(deps.now()).toISOString();
1082
1317
  // Record how the PREVIOUS supervisor process ended before doing anything
@@ -1104,10 +1339,12 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1104
1339
  process.on('SIGINT', requestStop);
1105
1340
  try {
1106
1341
  while (!shouldStop()) {
1342
+ if (taskMember && !taskSupervisorMayRun(name))
1343
+ break;
1107
1344
  let ledger = readRestartLedger(dir);
1108
1345
  try {
1109
1346
  const configPath = resolveConfigPath(dir, opts.configPath);
1110
- const role = findRole(loadConfig(configPath), name);
1347
+ const role = taskMember ? loadTempRole(name) : findRole(loadConfig(configPath), name);
1111
1348
  reconcileModelRecovery(dir, role, stamp());
1112
1349
  if (modelRecoveryHeld(dir)) {
1113
1350
  await deps.sleep(HELD_DOWN_POLL_MS);
@@ -1123,20 +1360,74 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1123
1360
  await deps.sleep(HELD_DOWN_POLL_MS);
1124
1361
  continue;
1125
1362
  }
1363
+ if (attempt === runOnce && taskMember) {
1364
+ // Boot-time daemon unavailability must not consume the harness crash budget.
1365
+ const { waitForRoleDaemon } = await import('./startup-readiness.js');
1366
+ await waitForRoleDaemon(name, undefined, {}, true);
1367
+ }
1368
+ if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
1369
+ break;
1370
+ if (attempt === runOnce && taskMember) {
1371
+ // Cowork can come up after the daemon on boot. Wait read-only so a
1372
+ // gateway still starting its upstream does not spend the failure budget.
1373
+ const { waitForTaskRoomManagement } = await import('./startup-readiness.js');
1374
+ await waitForTaskRoomManagement(name, { shouldStop });
1375
+ }
1376
+ if (taskMember && (shouldStop() || !taskSupervisorMayRun(name)))
1377
+ break;
1378
+ const fastFailSecs = taskMember ? tempFastFailSecs(name) : fastFailSecsFor(name, opts.configPath);
1379
+ // The fast-fail boundary starts a recovery episode; it must not also be
1380
+ // the boundary that declares recovery successful. Otherwise alternating
1381
+ // 19s and 20s deaths erase one another forever. Require the configured
1382
+ // number of fast-fail windows to survive before closing an active streak.
1383
+ // This hysteresis stays adapter-relative (100s for the current 20s/5-attempt
1384
+ // policy) and still lets a genuinely sustained session reset the breaker.
1385
+ const stableRecoverySecs = fastFailSecs * RESTART_FAIL_THRESHOLD;
1386
+ // A session that stays up that long has recovered, even if it never
1387
+ // returns: a host reset or OOM-kill ends it without reaching the
1388
+ // post-attempt reset below, and a stale streak would then hold the role
1389
+ // down after a few transient boot failures spread over days.
1390
+ const streakAtStart = ledger;
1391
+ const clearSustainedStreak = () => {
1392
+ if (streakAtStart.consecutiveImmediateFailures === 0)
1393
+ return;
1394
+ const current = readRestartLedger(dir);
1395
+ // Never overwrite an operator reset, an open circuit or a successor's state.
1396
+ if (current.circuit === 'open' || current.updatedAt !== streakAtStart.updatedAt
1397
+ || current.consecutiveImmediateFailures !== streakAtStart.consecutiveImmediateFailures
1398
+ || readRunMarker(dir)?.startedAt !== startedAt)
1399
+ return;
1400
+ writeRestartLedger(dir, {
1401
+ ...emptyLedger(),
1402
+ ...carriedForward(current),
1403
+ lastReason: `sustained healthy session (${stableRecoverySecs}s)`,
1404
+ updatedAt: stamp(),
1405
+ });
1406
+ deps.log(`[${name}] healthy for ${stableRecoverySecs}s; cleared `
1407
+ + `${current.consecutiveImmediateFailures} earlier immediate failure(s)`);
1408
+ };
1409
+ let cancelStabilityTimer;
1410
+ const onHealthy = () => {
1411
+ if (cancelStabilityTimer || streakAtStart.consecutiveImmediateFailures === 0)
1412
+ return;
1413
+ cancelStabilityTimer = (deps.setTimer ?? unrefTimer)(clearSustainedStreak, stableRecoverySecs * 1000);
1414
+ };
1126
1415
  let result;
1127
1416
  try {
1128
1417
  // Service-manager boot and automatic retry bypass the operator-facing
1129
1418
  // up/restart commands. Reconcile again immediately before every real
1130
1419
  // permanent harness attempt; the operation is idempotent and releases its
1131
1420
  // provisioning lease before the agent or owner channel binds.
1132
- if (attempt === runOnce) {
1421
+ if (attempt === runOnce && !taskMember) {
1133
1422
  const configPath = resolveConfigPath(dir, opts.configPath);
1134
1423
  const role = findRole(loadConfig(configPath), name);
1135
1424
  await reconcilePermanentRoleIdentities(role, undefined, deps.log);
1136
1425
  }
1137
- result = await attempt(name, { configPath: opts.configPath, allowResumeRotation: !ledger.resumeDiscarded }, deps);
1426
+ result = await attempt(name, { configPath: opts.configPath, ...(taskMember ? { temp: true } : {}),
1427
+ allowResumeRotation: taskMember ? false : !ledger.resumeDiscarded, onHealthy }, deps);
1138
1428
  }
1139
1429
  catch (e) {
1430
+ cancelStabilityTimer?.();
1140
1431
  if (e instanceof SupervisorRecycleRequiredError) {
1141
1432
  writeRestartLedger(dir, {
1142
1433
  ...ledger,
@@ -1156,8 +1447,14 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1156
1447
  mode: 'fresh',
1157
1448
  };
1158
1449
  }
1159
- if (stopping)
1450
+ cancelStabilityTimer?.();
1451
+ if (stopping || (taskMember && (shouldStop() || result.retirementReason))) {
1452
+ // An orderly stop of a session that was healthy long enough is a
1453
+ // recovery too; only the timer's race with the stop is decided here.
1454
+ if ((result.healthySecs ?? 0) >= stableRecoverySecs)
1455
+ clearSustainedStreak();
1160
1456
  break;
1457
+ }
1161
1458
  // Re-read: the attempt itself may have taken minutes, and an operator may
1162
1459
  // have reset the ledger meanwhile.
1163
1460
  ledger = readRestartLedger(dir);
@@ -1174,14 +1471,6 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1174
1471
  await deps.sleep(HELD_DOWN_POLL_MS);
1175
1472
  continue;
1176
1473
  }
1177
- const fastFailSecs = fastFailSecsFor(name, opts.configPath);
1178
- // The fast-fail boundary starts a recovery episode; it must not also be
1179
- // the boundary that declares recovery successful. Otherwise alternating
1180
- // 19s and 20s deaths erase one another forever. Require the configured
1181
- // number of fast-fail windows to survive before closing an active streak.
1182
- // This hysteresis stays adapter-relative (100s for the current 20s/5-attempt
1183
- // policy) and still lets a genuinely sustained session reset the breaker.
1184
- const stableRecoverySecs = fastFailSecs * RESTART_FAIL_THRESHOLD;
1185
1474
  const recoveryFailed = result.elapsedSecs < fastFailSecs
1186
1475
  || (ledger.consecutiveImmediateFailures > 0 && result.elapsedSecs < stableRecoverySecs);
1187
1476
  if (!recoveryFailed) {
@@ -1211,7 +1500,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1211
1500
  next.nextDelayMs = 0;
1212
1501
  writeRestartLedger(dir, next);
1213
1502
  deps.log(`[${name}] HELD DOWN after ${failures} immediate failures at ${next.openedAt} — ` +
1214
- `${reason}; the agent will not be restarted until: ours-fleet restart ${name}`);
1503
+ `${reason}; recover with: ${taskMember ? `ours-fleet task recover-members ${readTempSupervisor(dir).taskOwner.taskId}` : `ours-fleet restart ${name}`}`);
1215
1504
  continue;
1216
1505
  }
1217
1506
  writeRestartLedger(dir, next);
@@ -1226,7 +1515,7 @@ export async function runSupervised(name, opts = {}, partialDeps = {}, attempt =
1226
1515
  process.off('SIGINT', requestStop);
1227
1516
  // An orderly shutdown clears the marker; an unhandled signal or OOM-kill
1228
1517
  // leaves it so the successor can identify an abrupt termination.
1229
- if (shouldStop())
1518
+ if (!taskMember && shouldStop())
1230
1519
  await deps.releaseAgentOurs(findRole(loadConfig(opts.configPath), name));
1231
1520
  releaseSupervisorRun(dir);
1232
1521
  }
@@ -1245,9 +1534,67 @@ function fastFailSecsFor(name, configPath) {
1245
1534
  return 20;
1246
1535
  }
1247
1536
  }
1537
+ function tempFastFailSecs(name) {
1538
+ try {
1539
+ return getAdapter(loadTempRole(name).harness).exitPolicy.fastFailSecs;
1540
+ }
1541
+ catch {
1542
+ return 20;
1543
+ }
1544
+ }
1248
1545
  /** Temp-agent entrypoint: run once, journal why it ended, then archive its evidence. */
1546
+ /** The parent uses the same adapter-relative sustained-uptime reset boundary. */
1547
+ export function stableSupervisorWindow(name, temp, configPath) {
1548
+ try {
1549
+ const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
1550
+ return getAdapter(role.harness).exitPolicy.fastFailSecs * RESTART_FAIL_THRESHOLD;
1551
+ }
1552
+ catch {
1553
+ return 20 * RESTART_FAIL_THRESHOLD;
1554
+ }
1555
+ }
1249
1556
  export async function runTemp(name, deps = {}, attempt = runOnce) {
1250
1557
  const dir = agentDir(name, true);
1558
+ const metadata = readTempSupervisor(dir);
1559
+ // Non-resumable standalone temporaries retain their prior finite lifecycle:
1560
+ // a parent stop retires them rather than promising retained conversation.
1561
+ if (!metadata?.taskOwner && deps.suspendOnStop && !getAdapter(loadTempRole(name).harness).supportsResume)
1562
+ deps = { ...deps, suspendOnStop: false };
1563
+ if (!metadata?.taskOwner) {
1564
+ const role = loadTempRole(name);
1565
+ const provenance = readProvenance(dir);
1566
+ if (role.roomMemberStartup?.task_id
1567
+ || (name.startsWith('layout-') && provenance?.creationActionId?.startsWith('task-')))
1568
+ throw Error('TASK_SUPERVISOR_OWNER_MISSING');
1569
+ }
1570
+ if (metadata?.taskOwner) {
1571
+ // Serialize the entire process lifetime before metadata/session writes.
1572
+ const lease = await acquireOwnerBinderLease(join(dir, '.task-supervisor'), 'task-supervisor', name);
1573
+ let ownershipVerified = false;
1574
+ try {
1575
+ const mayRun = taskSupervisorMayRun(name);
1576
+ ownershipVerified = true;
1577
+ if (!mayRun)
1578
+ return;
1579
+ await markTempSupervisorActive(dir);
1580
+ await runSupervised(name, { temp: true }, deps, attempt);
1581
+ }
1582
+ finally {
1583
+ try {
1584
+ if (ownershipVerified && requestedTempStopReason(dir)) {
1585
+ // Parent shutdown suspends; an explicit stop retires the exact saved
1586
+ // owner even when no harness attempt ran (for example held circuits).
1587
+ await (deps.releaseAgentOurs ?? releaseManagedAgent)(loadTempRole(name), taskMemberRetirementProof(name));
1588
+ const archived = archiveTempState(name, 'operator-stop', 'retired', 'explicit task member retirement');
1589
+ deps.log?.(`[${name}] task member retired: operator-stop${archived ? `; evidence archived at ${archived}` : ''}`);
1590
+ }
1591
+ }
1592
+ finally {
1593
+ lease.release();
1594
+ }
1595
+ }
1596
+ return;
1597
+ }
1251
1598
  await markTempSupervisorActive(dir);
1252
1599
  let signal;
1253
1600
  const onTerm = () => { signal = 'SIGTERM'; };
@@ -1274,7 +1621,8 @@ export async function runTemp(name, deps = {}, attempt = runOnce) {
1274
1621
  process.off('SIGTERM', onTerm);
1275
1622
  process.off('SIGINT', onInt);
1276
1623
  // A recycle replaces the supervisor process, not the logical temporary agent.
1277
- if (!(failure instanceof SupervisorRecycleRequiredError) || signal || deps.shouldStop?.()) {
1624
+ if (!((signal || deps.shouldStop?.()) && deps.suspendOnStop && !requestedTempStopReason(dir))
1625
+ && (!(failure instanceof SupervisorRecycleRequiredError) || signal || deps.shouldStop?.())) {
1278
1626
  const requested = requestedTempStopReason(dir);
1279
1627
  const reason = requested
1280
1628
  ?? result?.retirementReason