@ours.network/fleet 1.2.0-nightly.7 → 1.2.0-nightly.70

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