@ours.network/fleet 1.2.0-nightly.4 → 1.2.0-nightly.40

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 (310) hide show
  1. package/README.md +165 -53
  2. package/dist/account-origin.d.ts +2 -0
  3. package/dist/account-origin.js +6 -0
  4. package/dist/agent-ours/bridge.js +9 -0
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/harness.js +8 -0
  8. package/dist/agent-ours/runtime.d.ts +6 -0
  9. package/dist/agent-ours/runtime.js +36 -0
  10. package/dist/agent-ours/service.js +33 -13
  11. package/dist/application/codex-models.d.ts +15 -0
  12. package/dist/application/codex-models.js +56 -0
  13. package/dist/application/conversation-history.d.ts +25 -0
  14. package/dist/application/conversation-history.js +189 -0
  15. package/dist/application/fleet-query-service.d.ts +1 -1
  16. package/dist/application/fleet-query-service.js +2 -2
  17. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  18. package/dist/application/legacy-supervisor-identity.js +43 -0
  19. package/dist/application/model-catalog.d.ts +1 -1
  20. package/dist/application/model-catalog.js +5 -2
  21. package/dist/application/preset-provenance.d.ts +19 -0
  22. package/dist/application/preset-provenance.js +59 -0
  23. package/dist/application/role-creation-service.js +1 -1
  24. package/dist/application/room-layout-definitions.d.ts +45 -0
  25. package/dist/application/room-layout-definitions.js +130 -0
  26. package/dist/application/session-control.d.ts +18 -0
  27. package/dist/application/session-control.js +54 -3
  28. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  29. package/dist/application/supervisor-ours-tools.js +113 -0
  30. package/dist/application/task-layouts.d.ts +91 -0
  31. package/dist/application/task-layouts.js +249 -0
  32. package/dist/application/task-room-service.d.ts +99 -3
  33. package/dist/application/task-room-service.js +296 -31
  34. package/dist/atomic-file.d.ts +5 -0
  35. package/dist/atomic-file.js +62 -1
  36. package/dist/build-info.json +6 -4
  37. package/dist/capabilities.d.ts +3 -1
  38. package/dist/capabilities.js +4 -0
  39. package/dist/cli.js +186 -34
  40. package/dist/client-profile.d.ts +5 -16
  41. package/dist/client-profile.js +4 -112
  42. package/dist/config.d.ts +13 -0
  43. package/dist/config.js +19 -4
  44. package/dist/creation.js +2 -2
  45. package/dist/daemon-recovery.d.ts +1 -8
  46. package/dist/daemon-recovery.js +8 -113
  47. package/dist/docs.d.ts +3 -3
  48. package/dist/docs.js +44 -22
  49. package/dist/doctor.d.ts +1 -1
  50. package/dist/doctor.js +46 -59
  51. package/dist/erased-resources.d.ts +9 -0
  52. package/dist/erased-resources.js +87 -0
  53. package/dist/fleet-command-audit.d.ts +1 -0
  54. package/dist/fleet-command-audit.js +11 -6
  55. package/dist/harness/claude-code.js +9 -1
  56. package/dist/harness/claude-runtime.d.ts +12 -0
  57. package/dist/harness/claude-runtime.js +43 -0
  58. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  59. package/dist/harness/codex-app-server-proxy.js +41 -5
  60. package/dist/harness/codex-runtime.d.ts +11 -1
  61. package/dist/harness/codex-runtime.js +31 -4
  62. package/dist/harness/codex.d.ts +3 -2
  63. package/dist/harness/codex.js +8 -5
  64. package/dist/init-wizard.d.ts +2 -0
  65. package/dist/init-wizard.js +38 -19
  66. package/dist/isolation/policy.js +2 -0
  67. package/dist/isolation/types.d.ts +5 -0
  68. package/dist/minimal-setup.d.ts +2 -0
  69. package/dist/minimal-setup.js +44 -0
  70. package/dist/monitor.d.ts +1 -34
  71. package/dist/monitor.js +24 -151
  72. package/dist/notifications/fleet-producer.d.ts +18 -0
  73. package/dist/notifications/fleet-producer.js +129 -0
  74. package/dist/notifications/outbox.d.ts +42 -0
  75. package/dist/notifications/outbox.js +149 -0
  76. package/dist/owner-channel/channel.d.ts +3 -0
  77. package/dist/owner-channel/channel.js +34 -6
  78. package/dist/owner-channel/commands.d.ts +4 -0
  79. package/dist/owner-channel/commands.js +8 -0
  80. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  81. package/dist/owner-channel/ours-client.js +2 -17
  82. package/dist/paths.d.ts +2 -0
  83. package/dist/paths.js +2 -0
  84. package/dist/preset-migration.js +25 -0
  85. package/dist/rooms-tasks/add-member.d.ts +48 -0
  86. package/dist/rooms-tasks/add-member.js +83 -0
  87. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  88. package/dist/rooms-tasks/archived-absence.js +48 -0
  89. package/dist/rooms-tasks/cli.js +34 -5
  90. package/dist/rooms-tasks/close.d.ts +5 -2
  91. package/dist/rooms-tasks/close.js +208 -28
  92. package/dist/rooms-tasks/config.d.ts +1 -1
  93. package/dist/rooms-tasks/config.js +11 -4
  94. package/dist/rooms-tasks/cowork-adapter.d.ts +6 -4
  95. package/dist/rooms-tasks/cowork-adapter.js +11 -117
  96. package/dist/rooms-tasks/cowork-http.d.ts +3 -0
  97. package/dist/rooms-tasks/cowork-http.js +70 -0
  98. package/dist/rooms-tasks/deletion.d.ts +1 -1
  99. package/dist/rooms-tasks/deletion.js +74 -5
  100. package/dist/rooms-tasks/erasure.d.ts +6 -0
  101. package/dist/rooms-tasks/erasure.js +169 -0
  102. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  103. package/dist/rooms-tasks/external-worker.js +3 -4
  104. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  105. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  106. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  107. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  108. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  109. package/dist/rooms-tasks/layout-cli.js +107 -0
  110. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  111. package/dist/rooms-tasks/layout-config.js +89 -0
  112. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  113. package/dist/rooms-tasks/layout-control.js +52 -0
  114. package/dist/rooms-tasks/layout-service.d.ts +42 -0
  115. package/dist/rooms-tasks/layout-service.js +185 -0
  116. package/dist/rooms-tasks/layout.d.ts +114 -0
  117. package/dist/rooms-tasks/layout.js +250 -0
  118. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  119. package/dist/rooms-tasks/live-readiness.js +85 -0
  120. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  121. package/dist/rooms-tasks/member-startup.js +2 -0
  122. package/dist/rooms-tasks/provision.d.ts +14 -0
  123. package/dist/rooms-tasks/provision.js +173 -46
  124. package/dist/rooms-tasks/room-state.d.ts +4 -2
  125. package/dist/rooms-tasks/room-state.js +53 -8
  126. package/dist/rooms-tasks/task-state.d.ts +42 -7
  127. package/dist/rooms-tasks/task-state.js +153 -23
  128. package/dist/rooms-tasks/types.d.ts +28 -0
  129. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  130. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  131. package/dist/rooms-tasks/workspace.d.ts +26 -0
  132. package/dist/rooms-tasks/workspace.js +195 -0
  133. package/dist/runner.d.ts +19 -3
  134. package/dist/runner.js +89 -29
  135. package/dist/session/acp.d.ts +7 -1
  136. package/dist/session/acp.js +21 -6
  137. package/dist/session/arbiter.d.ts +3 -0
  138. package/dist/session/arbiter.js +20 -2
  139. package/dist/session/codex-app-server.d.ts +3 -0
  140. package/dist/session/codex-app-server.js +26 -3
  141. package/dist/session/control.d.ts +10 -1
  142. package/dist/session/control.js +37 -2
  143. package/dist/session/conversation-normalizer.d.ts +2 -1
  144. package/dist/session/conversation-normalizer.js +1 -1
  145. package/dist/session/conversation-types.d.ts +5 -3
  146. package/dist/session/task-notice.d.ts +14 -0
  147. package/dist/session/task-notice.js +13 -0
  148. package/dist/session/types.d.ts +3 -0
  149. package/dist/setup-tunnel-preflight.d.ts +32 -0
  150. package/dist/setup-tunnel-preflight.js +58 -0
  151. package/dist/socket-path.d.ts +2 -0
  152. package/dist/socket-path.js +22 -0
  153. package/dist/spawn.js +7 -0
  154. package/dist/startup-readiness.d.ts +13 -0
  155. package/dist/startup-readiness.js +38 -0
  156. package/dist/subscriptions/cli.d.ts +91 -0
  157. package/dist/subscriptions/cli.js +223 -0
  158. package/dist/subscriptions/launch.d.ts +37 -0
  159. package/dist/subscriptions/launch.js +93 -0
  160. package/dist/subscriptions/login.d.ts +75 -0
  161. package/dist/subscriptions/login.js +182 -0
  162. package/dist/subscriptions/observed.d.ts +7 -0
  163. package/dist/subscriptions/observed.js +44 -0
  164. package/dist/subscriptions/probe.d.ts +10 -0
  165. package/dist/subscriptions/probe.js +73 -0
  166. package/dist/subscriptions/service.d.ts +102 -0
  167. package/dist/subscriptions/service.js +364 -0
  168. package/dist/subscriptions/store.d.ts +71 -0
  169. package/dist/subscriptions/store.js +140 -0
  170. package/dist/supervisor/launchd.js +2 -1
  171. package/dist/supervisor/systemd.js +9 -23
  172. package/dist/temp-idle.d.ts +17 -0
  173. package/dist/temp-idle.js +42 -0
  174. package/dist/temp-lifecycle.d.ts +6 -1
  175. package/dist/temp-lifecycle.js +29 -13
  176. package/dist/web/agent-attachments.d.ts +29 -0
  177. package/dist/web/agent-attachments.js +162 -0
  178. package/dist/web/auth.d.ts +29 -1
  179. package/dist/web/auth.js +81 -7
  180. package/dist/web/available-port.d.ts +2 -0
  181. package/dist/web/available-port.js +23 -0
  182. package/dist/web/control.d.ts +3 -3
  183. package/dist/web/control.js +10 -8
  184. package/dist/web/fleet-config-service.d.ts +4 -1
  185. package/dist/web/fleet-config-service.js +46 -15
  186. package/dist/web/prefix-gateway.d.ts +29 -0
  187. package/dist/web/prefix-gateway.js +206 -0
  188. package/dist/web/runtime.d.ts +3 -1
  189. package/dist/web/runtime.js +116 -31
  190. package/dist/web/server.d.ts +16 -0
  191. package/dist/web/server.js +501 -53
  192. package/dist/web/service.d.ts +11 -2
  193. package/dist/web/service.js +30 -4
  194. package/dist/web/workspace-devices.d.ts +29 -0
  195. package/dist/web/workspace-devices.js +84 -0
  196. package/dist/web/workspace-tunnel.d.ts +1 -0
  197. package/dist/web/workspace-tunnel.js +32 -0
  198. package/dist/workspace-enrollment.d.ts +53 -0
  199. package/dist/workspace-enrollment.js +370 -0
  200. package/docs/room-layouts.md +227 -0
  201. package/docs/task-workspaces.md +130 -0
  202. package/examples/room-layouts/fresh.yaml +27 -0
  203. package/examples/room-layouts/scoped.yaml +21 -0
  204. package/examples/room-layouts/shared.yaml +19 -0
  205. package/package.json +13 -18
  206. package/presets/README.md +78 -0
  207. package/presets/brain-catalog.json +8 -2
  208. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  209. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  210. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  211. package/presets/fleet/agent_templates/LocalCoordinator.yaml +1 -1
  212. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  213. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  214. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  215. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  216. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  217. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  218. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  219. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  220. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  221. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  222. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  223. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  224. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  225. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  226. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  227. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  228. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  229. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  230. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  231. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  232. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  233. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  234. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  235. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  236. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  237. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  238. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  239. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  240. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  241. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  242. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  243. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  244. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  245. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  246. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  247. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  248. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  249. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  250. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  251. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  252. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  253. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  254. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  255. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  256. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  257. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  258. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  259. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  260. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  261. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  262. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  263. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  264. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  265. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  266. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  267. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  268. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  269. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  270. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  271. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  272. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  273. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  274. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  275. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  276. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  277. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  278. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  279. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  280. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  281. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  282. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  283. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  284. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  285. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  286. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  287. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  288. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  289. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  290. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  291. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  292. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  293. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  294. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  295. package/presets/fleet/roles/Coordinator.yaml +34 -11
  296. package/presets/fleet/roles/Engineer.yaml +40 -0
  297. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  298. package/presets/fleet/room_layouts/pair.yaml +24 -0
  299. package/presets/fleet/room_layouts/single.yaml +17 -0
  300. package/presets/fleet/room_layouts/team.yaml +30 -0
  301. package/presets/fleet/room_templates/engineering.yaml +19 -0
  302. package/presets/manifest.json +2 -2
  303. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  304. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  305. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  306. package/dist/web-app/icons/ours-fleet.svg +0 -4
  307. package/dist/web-app/index.html +0 -17
  308. package/dist/web-app/manifest.webmanifest +0 -15
  309. package/dist/web-app/offline.html +0 -18
  310. package/dist/web-app/sw.js +0 -51
package/dist/runner.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
2
2
  import { type ResolvedRole } from './config.js';
3
- import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
3
+ import { probeIdentityPresence, type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
4
4
  import { type DaemonGenerationProbe } from './daemon-recovery.js';
5
5
  import { type Exec } from './exec.js';
6
6
  import { RoleControlServer } from './session/control.js';
@@ -20,6 +20,7 @@ export interface RunnerDeps {
20
20
  log(line: string): void;
21
21
  /** HTTP transport for the monitor's daemon long-poll (injectable for tests). */
22
22
  fetch: FetchLike;
23
+ identityProbeDeps?: Parameters<typeof probeIdentityPresence>[3];
23
24
  probeGeneration(env: NodeJS.ProcessEnv): Promise<DaemonGenerationProbe>;
24
25
  /** Construct the supervisor mail monitor (injectable so tests stub it out). */
25
26
  createMonitor(opts: MonitorOpts): MonitorHandle;
@@ -29,7 +30,7 @@ export interface RunnerDeps {
29
30
  /** Neutral construction seam for deterministic runner lifecycle tests. */
30
31
  startAgentSession(adapter: AgentSessionAdapter, options: AgentSessionStartOptions): Promise<AgentSession>;
31
32
  /** Construct the authenticated role control route (injectable where sockets are unavailable). */
32
- createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'>;
33
+ createControlServer(stateDir: string, session: AgentSession, log: (line: string) => void): Pick<RoleControlServer, 'start' | 'close' | 'setFleetSpawner' | 'setFleetAuditor' | 'setOwnerChannel' | 'setConfigReloader' | 'setLoopManager'> & Partial<Pick<RoleControlServer, 'setLayoutControl' | 'setCorrespondence'>>;
33
34
  /** Construct scheduled-loop execution (injectable for fail-closed startup tests). */
34
35
  createLoopManager(...args: ConstructorParameters<typeof ScheduledLoopManager>): ScheduledLoopManagerHandle;
35
36
  /** Acquire the cross-process owner-channel binder lease before replacing the control socket. */
@@ -44,6 +45,11 @@ export declare class SupervisorRecycleRequiredError extends Error {
44
45
  readonly code = "OWNER_CHANNEL_SUPERVISOR_RECYCLE_REQUIRED";
45
46
  constructor();
46
47
  }
48
+ export declare function localFleetAuditor(stateDir: string, caller: string, log: (line: string) => void, session: AgentSession): {
49
+ begin(requestId: string, argv: string[]): Promise<import("./fleet-command-audit.js").FleetAuditAttempt>;
50
+ finish(input: Parameters<NonNullable<OwnerChannelHandle["finishFleetCommandAudit"]>>[0]): Promise<import("./fleet-command-audit.js").FleetAuditAttempt>;
51
+ present(): Promise<void>;
52
+ };
47
53
  /** Environment injected only into the managed harness process. */
48
54
  export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
49
55
  /**
@@ -149,6 +155,16 @@ export declare const START_STAGGER_FILE = ".start-stagger-ms";
149
155
  export declare function reserveLaunchSlot(root: string, staggerMs: number, deps: Pick<RunnerDeps, 'now' | 'sleep' | 'log'>): Promise<number>;
150
156
  /** Read a temp role's config snapshot written by spawnTemp. */
151
157
  export declare function loadTempRole(name: string): ResolvedRole;
158
+ /**
159
+ * Fall back to the config path applyRole() recorded at the last up/restart/spawn
160
+ * for this role. systemd's shared unit template (`ours-fleet-agent@.service`)
161
+ * execs `_run <name>` with no -c on every restart, so a role brought up from a
162
+ * non-default config (`ours-fleet up -c custom.yaml`) would otherwise silently
163
+ * resolve against the default ~/fleet.yaml on its very first restart and fail
164
+ * with "no such role". An empty/missing marker means "use the default", same as
165
+ * no -c was ever given.
166
+ */
167
+ export declare function resolveConfigPath(dir: string, explicit?: string): string | undefined;
152
168
  /** What one child session did, so the supervising loop can decide what follows. */
153
169
  export interface AttemptResult {
154
170
  elapsedSecs: number;
@@ -158,7 +174,7 @@ export interface AttemptResult {
158
174
  mode: 'fresh' | 'resume';
159
175
  modelRecovery?: 'advance' | 'hold';
160
176
  /** Present only when a temporary-role lifecycle signal ended the session. */
161
- retirementReason?: 'identity-closed' | 'operator-stop' | 'supervisor-signal';
177
+ retirementReason?: 'identity-closed' | 'operator-stop' | 'supervisor-signal' | 'idle-timeout';
162
178
  }
163
179
  /** Continuous authoritative absence required after an identity was observed. */
164
180
  export declare const TEMP_IDENTITY_CLOSE_DEBOUNCE_MS = 5000;
package/dist/runner.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { createLayoutControl } from './rooms-tasks/layout-control.js';
2
+ import { TemporaryChatIdle } from './temp-idle.js';
1
3
  import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
2
4
  import { prepareManagedHarness } from './agent-ours/harness.js';
3
5
  import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, realpathSync } from 'node:fs';
@@ -12,6 +14,7 @@ import { createMonitor, probeIdentityPresence, } from './monitor.js';
12
14
  import { DaemonGenerationObserver, RoleRecoveryController, probeDaemonGeneration, } from './daemon-recovery.js';
13
15
  import { realExec } from './exec.js';
14
16
  import { resolveIsolation } from './isolation/policy.js';
17
+ import { pinSubscriptionForLaunch } from './subscriptions/launch.js';
15
18
  import { selectIsolationBackend } from './isolation/registry.js';
16
19
  import { resourceArgs, cpuControllerDelegated } from './isolation/resources.js';
17
20
  import { resolveLaunchRuntime } from './isolation/runtime.js';
@@ -75,12 +78,16 @@ const defaultDeps = () => ({
75
78
  });
76
79
  const MONITOR_OWNER_FILE = '.monitor-owner';
77
80
  const OBSOLETE_OURS_AUTOSTART_ENV = 'OURS_AUTOSTART';
78
- function localFleetAuditor(stateDir, caller, log) {
81
+ export function localFleetAuditor(stateDir, caller, log, session) {
82
+ const bindings = new Map();
79
83
  const store = new FleetCommandAuditStore(join(stateDir, '.fleet-command-audit.json'));
80
84
  return {
81
85
  async begin(requestId, argv) {
82
86
  let attempt = store.begin(requestId, caller, argv);
83
87
  if (attempt.invocation === 'sending') {
88
+ const binding = session.taskNoticeBinding?.();
89
+ if (binding)
90
+ bindings.set(attempt.correlationId, binding);
84
91
  log(`[${caller}] fleet proxy command ${attempt.correlationId} `
85
92
  + `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
86
93
  attempt = store.invocation(attempt.correlationId, caller, 'delivered');
@@ -94,6 +101,19 @@ function localFleetAuditor(stateDir, caller, log) {
94
101
  ...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
95
102
  ...(input.presentations ? { presentations: input.presentations } : {}),
96
103
  });
104
+ const binding = bindings.get(attempt.correlationId);
105
+ if (binding && attempt.outcome?.class === 'success' && attempt.outcome.effect === 'completed') {
106
+ for (const p of attempt.outcome.presentations ?? [])
107
+ if (p.kind === 'task' && p.operation === 'create') {
108
+ try {
109
+ session.recordTaskCreated?.(binding, { operationId: p.eventId, taskId: p.id, title: p.title ?? p.id, state: p.newState });
110
+ }
111
+ catch {
112
+ log(`[${caller}] task-created conversation notice could not be persisted`);
113
+ }
114
+ }
115
+ bindings.delete(attempt.correlationId);
116
+ }
97
117
  if (attempt.outcome?.delivery === 'sending')
98
118
  attempt = store.outcome(attempt.correlationId, caller, 'delivered');
99
119
  return attempt;
@@ -412,9 +432,12 @@ export function loadTempRole(name) {
412
432
  throw new Error(`temp role '${name}' has no snapshot at ${p}`);
413
433
  const role = parse(readFileSync(p, 'utf8'));
414
434
  // Upgrade snapshots written before monitor.mode/interrupt existed. A snapshot
415
- // with no monitor block keeps the historical native/no-supervisor behavior.
435
+ // with no monitor block keeps the historical native/no-supervisor behavior,
436
+ // and one without interrupt keeps the non-interrupting delivery it ran with.
416
437
  if (role.monitor)
417
- role.monitor = resolveMonitorConfig(undefined, role.monitor);
438
+ role.monitor = resolveMonitorConfig(undefined, {
439
+ interrupt: false, ...role.monitor,
440
+ });
418
441
  role.__temp = true;
419
442
  return role;
420
443
  }
@@ -427,7 +450,7 @@ export function loadTempRole(name) {
427
450
  * with "no such role". An empty/missing marker means "use the default", same as
428
451
  * no -c was ever given.
429
452
  */
430
- function resolveConfigPath(dir, explicit) {
453
+ export function resolveConfigPath(dir, explicit) {
431
454
  if (explicit)
432
455
  return explicit;
433
456
  const marker = join(dir, '.config-path');
@@ -506,6 +529,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
506
529
  const sessionBackend = role.session ?? 'acp';
507
530
  const sessionLabel = sessionBackend === 'acp' ? 'ACP' : 'Codex app-server';
508
531
  let launch = adapter.agentSession.prepareLaunch(role, prep);
532
+ // Resolve the fleet-wide active subscription profile once, now: a later switch
533
+ // never changes this session (the operator restarts agents to move them).
534
+ const subscription = pinSubscriptionForLaunch(role, dir);
535
+ if (subscription?.pin.home)
536
+ deps.log(`[${name}] subscription: ${subscription.pin.provider} profile ${subscription.pin.profileId}`);
509
537
  // Preserve the role's existing isolation policy.
510
538
  let wrappedArgv = launch.argv;
511
539
  if (role.isolation) {
@@ -516,6 +544,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
516
544
  launch = { ...launch, argv: runtime.argv };
517
545
  const ctx = {
518
546
  ...isolationContextFor(role), stateDir: dir, runCwd,
547
+ subscriptionHome: subscription?.writablePath,
519
548
  runtimeReadPaths: [...runtime.readPaths, ...resolveLaunchRuntime([process.execPath, fileURLToPath(new URL('./agent-ours/bridge.js', import.meta.url))]).readPaths],
520
549
  };
521
550
  const policy = resolveIsolation(role.isolation, ctx);
@@ -539,8 +568,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
539
568
  wrappedArgv = [...rprefix, ...wrappedArgv];
540
569
  }
541
570
  const managedService = await deps.prepareAgentOurs(role, dir, opts.identityLifetime ? opts.identityLifetime === 'temporary' : temp);
571
+ let layoutRetiring = false;
542
572
  try {
543
- const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, harnessChildEnv(role, launch.env, dir));
573
+ const managedHarness = prepareManagedHarness(role, dir, runCwd, managedService.descriptor, { ...harnessChildEnv(role, launch.env, dir), ...subscription?.env });
544
574
  // Start-stagger: space this launch at least start_stagger_ms after the previous
545
575
  // agent launch across the whole host, so a burst of boots (systemd starts every
546
576
  // user unit concurrently on boot; `ours-fleet up`/restart-all bulk-start) does not
@@ -662,6 +692,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
662
692
  harness: role.harness,
663
693
  config: role.owner_channel,
664
694
  session: arbiter,
695
+ startupPending: () => !sessionStartupComplete,
665
696
  stateDir: dir,
666
697
  env: role.env,
667
698
  log: deps.log,
@@ -669,11 +700,18 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
669
700
  ...(configPath ? { configPath } : {}),
670
701
  });
671
702
  control = deps.createControlServer(dir, arbiter, deps.log);
703
+ control.setCorrespondence?.({ contacts: () => managedService.runtime.readContacts(), history: query => managedService.runtime.readHistory(query) });
672
704
  control.setFleetAuditor(ownerChannel ? {
673
705
  begin: (requestId, argv) => ownerChannel.beginFleetCommandAudit(requestId, argv),
674
706
  finish: input => ownerChannel.finishFleetCommandAudit(input),
675
707
  present: presentations => ownerChannel.notifyFleetLifecycle(presentations),
676
- } : localFleetAuditor(dir, name, deps.log));
708
+ } : localFleetAuditor(dir, name, deps.log, agentSession));
709
+ control.setLayoutControl?.(createLayoutControl({
710
+ agent: name, temporary: temp, standalone: !role.roomMemberStartup,
711
+ runtime: managedService.runtime, session: arbiter,
712
+ stopping: () => Boolean(deps.shouldStop?.()),
713
+ retire: async () => { layoutRetiring = true; await arbiter.close(); },
714
+ }));
677
715
  await control.start();
678
716
  }
679
717
  catch (error) {
@@ -756,6 +794,26 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
756
794
  // interruption to steering until this startup turn reaches a terminal
757
795
  // success, so there is neither a deaf gap nor a boot-cancellation loop.
758
796
  monitorLoop = monitor?.run(pid);
797
+ // Owner traffic and agent replies must be observed during a long first
798
+ // turn. Ordinary owner input steers until startup has proved successful.
799
+ if (ownerChannel) {
800
+ try {
801
+ await ownerChannel.start();
802
+ control.setOwnerChannel(ownerChannel);
803
+ }
804
+ catch (error) {
805
+ monitor?.stop();
806
+ if (monitorLoop)
807
+ await monitorLoop;
808
+ await control.close();
809
+ await ownerChannel.close().catch(() => undefined);
810
+ ownerBinder?.release();
811
+ await agentSession.close();
812
+ unsubscribeRecovery?.();
813
+ throw new Error(`[${name}] owner channel failed to start: `
814
+ + `${error?.message ?? String(error)}`);
815
+ }
816
+ }
759
817
  const started = await starting;
760
818
  // A temporary role's first turn can be the active turn when an ours wake
761
819
  // needs immediate attention. A typed console/monitor cancellation ends
@@ -768,6 +826,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
768
826
  if (!started.succeeded && !interruptedForWake) {
769
827
  monitor?.stop();
770
828
  await control.close();
829
+ await ownerChannel?.close().catch(() => undefined);
771
830
  ownerBinder?.release();
772
831
  await agentSession.close();
773
832
  unsubscribeRecovery?.();
@@ -795,26 +854,6 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
795
854
  deps.log(`[${name}] ${sessionLabel} startup prompt cancelled by ${started.cancellationSource}; `
796
855
  + 'keeping temporary supervisor alive');
797
856
  sessionStartupComplete = true;
798
- if (ownerChannel) {
799
- try {
800
- await ownerChannel.start();
801
- }
802
- catch (error) {
803
- monitor?.stop();
804
- if (monitorLoop)
805
- await monitorLoop;
806
- await ownerChannel.close().catch(() => undefined);
807
- await control.close();
808
- ownerBinder?.release();
809
- await agentSession.close();
810
- unsubscribeRecovery?.();
811
- throw new Error(`[${name}] owner channel failed to start: `
812
- + `${error?.message ?? String(error)}`);
813
- }
814
- }
815
- if (ownerChannel) {
816
- control.setOwnerChannel(ownerChannel);
817
- }
818
857
  reloadLoopConfig = async () => {
819
858
  const nextRole = findRole(loadConfig(configPath), name);
820
859
  const definitions = nextRole.loops ?? [];
@@ -900,6 +939,14 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
900
939
  }
901
940
  },
902
941
  });
942
+ let chatIdle;
943
+ try {
944
+ if (temp && !role.roomMemberStartup)
945
+ chatIdle = new TemporaryChatIdle(dir, deps.now());
946
+ }
947
+ catch {
948
+ deps.log(`[${name}] automatic idle closure disabled: activity state is unreadable`);
949
+ }
903
950
  const start = deps.now();
904
951
  let nextLoopReloadAt = deps.now() + 30_000;
905
952
  let nextIdentityPollAt = deps.now();
@@ -923,6 +970,19 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
923
970
  void recoveryController.recover(observation.generation).catch(error => deps.log(`[${name}] daemon recovery controller failed: ${error?.name ?? 'Error'}`));
924
971
  }
925
972
  }
973
+ let idleExpired = false;
974
+ try {
975
+ idleExpired = chatIdle?.observe(sessionHandle.snapshot(), sessionHandle.conversationSnapshot?.().queueDepth ?? -1, now) ?? false;
976
+ }
977
+ catch {
978
+ chatIdle = undefined;
979
+ deps.log(`[${name}] automatic idle closure disabled: activity state could not be saved`);
980
+ }
981
+ if (idleExpired && await arbiter.retireIfIdle((snapshot, queue) => chatIdle.observe(snapshot, queue, deps.now()))) {
982
+ retirementReason = 'idle-timeout';
983
+ sessionClosed = true;
984
+ break;
985
+ }
926
986
  if (deps.shouldStop?.()) {
927
987
  if (temp) {
928
988
  retirementReason = requestedTempStopReason(dir) ?? 'supervisor-signal';
@@ -934,7 +994,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
934
994
  }
935
995
  if (temp && now >= nextIdentityPollAt) {
936
996
  nextIdentityPollAt = now + TEMP_IDENTITY_POLL_MS;
937
- const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env);
997
+ const presence = await probeIdentityPresence(role.identity, deps.fetch, resolvedMonitorDeps.env, deps.identityProbeDeps);
938
998
  if (presence.state === 'present') {
939
999
  identityObserved = true;
940
1000
  identityAbsentSince = undefined;
@@ -1051,11 +1111,11 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
1051
1111
  throw new SupervisorRecycleRequiredError();
1052
1112
  return {
1053
1113
  elapsedSecs: elapsed, exit: exitRecord, rotated, mode, modelRecovery,
1054
- ...(retirementReason ? { retirementReason } : {}),
1114
+ ...(layoutRetiring ? { retirementReason: 'operator-stop' } : retirementReason ? { retirementReason } : {}),
1055
1115
  };
1056
1116
  }
1057
1117
  finally {
1058
- await managedService.close(false);
1118
+ await managedService.close(layoutRetiring);
1059
1119
  }
1060
1120
  }
1061
1121
  /**
@@ -1,3 +1,4 @@
1
+ import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
1
2
  import * as acp from '@agentclientprotocol/sdk';
2
3
  import type { CommonPermissions } from '../config.js';
3
4
  import type { AcpMcpServer } from '../harness/types.js';
@@ -338,8 +339,13 @@ export declare class AcpSession implements AgentSession {
338
339
  * signal. Never infer commentary from text, message order, or unknown meta.
339
340
  */
340
341
  private codexMessagePhase;
341
- /** Normalize every ACP update losslessly into the durable ledger. */
342
+ /**
343
+ * Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
344
+ * owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
345
+ */
342
346
  private recordConversationUpdate;
347
+ taskNoticeBinding(): TaskNoticeBinding | undefined;
348
+ recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
343
349
  conversationPage(request?: {
344
350
  after?: string;
345
351
  limit?: number;
@@ -1,3 +1,4 @@
1
+ import { appendTaskCreated } from './task-notice.js';
1
2
  import { spawn } from 'node:child_process';
2
3
  import { createHash, randomUUID } from 'node:crypto';
3
4
  import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
@@ -7,6 +8,7 @@ import * as acp from '@agentclientprotocol/sdk';
7
8
  import { normalizeSessionUpdate } from './conversation-normalizer.js';
8
9
  import { ConversationEventStore } from './conversation-store.js';
9
10
  import { SessionEvents } from './events.js';
11
+ import { recordClaudeRateLimit } from '../subscriptions/observed.js';
10
12
  import { DEFAULT_STALL_TIMEOUT_MS, STALL_RECOVERY_PROMPT, StallWatchdog, StallToolHistory, hasStallRecoveryClaim } from './stall-watchdog.js';
11
13
  import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, sessionBackendCapabilities, turnResult, } from './types.js';
12
14
  const CANCEL_SETTLE_GRACE_MS = 15_000;
@@ -36,7 +38,6 @@ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
36
38
  /** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
37
39
  export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
38
40
  const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
39
- const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
40
41
  const MAX_CANONICAL_SYMLINK_DEPTH = 40;
41
42
  const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
42
43
  /**
@@ -760,6 +761,8 @@ export class AcpSession {
760
761
  const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
761
762
  if (existing)
762
763
  return existing;
764
+ if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
765
+ throw new Error('session_changed: Agent session changed. Record a new voice message.');
763
766
  const queued = await this.queuePrompt(command.text, {
764
767
  origin: { kind: 'owner-admin-console', commandId: command.commandId },
765
768
  actor: { browserSession: command.actorBrowserSession },
@@ -1707,9 +1710,15 @@ export class AcpSession {
1707
1710
  const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
1708
1711
  const messagePhase = update.sessionUpdate === 'agent_message_chunk'
1709
1712
  ? this.codexMessagePhase(update) : undefined;
1710
- this.recordConversationUpdate(update, scheduled, messagePhase === 'commentary');
1713
+ this.recordConversationUpdate(update, scheduled);
1711
1714
  if (update.sessionUpdate === 'config_option_update')
1712
1715
  this.captureRuntimeMetadata(update.configOptions);
1716
+ if (update.sessionUpdate === 'usage_update') {
1717
+ // Claude subscription rate-limit snapshot (claude-agent-acp forwards the SDK rate_limit_event).
1718
+ const rateLimit = update._meta?.['_claude/rateLimit'];
1719
+ if (rateLimit)
1720
+ recordClaudeRateLimit(this.options.stateDir, rateLimit);
1721
+ }
1713
1722
  switch (update.sessionUpdate) {
1714
1723
  case 'agent_message_chunk': {
1715
1724
  const phase = messagePhase;
@@ -1785,13 +1794,15 @@ export class AcpSession {
1785
1794
  return undefined;
1786
1795
  return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
1787
1796
  }
1788
- /** Normalize every ACP update losslessly into the durable ledger. */
1789
- recordConversationUpdate(update, scheduled, commentary = false) {
1797
+ /**
1798
+ * Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
1799
+ * owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
1800
+ */
1801
+ recordConversationUpdate(update, scheduled) {
1790
1802
  const normalized = normalizeSessionUpdate(update, scheduled ? {
1791
1803
  redactText: SCHEDULED_LOOP_REDACTION,
1792
1804
  redactToolCallId: 'scheduled-loop-tool',
1793
- }
1794
- : commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
1805
+ } : {});
1795
1806
  this.conversation.appendSafe({
1796
1807
  kind: normalized.kind,
1797
1808
  sessionGeneration: this.sessionGeneration,
@@ -1805,6 +1816,10 @@ export class AcpSession {
1805
1816
  });
1806
1817
  }
1807
1818
  // ── conversation ledger access (AgentSession) ─────────────────────────────
1819
+ taskNoticeBinding() {
1820
+ return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
1821
+ }
1822
+ recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
1808
1823
  conversationPage(request = {}) {
1809
1824
  const floor = Number(this.conversationStartCursor ?? 0);
1810
1825
  const requested = Number(request.after ?? 0);
@@ -23,6 +23,7 @@ export declare class RoleTurnArbiter implements AgentSession {
23
23
  private tail;
24
24
  private unsettled;
25
25
  private stopping;
26
+ private retiring;
26
27
  /** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
27
28
  private generation;
28
29
  /** Resolves the instant the CURRENT generation is retired. */
@@ -62,6 +63,8 @@ export declare class RoleTurnArbiter implements AgentSession {
62
63
  tryScheduled(text: string, origin: Extract<PromptOrigin, {
63
64
  kind: 'scheduled-loop';
64
65
  }>, beforeQueue?: () => void | Promise<void>): Promise<ScheduledAttempt>;
66
+ /** Retire only at the same admission boundary used by all prompt producers. */
67
+ retireIfIdle(expired: (snapshot: SessionSnapshot, queueDepth: number) => boolean): Promise<boolean>;
65
68
  stopScheduledAdmission(): void;
66
69
  isAlive(): boolean;
67
70
  snapshot(): SessionSnapshot;
@@ -29,6 +29,7 @@ export class RoleTurnArbiter {
29
29
  tail = Promise.resolve();
30
30
  unsettled = 0;
31
31
  stopping = false;
32
+ retiring = false;
32
33
  /** Bumped by `retireStalledAdmission`; every claim carries the one it was made under. */
33
34
  generation = 0;
34
35
  /** Resolves the instant the CURRENT generation is retired. */
@@ -89,7 +90,8 @@ export class RoleTurnArbiter {
89
90
  retired.retire();
90
91
  }
91
92
  queuePrompt(text, options = {}) {
92
- return this.exclusive(async () => this.track(await this.session.queuePrompt(text, options)));
93
+ return this.exclusive(async () => { if (this.retiring)
94
+ throw new Error('Chat is closing'); return this.track(await this.session.queuePrompt(text, options)); });
93
95
  }
94
96
  async submitPrompt(text, options = {}) {
95
97
  return (await this.queuePrompt(text, options)).completion;
@@ -99,6 +101,8 @@ export class RoleTurnArbiter {
99
101
  * answers and explicit interrupts must remain able to pass immediately.
100
102
  */
101
103
  submitPromptAfterTool(text, options = {}) {
104
+ if (this.retiring)
105
+ return Promise.reject(new Error('Chat is closing'));
102
106
  return this.session.submitPromptAfterTool?.(text, options)
103
107
  ?? this.session.submitPrompt(text, { ...options, interrupt: false, steer: false });
104
108
  }
@@ -136,6 +140,19 @@ export class RoleTurnArbiter {
136
140
  throw error;
137
141
  });
138
142
  }
143
+ /** Retire only at the same admission boundary used by all prompt producers. */
144
+ retireIfIdle(expired) {
145
+ return this.exclusive(async () => {
146
+ const snapshot = this.session.snapshot();
147
+ const queue = this.session.conversationSnapshot?.().queueDepth ?? -1;
148
+ if (this.retiring || this.unsettled > 0 || snapshot.readiness !== 'idle' || !expired(snapshot, queue))
149
+ return false;
150
+ this.retiring = true;
151
+ this.stopping = true;
152
+ await this.session.close();
153
+ return true;
154
+ });
155
+ }
139
156
  stopScheduledAdmission() { this.stopping = true; }
140
157
  isAlive() { return this.session.isAlive(); }
141
158
  snapshot() { return this.session.snapshot(); }
@@ -157,7 +174,8 @@ export class RoleTurnArbiter {
157
174
  submitPromptBrowser(command) {
158
175
  if (!this.session.submitPromptBrowser)
159
176
  return Promise.reject(new Error('browser prompt admission is unavailable'));
160
- return this.exclusive(() => this.session.submitPromptBrowser(command));
177
+ return this.exclusive(() => { if (this.retiring)
178
+ throw new Error('Chat is closing'); return this.session.submitPromptBrowser(command); });
161
179
  }
162
180
  interrupt(source = 'local-console') {
163
181
  return this.exclusive(async () => interruptOutcome(await this.session.interrupt(source)));
@@ -1,3 +1,4 @@
1
+ import { type TaskNoticeBinding, type TaskCreatedNotice } from './task-notice.js';
1
2
  import type { CommonPermissions } from '../config.js';
2
3
  import type { ConversationEventV1, ConversationSnapshot, PromptOrigin, PromptReceipt, SubmitPromptCommand } from './conversation-types.js';
3
4
  import type { CodexAppServerTransportFactory } from './codex-app-server-transport.js';
@@ -76,6 +77,8 @@ export declare class CodexAppServerSession implements AgentSession {
76
77
  setControllerAttached(attached: boolean): void;
77
78
  exitResult(): ExitRecord | null;
78
79
  close(): Promise<void>;
80
+ taskNoticeBinding(): TaskNoticeBinding | undefined;
81
+ recordTaskCreated(binding: TaskNoticeBinding, notice: TaskCreatedNotice): void;
79
82
  conversationPage(request?: {
80
83
  after?: string;
81
84
  limit?: number;
@@ -1,6 +1,8 @@
1
+ import { appendTaskCreated } from './task-notice.js';
1
2
  import { createHash, randomUUID } from 'node:crypto';
2
3
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
4
  import { isAbsolute, join, resolve } from 'node:path';
5
+ import { boundedJson } from './conversation-normalizer.js';
4
6
  import { ConversationEventStore } from './conversation-store.js';
5
7
  import { SessionEvents } from './events.js';
6
8
  import { CodexAppServerTransport } from './codex-app-server-transport.js';
@@ -12,7 +14,6 @@ const INTERRUPT_TERMINATE_GRACE_MS = 5_000;
12
14
  const TERMINAL_RECONCILE_QUIET_MS = 2_000;
13
15
  const TERMINAL_RECONCILE_REQUEST_TIMEOUT_MS = 5_000;
14
16
  const SCHEDULED_REDACTION = '[scheduled-loop content redacted]';
15
- const COMMENTARY_REDACTION = '[assistant commentary redacted]';
16
17
  const OWNER_ADMIN_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_admin_console';
17
18
  const OWNER_CHANNEL_CONTEXT_KEY = 'ours-fleet://prompt-provenance?source=owner_channel';
18
19
  const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -228,8 +229,10 @@ export class CodexAppServerSession {
228
229
  }
229
230
  async interrupt(source = 'local-console') {
230
231
  const active = this.activeTurn;
231
- if (!active || !this.threadId || !active.nativeTurnId)
232
+ if (!active || !this.threadId)
232
233
  return { state: 'settled' };
234
+ if (!active.nativeTurnId)
235
+ throw new SessionControlError('backend', 'The operation is still pending without a native turn ID. Cancellation is not confirmed; inspect or restart the agent if it does not progress.');
233
236
  active.cancellationSource ??= source;
234
237
  this.events.emit('state', { turnId: active.promptId, status: 'running', origin: active.origin });
235
238
  this.conversation.appendSafe({
@@ -335,6 +338,10 @@ export class CodexAppServerSession {
335
338
  this.conversation.close();
336
339
  await this.transport.close();
337
340
  }
341
+ taskNoticeBinding() {
342
+ return this.activeTurn && this.threadId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.threadId, promptId: this.activeTurn.promptId } : undefined;
343
+ }
344
+ recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
338
345
  conversationPage(request = {}) {
339
346
  const floor = Number(this.conversationStartCursor ?? 0);
340
347
  const requested = Number(request.after ?? 0);
@@ -367,6 +374,8 @@ export class CodexAppServerSession {
367
374
  const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
368
375
  if (existing)
369
376
  return existing;
377
+ if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
378
+ throw new Error('session_changed: Agent session changed. Record a new voice message.');
370
379
  const queued = await this.queuePrompt(command.text, {
371
380
  origin: { kind: 'owner-admin-console', commandId: command.commandId },
372
381
  actor: { browserSession: command.actorBrowserSession },
@@ -415,6 +424,10 @@ export class CodexAppServerSession {
415
424
  this.reasoningEffort = { value: effort };
416
425
  this.readiness = 'idle';
417
426
  this.events.emit('state', { status: 'idle', text: `Codex thread ${this.threadId}` });
427
+ this.conversation.appendSafe({
428
+ kind: 'capabilities.updated', sessionGeneration: this.sessionGeneration,
429
+ payload: { commands: [{ name: 'compact', description: textBlock('Compact session context') }] },
430
+ });
418
431
  this.conversation.appendSafe({
419
432
  kind: 'session.state', sessionGeneration: this.sessionGeneration,
420
433
  payload: { status: 'idle' },
@@ -486,6 +499,12 @@ export class CodexAppServerSession {
486
499
  promptId, turnId: promptId, source: sourceFor(origin).source, payload: {},
487
500
  });
488
501
  try {
502
+ if (text.trim() === '/compact') {
503
+ // The acknowledgement is not completion. Standard turn notifications
504
+ // settle the same queued prompt and durable browser receipt.
505
+ await this.transport.request('thread/compact/start', { threadId: this.threadId });
506
+ return await settled;
507
+ }
489
508
  const response = await this.transport.request('turn/start', {
490
509
  threadId: this.threadId,
491
510
  input: [{ type: 'text', text, text_elements: [] }],
@@ -699,7 +718,7 @@ export class CodexAppServerSession {
699
718
  promptId: this.activeTurn?.promptId, turnId: this.activeTurn?.promptId,
700
719
  messageId: id, source: 'agent',
701
720
  payload: {
702
- role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : phase === 'commentary' ? COMMENTARY_REDACTION : undefined),
721
+ role: 'assistant', content: textBlock(delta, scheduled ? SCHEDULED_REDACTION : undefined),
703
722
  },
704
723
  });
705
724
  }
@@ -746,6 +765,10 @@ export class CodexAppServerSession {
746
765
  toolCallId: scheduled ? 'scheduled-loop-tool' : id, snapshot,
747
766
  title: scheduled ? 'scheduled-loop tool' : tool.title, status: tool.status,
748
767
  kind: string(item.type),
768
+ ...(!scheduled && item.arguments !== undefined ? { rawInput: boundedJson(item.arguments) } : {}),
769
+ ...(!scheduled && item.type === 'commandExecution' ? { rawInput: boundedJson({ command: item.command, cwd: item.cwd }), ...(item.aggregatedOutput !== undefined ? { rawOutput: boundedJson({ output: item.aggregatedOutput, exitCode: item.exitCode }) } : {}) } : {}),
770
+ ...(!scheduled && item.type === 'fileChange' && item.changes !== undefined ? { rawOutput: boundedJson(item.changes) } : {}),
771
+ ...(!scheduled && (item.result !== undefined || item.error !== undefined) ? { rawOutput: boundedJson({ result: item.result, error: item.error }) } : {}),
749
772
  },
750
773
  });
751
774
  }
@@ -1,3 +1,5 @@
1
+ import type { LayoutControlRequest } from '../rooms-tasks/layout-control.js';
2
+ import type { SupervisorCorrespondence, AgentHistoryQuery } from '../agent-ours/correspondence.js';
1
3
  import { type Socket } from 'node:net';
2
4
  import type { AgentSession, ControlFailureKind, SessionEvent, SessionSnapshot } from './types.js';
3
5
  import type { OwnerChannelHandle, OwnerChannelManagementRequest } from '../owner-channel/channel.js';
@@ -9,8 +11,11 @@ export interface ControlRequest {
9
11
  version: 1 | 2 | 3;
10
12
  id: string;
11
13
  token: string;
12
- command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'interrupt_v2' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
14
+ command: 'status' | 'snapshot' | 'submit_prompt' | 'respond_permission' | 'interrupt' | 'follow' | 'events_since' | 'owner_channel_manage' | 'loop_status' | 'loop_run_now' | 'loop_disable' | 'loop_enable' | 'reload_config' | 'conversation_page' | 'conversation_follow' | 'submit_prompt_v2' | 'submit_voice_prompt' | 'interrupt_v2' | 'layout_control' | 'agent_contacts' | 'agent_history' | 'respond_permission_v2' | 'fleet_spawn' | 'fleet_audit_begin' | 'fleet_audit_present' | 'fleet_audit_finish';
15
+ layout?: LayoutControlRequest;
16
+ agentHistory?: AgentHistoryQuery;
13
17
  text?: string;
18
+ expectedSessionGeneration?: string;
14
19
  permissionId?: string;
15
20
  optionId?: string;
16
21
  since?: number;
@@ -112,9 +117,13 @@ export declare class RoleControlServer {
112
117
  private readonly sockets;
113
118
  /** Interrupt idempotency: same command id returns the first receipt. */
114
119
  private readonly interruptCommands;
120
+ private correspondence?;
121
+ setCorrespondence(value: SupervisorCorrespondence): void;
115
122
  private ownerChannel?;
116
123
  private loopManager?;
117
124
  private reloadConfig?;
125
+ private layoutControl?;
126
+ setLayoutControl(handler: (request: LayoutControlRequest) => Promise<unknown>): void;
118
127
  private fleetSpawner?;
119
128
  private fleetAuditor?;
120
129
  constructor(stateDir: string, session: AgentSession, log: (line: string) => void);