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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +280 -68
  2. package/dist/account-origin.d.ts +5 -0
  3. package/dist/account-origin.js +31 -0
  4. package/dist/agent-ours/bridge.js +26 -9
  5. package/dist/agent-ours/correspondence.d.ts +10 -0
  6. package/dist/agent-ours/correspondence.js +1 -0
  7. package/dist/agent-ours/file-rpc.d.ts +12 -0
  8. package/dist/agent-ours/file-rpc.js +18 -4
  9. package/dist/agent-ours/harness.js +9 -1
  10. package/dist/agent-ours/mcp-endpoint.d.ts +22 -0
  11. package/dist/agent-ours/mcp-endpoint.js +101 -25
  12. package/dist/agent-ours/runtime.d.ts +9 -1
  13. package/dist/agent-ours/runtime.js +50 -2
  14. package/dist/agent-ours/service.d.ts +27 -1
  15. package/dist/agent-ours/service.js +107 -13
  16. package/dist/application/codex-models.d.ts +15 -0
  17. package/dist/application/codex-models.js +56 -0
  18. package/dist/application/conversation-history.d.ts +34 -0
  19. package/dist/application/conversation-history.js +294 -0
  20. package/dist/application/conversation-role-observer.d.ts +62 -0
  21. package/dist/application/conversation-role-observer.js +206 -0
  22. package/dist/application/fleet-query-service.d.ts +1 -1
  23. package/dist/application/fleet-query-service.js +3 -3
  24. package/dist/application/log-service.js +8 -8
  25. package/dist/application/model-catalog.d.ts +1 -1
  26. package/dist/application/model-catalog.js +5 -2
  27. package/dist/application/preset-provenance.d.ts +19 -0
  28. package/dist/application/preset-provenance.js +59 -0
  29. package/dist/application/resource-observer.d.ts +43 -0
  30. package/dist/application/resource-observer.js +323 -0
  31. package/dist/application/role-creation-service.js +3 -2
  32. package/dist/application/role-removal-service.js +17 -3
  33. package/dist/application/role-repository.d.ts +1 -1
  34. package/dist/application/role-repository.js +30 -7
  35. package/dist/application/room-layout-definitions.d.ts +45 -0
  36. package/dist/application/room-layout-definitions.js +130 -0
  37. package/dist/application/session-control.d.ts +63 -17
  38. package/dist/application/session-control.js +221 -23
  39. package/dist/application/supervisor-ours-tools.js +5 -11
  40. package/dist/application/task-layouts.d.ts +91 -0
  41. package/dist/application/task-layouts.js +261 -0
  42. package/dist/application/task-room-service.d.ts +101 -3
  43. package/dist/application/task-room-service.js +286 -33
  44. package/dist/application/types.d.ts +2 -0
  45. package/dist/atomic-file.d.ts +34 -1
  46. package/dist/atomic-file.js +161 -18
  47. package/dist/briefing.d.ts +3 -1
  48. package/dist/briefing.js +120 -31
  49. package/dist/build-info.json +9 -4
  50. package/dist/capabilities.d.ts +9 -1
  51. package/dist/capabilities.js +13 -0
  52. package/dist/cli.js +402 -38
  53. package/dist/client-profile.d.ts +5 -17
  54. package/dist/client-profile.js +4 -127
  55. package/dist/config.d.ts +45 -0
  56. package/dist/config.js +57 -6
  57. package/dist/creation.d.ts +1 -1
  58. package/dist/creation.js +4 -4
  59. package/dist/daemon-recovery.d.ts +14 -9
  60. package/dist/daemon-recovery.js +124 -115
  61. package/dist/docs.d.ts +1 -1
  62. package/dist/docs.js +227 -19
  63. package/dist/doctor.d.ts +4 -1
  64. package/dist/doctor.js +75 -60
  65. package/dist/erased-resources.d.ts +9 -0
  66. package/dist/erased-resources.js +87 -0
  67. package/dist/file-delivery/reader.d.ts +15 -0
  68. package/dist/file-delivery/reader.js +63 -0
  69. package/dist/file-delivery/store.d.ts +15 -0
  70. package/dist/file-delivery/store.js +124 -0
  71. package/dist/file-delivery/types.d.ts +19 -0
  72. package/dist/file-delivery/types.js +1 -0
  73. package/dist/fleet-command-audit.d.ts +13 -0
  74. package/dist/fleet-command-audit.js +31 -9
  75. package/dist/harness/agent-session.d.ts +4 -0
  76. package/dist/harness/claude-code-session.js +1 -1
  77. package/dist/harness/claude-code.js +21 -11
  78. package/dist/harness/claude-runtime.d.ts +12 -0
  79. package/dist/harness/claude-runtime.js +43 -0
  80. package/dist/harness/codex-app-server-proxy.d.ts +4 -1
  81. package/dist/harness/codex-app-server-proxy.js +41 -5
  82. package/dist/harness/codex-reasoning.d.ts +5 -0
  83. package/dist/harness/codex-reasoning.js +12 -0
  84. package/dist/harness/codex-runtime.d.ts +11 -1
  85. package/dist/harness/codex-runtime.js +31 -4
  86. package/dist/harness/codex-session.js +2 -2
  87. package/dist/harness/codex.js +24 -12
  88. package/dist/init-wizard.d.ts +2 -0
  89. package/dist/init-wizard.js +38 -19
  90. package/dist/isolation/policy.js +6 -1
  91. package/dist/isolation/types.d.ts +5 -0
  92. package/dist/managed-cli.d.ts +253 -0
  93. package/dist/managed-cli.js +923 -0
  94. package/dist/managed-recovery.d.ts +13 -0
  95. package/dist/managed-recovery.js +31 -0
  96. package/dist/minimal-setup.d.ts +2 -0
  97. package/dist/minimal-setup.js +44 -0
  98. package/dist/monitor.d.ts +9 -34
  99. package/dist/monitor.js +90 -153
  100. package/dist/notifications/fleet-producer.d.ts +31 -0
  101. package/dist/notifications/fleet-producer.js +351 -0
  102. package/dist/notifications/outbox.d.ts +59 -0
  103. package/dist/notifications/outbox.js +199 -0
  104. package/dist/notifications/target-cleanup.d.ts +16 -0
  105. package/dist/notifications/target-cleanup.js +75 -0
  106. package/dist/notifications/task-producer.d.ts +20 -0
  107. package/dist/notifications/task-producer.js +100 -0
  108. package/dist/ops.d.ts +2 -0
  109. package/dist/ops.js +20 -3
  110. package/dist/owner-channel/channel.js +17 -6
  111. package/dist/owner-channel/commands.d.ts +4 -0
  112. package/dist/owner-channel/commands.js +8 -0
  113. package/dist/owner-channel/lifecycle-outbox.js +4 -3
  114. package/dist/owner-channel/ours-client.js +2 -17
  115. package/dist/paths.d.ts +5 -0
  116. package/dist/paths.js +5 -0
  117. package/dist/preset-migration.js +55 -0
  118. package/dist/process-incarnation.d.ts +23 -0
  119. package/dist/process-incarnation.js +42 -0
  120. package/dist/reminders/manager.d.ts +75 -0
  121. package/dist/reminders/manager.js +278 -0
  122. package/dist/reminders/runtime.d.ts +27 -0
  123. package/dist/reminders/runtime.js +32 -0
  124. package/dist/reminders/schedule.d.ts +37 -0
  125. package/dist/reminders/schedule.js +104 -0
  126. package/dist/reminders/store.d.ts +341 -0
  127. package/dist/reminders/store.js +762 -0
  128. package/dist/reminders/tools.d.ts +20 -0
  129. package/dist/reminders/tools.js +114 -0
  130. package/dist/resolved-plan.js +1 -0
  131. package/dist/rooms-tasks/add-member.d.ts +48 -0
  132. package/dist/rooms-tasks/add-member.js +84 -0
  133. package/dist/rooms-tasks/archived-absence.d.ts +4 -0
  134. package/dist/rooms-tasks/archived-absence.js +11 -0
  135. package/dist/rooms-tasks/cli.js +182 -5
  136. package/dist/rooms-tasks/close.d.ts +5 -2
  137. package/dist/rooms-tasks/close.js +224 -36
  138. package/dist/rooms-tasks/config.d.ts +1 -1
  139. package/dist/rooms-tasks/config.js +11 -4
  140. package/dist/rooms-tasks/cowork-adapter.d.ts +12 -5
  141. package/dist/rooms-tasks/cowork-adapter.js +15 -121
  142. package/dist/rooms-tasks/cowork-http.js +56 -6
  143. package/dist/rooms-tasks/deletion.d.ts +1 -1
  144. package/dist/rooms-tasks/deletion.js +66 -10
  145. package/dist/rooms-tasks/erasure.d.ts +6 -0
  146. package/dist/rooms-tasks/erasure.js +173 -0
  147. package/dist/rooms-tasks/external-worker.d.ts +1 -1
  148. package/dist/rooms-tasks/external-worker.js +3 -4
  149. package/dist/rooms-tasks/layout-binding-client.d.ts +11 -0
  150. package/dist/rooms-tasks/layout-binding-client.js +56 -0
  151. package/dist/rooms-tasks/layout-binding-grants.d.ts +21 -0
  152. package/dist/rooms-tasks/layout-binding-grants.js +128 -0
  153. package/dist/rooms-tasks/layout-cli.d.ts +4 -0
  154. package/dist/rooms-tasks/layout-cli.js +107 -0
  155. package/dist/rooms-tasks/layout-config.d.ts +11 -0
  156. package/dist/rooms-tasks/layout-config.js +89 -0
  157. package/dist/rooms-tasks/layout-control.d.ts +21 -0
  158. package/dist/rooms-tasks/layout-control.js +52 -0
  159. package/dist/rooms-tasks/layout-member-retirement.d.ts +10 -0
  160. package/dist/rooms-tasks/layout-member-retirement.js +90 -0
  161. package/dist/rooms-tasks/layout-service.d.ts +43 -0
  162. package/dist/rooms-tasks/layout-service.js +203 -0
  163. package/dist/rooms-tasks/layout.d.ts +121 -0
  164. package/dist/rooms-tasks/layout.js +286 -0
  165. package/dist/rooms-tasks/legacy-task-member.d.ts +19 -0
  166. package/dist/rooms-tasks/legacy-task-member.js +116 -0
  167. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  168. package/dist/rooms-tasks/live-readiness.js +85 -0
  169. package/dist/rooms-tasks/member-ownership.d.ts +5 -0
  170. package/dist/rooms-tasks/member-ownership.js +40 -0
  171. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  172. package/dist/rooms-tasks/member-startup.js +2 -0
  173. package/dist/rooms-tasks/provision.d.ts +3 -0
  174. package/dist/rooms-tasks/provision.js +101 -49
  175. package/dist/rooms-tasks/recovery.d.ts +19 -0
  176. package/dist/rooms-tasks/recovery.js +110 -0
  177. package/dist/rooms-tasks/room-state.d.ts +4 -2
  178. package/dist/rooms-tasks/room-state.js +47 -8
  179. package/dist/rooms-tasks/task-review.d.ts +113 -0
  180. package/dist/rooms-tasks/task-review.js +561 -0
  181. package/dist/rooms-tasks/task-service-retirement.d.ts +11 -0
  182. package/dist/rooms-tasks/task-service-retirement.js +21 -0
  183. package/dist/rooms-tasks/task-state.d.ts +51 -5
  184. package/dist/rooms-tasks/task-state.js +358 -50
  185. package/dist/rooms-tasks/task-tools.d.ts +35 -0
  186. package/dist/rooms-tasks/task-tools.js +134 -0
  187. package/dist/rooms-tasks/types.d.ts +78 -0
  188. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  189. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  190. package/dist/rooms-tasks/workspace.d.ts +35 -0
  191. package/dist/rooms-tasks/workspace.js +325 -0
  192. package/dist/runner.d.ts +33 -4
  193. package/dist/runner.js +410 -62
  194. package/dist/session/acp.d.ts +75 -2
  195. package/dist/session/acp.js +345 -82
  196. package/dist/session/arbiter.d.ts +98 -0
  197. package/dist/session/arbiter.js +272 -3
  198. package/dist/session/codex-app-server.d.ts +45 -1
  199. package/dist/session/codex-app-server.js +225 -32
  200. package/dist/session/control.d.ts +17 -3
  201. package/dist/session/control.js +219 -23
  202. package/dist/session/conversation-normalizer.d.ts +2 -1
  203. package/dist/session/conversation-normalizer.js +1 -1
  204. package/dist/session/conversation-observation.d.ts +25 -0
  205. package/dist/session/conversation-observation.js +100 -0
  206. package/dist/session/conversation-replay.d.ts +50 -0
  207. package/dist/session/conversation-replay.js +203 -0
  208. package/dist/session/conversation-store.d.ts +15 -1
  209. package/dist/session/conversation-store.js +213 -26
  210. package/dist/session/conversation-types.d.ts +57 -4
  211. package/dist/session/prompt-batch.d.ts +79 -0
  212. package/dist/session/prompt-batch.js +120 -0
  213. package/dist/session/task-notice.d.ts +14 -0
  214. package/dist/session/task-notice.js +13 -0
  215. package/dist/session/types.d.ts +42 -1
  216. package/dist/setup-tunnel-preflight.d.ts +34 -0
  217. package/dist/setup-tunnel-preflight.js +62 -0
  218. package/dist/socket-path.d.ts +2 -0
  219. package/dist/socket-path.js +22 -0
  220. package/dist/spawn.d.ts +5 -1
  221. package/dist/spawn.js +40 -4
  222. package/dist/startup-prompt.d.ts +15 -0
  223. package/dist/startup-prompt.js +71 -0
  224. package/dist/startup-readiness.d.ts +32 -0
  225. package/dist/startup-readiness.js +101 -0
  226. package/dist/startup-telemetry.d.ts +16 -0
  227. package/dist/startup-telemetry.js +46 -0
  228. package/dist/subscriptions/cli.d.ts +91 -0
  229. package/dist/subscriptions/cli.js +223 -0
  230. package/dist/subscriptions/launch.d.ts +37 -0
  231. package/dist/subscriptions/launch.js +93 -0
  232. package/dist/subscriptions/login.d.ts +77 -0
  233. package/dist/subscriptions/login.js +215 -0
  234. package/dist/subscriptions/observed.d.ts +7 -0
  235. package/dist/subscriptions/observed.js +44 -0
  236. package/dist/subscriptions/probe.d.ts +10 -0
  237. package/dist/subscriptions/probe.js +73 -0
  238. package/dist/subscriptions/service.d.ts +102 -0
  239. package/dist/subscriptions/service.js +364 -0
  240. package/dist/subscriptions/store.d.ts +71 -0
  241. package/dist/subscriptions/store.js +143 -0
  242. package/dist/supervisor/adoption.d.ts +8 -0
  243. package/dist/supervisor/adoption.js +78 -0
  244. package/dist/supervisor/catalog.d.ts +62 -0
  245. package/dist/supervisor/catalog.js +254 -0
  246. package/dist/supervisor/fleet.d.ts +8 -0
  247. package/dist/supervisor/fleet.js +89 -0
  248. package/dist/supervisor/index.js +3 -6
  249. package/dist/supervisor/launchd.d.ts +1 -1
  250. package/dist/supervisor/launchd.js +26 -13
  251. package/dist/supervisor/legacy.d.ts +35 -0
  252. package/dist/supervisor/legacy.js +524 -0
  253. package/dist/supervisor/manager.d.ts +15 -0
  254. package/dist/supervisor/manager.js +270 -0
  255. package/dist/supervisor/registration-refresh.d.ts +45 -0
  256. package/dist/supervisor/registration-refresh.js +266 -0
  257. package/dist/supervisor/scope.d.ts +4 -0
  258. package/dist/supervisor/scope.js +10 -0
  259. package/dist/supervisor/systemd.d.ts +2 -2
  260. package/dist/supervisor/systemd.js +19 -33
  261. package/dist/task-supervision.d.ts +9 -0
  262. package/dist/task-supervision.js +145 -0
  263. package/dist/temp-idle.d.ts +17 -0
  264. package/dist/temp-idle.js +42 -0
  265. package/dist/temp-lifecycle.d.ts +23 -8
  266. package/dist/temp-lifecycle.js +123 -66
  267. package/dist/temp-supervisor-recovery.d.ts +1 -1
  268. package/dist/temp-supervisor-recovery.js +2 -2
  269. package/dist/tunnel-setup-completion.d.ts +7 -0
  270. package/dist/tunnel-setup-completion.js +13 -0
  271. package/dist/tunnel-setup-input.d.ts +19 -0
  272. package/dist/tunnel-setup-input.js +130 -0
  273. package/dist/watchdog/query.d.ts +3 -1
  274. package/dist/watchdog/query.js +2 -1
  275. package/dist/web/agent-attachments.d.ts +29 -0
  276. package/dist/web/agent-attachments.js +162 -0
  277. package/dist/web/auth.d.ts +65 -2
  278. package/dist/web/auth.js +201 -11
  279. package/dist/web/available-port.d.ts +2 -0
  280. package/dist/web/available-port.js +23 -0
  281. package/dist/web/control.d.ts +3 -3
  282. package/dist/web/control.js +10 -8
  283. package/dist/web/conversation-stream-codec.d.ts +23 -0
  284. package/dist/web/conversation-stream-codec.js +110 -0
  285. package/dist/web/conversation-stream.d.ts +26 -0
  286. package/dist/web/conversation-stream.js +239 -0
  287. package/dist/web/events.d.ts +2 -0
  288. package/dist/web/events.js +9 -0
  289. package/dist/web/fleet-config-service.d.ts +7 -1
  290. package/dist/web/fleet-config-service.js +61 -16
  291. package/dist/web/prefix-gateway.d.ts +32 -0
  292. package/dist/web/prefix-gateway.js +248 -0
  293. package/dist/web/resource-views.d.ts +135 -0
  294. package/dist/web/resource-views.js +494 -0
  295. package/dist/web/runtime.d.ts +3 -1
  296. package/dist/web/runtime.js +132 -34
  297. package/dist/web/server.d.ts +19 -0
  298. package/dist/web/server.js +893 -71
  299. package/dist/web/service.d.ts +11 -2
  300. package/dist/web/service.js +30 -4
  301. package/dist/web/workspace-devices.d.ts +107 -0
  302. package/dist/web/workspace-devices.js +277 -0
  303. package/dist/web/workspace-tunnel.d.ts +1 -0
  304. package/dist/web/workspace-tunnel.js +34 -0
  305. package/dist/workspace-enrollment.d.ts +83 -0
  306. package/dist/workspace-enrollment.js +605 -0
  307. package/dist/workspace-replacement.d.ts +66 -0
  308. package/dist/workspace-replacement.js +238 -0
  309. package/docs/acp-files.md +146 -0
  310. package/docs/conversation-stream.md +216 -0
  311. package/docs/fleet-migration.md +54 -0
  312. package/docs/live-voice.md +13 -0
  313. package/docs/room-layouts.md +227 -0
  314. package/docs/self-reminders.md +374 -0
  315. package/docs/task-progress-review.md +143 -0
  316. package/docs/task-workspaces.md +225 -0
  317. package/docs/validation/managed-cli-permissions.md +421 -0
  318. package/examples/room-layouts/fresh.yaml +27 -0
  319. package/examples/room-layouts/scoped.yaml +21 -0
  320. package/examples/room-layouts/shared.yaml +19 -0
  321. package/package.json +25 -20
  322. package/presets/README.md +78 -0
  323. package/presets/brain-catalog.json +8 -2
  324. package/presets/fleet/agent_templates/Critic.yaml +1 -1
  325. package/presets/fleet/agent_templates/Developer.yaml +1 -1
  326. package/presets/fleet/agent_templates/Engineer.yaml +5 -0
  327. package/presets/fleet/agent_templates/LocalCoordinator.yaml +6 -3
  328. package/presets/fleet/agents/FleetCoordinator.yaml +1 -1
  329. package/presets/fleet/brains/claude-fable-5-1-high.yaml +5 -0
  330. package/presets/fleet/brains/claude-fable-5-1-low.yaml +5 -0
  331. package/presets/fleet/brains/claude-fable-5-1-max.yaml +5 -0
  332. package/presets/fleet/brains/claude-fable-5-1-medium.yaml +5 -0
  333. package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +5 -0
  334. package/presets/fleet/brains/claude-fable-5-high.yaml +1 -1
  335. package/presets/fleet/brains/claude-fable-5-low.yaml +1 -1
  336. package/presets/fleet/brains/claude-fable-5-max.yaml +1 -1
  337. package/presets/fleet/brains/claude-fable-5-medium.yaml +1 -1
  338. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +1 -1
  339. package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +4 -0
  340. package/presets/fleet/brains/claude-opus-5-5-high.yaml +5 -0
  341. package/presets/fleet/brains/claude-opus-5-5-low.yaml +5 -0
  342. package/presets/fleet/brains/claude-opus-5-5-max.yaml +5 -0
  343. package/presets/fleet/brains/claude-opus-5-5-medium.yaml +5 -0
  344. package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +5 -0
  345. package/presets/fleet/brains/claude-opus-5-high.yaml +1 -1
  346. package/presets/fleet/brains/claude-opus-5-low.yaml +1 -1
  347. package/presets/fleet/brains/claude-opus-5-max.yaml +1 -1
  348. package/presets/fleet/brains/claude-opus-5-medium.yaml +1 -1
  349. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +1 -1
  350. package/presets/fleet/brains/claude-sonnet-5-high.yaml +1 -1
  351. package/presets/fleet/brains/claude-sonnet-5-low.yaml +1 -1
  352. package/presets/fleet/brains/claude-sonnet-5-max.yaml +1 -1
  353. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +1 -1
  354. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +1 -1
  355. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +1 -1
  356. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +1 -1
  357. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +1 -1
  358. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +1 -1
  359. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +1 -1
  360. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +1 -1
  361. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +1 -1
  362. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +1 -1
  363. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +1 -1
  364. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +1 -1
  365. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +1 -1
  366. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +1 -1
  367. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +1 -1
  368. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +1 -1
  369. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +1 -1
  370. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +1 -1
  371. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +1 -1
  372. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +1 -1
  373. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +1 -1
  374. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +1 -1
  375. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +1 -1
  376. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +1 -1
  377. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +1 -1
  378. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +1 -1
  379. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +1 -1
  380. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +1 -1
  381. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +1 -1
  382. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +1 -1
  383. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +1 -1
  384. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +1 -1
  385. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +1 -1
  386. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +1 -1
  387. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +1 -1
  388. package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +5 -0
  389. package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +5 -0
  390. package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +5 -0
  391. package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +5 -0
  392. package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +5 -0
  393. package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +5 -0
  394. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +1 -1
  395. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +1 -1
  396. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +1 -1
  397. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +1 -1
  398. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +1 -1
  399. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +1 -1
  400. package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +5 -0
  401. package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +5 -0
  402. package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +5 -0
  403. package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +5 -0
  404. package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +5 -0
  405. package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +5 -0
  406. package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +5 -0
  407. package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +5 -0
  408. package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +5 -0
  409. package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +5 -0
  410. package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +5 -0
  411. package/presets/fleet/roles/Coordinator.yaml +21 -13
  412. package/presets/fleet/roles/Critic.yaml +24 -0
  413. package/presets/fleet/roles/Developer.yaml +32 -0
  414. package/presets/fleet/roles/Engineer.yaml +72 -0
  415. package/presets/fleet/roles/LocalCoordinator.yaml +17 -0
  416. package/presets/fleet/room_layouts/engineering.yaml +25 -0
  417. package/presets/fleet/room_layouts/pair.yaml +24 -0
  418. package/presets/fleet/room_layouts/single.yaml +17 -0
  419. package/presets/fleet/room_layouts/team.yaml +30 -0
  420. package/presets/fleet/room_templates/engineering.yaml +19 -0
  421. package/presets/manifest.json +2 -2
  422. package/dist/web-app/assets/index-59GF-gsZ.css +0 -1
  423. package/dist/web-app/assets/index-C0h3ALvs.js +0 -10
  424. package/dist/web-app/icons/ours-fleet-maskable.svg +0 -4
  425. package/dist/web-app/icons/ours-fleet.svg +0 -4
  426. package/dist/web-app/index.html +0 -17
  427. package/dist/web-app/manifest.webmanifest +0 -15
  428. package/dist/web-app/offline.html +0 -18
  429. package/dist/web-app/sw.js +0 -51
@@ -1,3 +1,5 @@
1
+ import { ConversationObservation } from './conversation-observation.js';
2
+ import { appendTaskCreated } from './task-notice.js';
1
3
  import { spawn } from 'node:child_process';
2
4
  import { createHash, randomUUID } from 'node:crypto';
3
5
  import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
@@ -7,6 +9,8 @@ import * as acp from '@agentclientprotocol/sdk';
7
9
  import { normalizeSessionUpdate } from './conversation-normalizer.js';
8
10
  import { ConversationEventStore } from './conversation-store.js';
9
11
  import { SessionEvents } from './events.js';
12
+ import { commandName, memberAuthority, memberBindings, pendingInventory, takeBatch, refuseAtRelease, RELEASE_GUARD_REFUSED, } from './prompt-batch.js';
13
+ import { recordClaudeRateLimit } from '../subscriptions/observed.js';
10
14
  import { DEFAULT_STALL_TIMEOUT_MS, STALL_RECOVERY_PROMPT, StallWatchdog, StallToolHistory, hasStallRecoveryClaim } from './stall-watchdog.js';
11
15
  import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, classifyChildExit, sessionBackendCapabilities, turnResult, } from './types.js';
12
16
  const CANCEL_SETTLE_GRACE_MS = 15_000;
@@ -36,7 +40,6 @@ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
36
40
  /** Consumed only by Fleet's authenticated bundled-Codex app-server proxy. */
37
41
  export const CODEX_DISABLE_INHERITED_MCP_ENV = 'OURS_FLEET_CODEX_DISABLE_INHERITED_MCP';
38
42
  const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
39
- const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
40
43
  const MAX_CANONICAL_SYMLINK_DEPTH = 40;
41
44
  const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
42
45
  /**
@@ -164,6 +167,9 @@ function conversationSource(origin) {
164
167
  case 'owner': return { source: 'owner_channel', persistBody: false };
165
168
  case 'fleet-monitor': return { source: 'fleet_monitor', persistBody: false };
166
169
  case 'scheduled-loop': return { source: 'scheduled_loop', persistBody: false };
170
+ // A supervisor wake like a monitor notice; the body is never retained, so
171
+ // restart recovery can never replay a reminder past its dispatch fence.
172
+ case 'self-reminder': return { source: 'fleet_monitor', persistBody: false };
167
173
  case 'local-console':
168
174
  default:
169
175
  return { source: 'local_console', persistBody: true };
@@ -181,6 +187,38 @@ export function promptContentBlocks(text, origin) {
181
187
  mimeType: 'application/vnd.ours-fleet.prompt-provenance+json',
182
188
  }, { type: 'text', text }];
183
189
  }
190
+ /**
191
+ * One adapter turn that carries several admitted prompts.
192
+ *
193
+ * A single prompt's provenance block describes "the accompanying text", which
194
+ * is only unambiguous when there is one text. Here every member is one text
195
+ * block, unchanged, immediately preceded by its own server-generated block
196
+ * that names its route and binds its exact bytes — so a monitor wake next to
197
+ * an owner prompt is never read under the owner's authority, and text that
198
+ * imitates a marker stays text: it cannot produce a typed block.
199
+ */
200
+ export function batchContentBlocks(members) {
201
+ if (members.length === 1)
202
+ return promptContentBlocks(members[0].text, members[0].origin);
203
+ const mimeType = 'application/vnd.ours-fleet.prompt-provenance+json';
204
+ return [{
205
+ type: 'resource_link',
206
+ uri: `ours-fleet://prompt-batch?members=${members.length}`,
207
+ name: 'Fleet queued prompt batch',
208
+ description: `Server-generated. This turn delivers ${members.length} separately admitted prompts in `
209
+ + 'submission order. Each prompt is one text block immediately preceded by its own '
210
+ + 'server-generated provenance block, which describes only that one text block.',
211
+ mimeType,
212
+ }, ...memberBindings(members, origin => conversationSource(origin).source).flatMap((binding, at) => [{
213
+ type: 'resource_link',
214
+ uri: `ours-fleet://prompt-provenance?source=${binding.source}&member=${binding.index}`
215
+ + `&of=${binding.of}&bytes=${binding.bytes}&sha256=${binding.sha256}`,
216
+ name: binding.source === 'owner_admin_console' ? 'Direct owner admin console' : 'Fleet queued prompt',
217
+ description: `Server-generated provenance for prompt ${binding.index} of ${binding.of}: `
218
+ + `${memberAuthority(binding.source)}. Applies only to the text block that immediately follows.`,
219
+ mimeType,
220
+ }, { type: 'text', text: members[at].text }])];
221
+ }
184
222
  export function runtimeSelector(options, category) {
185
223
  const option = options?.find(candidate => candidate.category === category);
186
224
  if (!option || typeof option.currentValue !== 'string')
@@ -238,7 +276,16 @@ export class AcpSession {
238
276
  lastUpdateAt;
239
277
  lastError;
240
278
  promptTail = Promise.resolve();
279
+ /** Admitted prompts not yet handed to the adapter, in admission order. */
280
+ pendingPrompts = [];
281
+ /** Admitted prompt → the adapter turn that carries it, while that turn is open. */
282
+ sharedTurns = new Map();
283
+ /** Commands the adapter currently advertises; only these are control operations. */
284
+ advertisedCommands = new Set();
285
+ presentedCommands = [];
241
286
  queueDepth = 0;
287
+ deferredRecovered = [];
288
+ observation = new ConversationObservation(() => this.captureObservation());
242
289
  exit = null;
243
290
  steeringSupported = false;
244
291
  agentCapabilities;
@@ -277,6 +324,7 @@ export class AcpSession {
277
324
  stallTimer;
278
325
  stallAttempt;
279
326
  toolBoundaryWaiters = new Set();
327
+ fileExportBusy = false;
280
328
  activeTurn;
281
329
  constructor(options, child, connection) {
282
330
  this.options = options;
@@ -289,6 +337,9 @@ export class AcpSession {
289
337
  roleId: options.name, log: line => options.log(`[${options.name}] ${line}`),
290
338
  });
291
339
  this.conversationStartCursor = this.conversation.lastCursor();
340
+ // A durable append can precede its inventory mutation. Capture only after
341
+ // the synchronous owner checkpoint, never the state at append notification.
342
+ this.conversation.subscribe(() => this.observation.scheduleCapture());
292
343
  this.sessionFile = join(options.stateDir, '.acp-session-id');
293
344
  this.terminated = new Promise((_resolve, reject) => { this.terminate = reject; });
294
345
  // Nothing awaits this promise until a request races it; an unobserved
@@ -322,6 +373,7 @@ export class AcpSession {
322
373
  acpSessionId: this.sessionId,
323
374
  payload: { status: 'failed', detail: this.lastError },
324
375
  });
376
+ this.observation.publish();
325
377
  });
326
378
  }
327
379
  static async start(options) {
@@ -406,14 +458,29 @@ export class AcpSession {
406
458
  });
407
459
  continue;
408
460
  }
409
- const text = open.text;
410
- const promptId = open.promptId;
411
- this.queueDepth++;
412
- const run = this.promptTail.then(() => this.runPrompt(text, promptId));
413
- this.promptTail = run.then(() => undefined, () => undefined);
414
- void run.finally(() => { this.queueDepth = Math.max(0, this.queueDepth - 1); });
461
+ const recovered = { promptId: open.promptId, text: open.text };
462
+ if (this.options.deferRecoveredPrompts)
463
+ this.deferredRecovered.push(recovered);
464
+ else
465
+ this.dispatchRecovered(recovered);
415
466
  }
416
467
  }
468
+ /**
469
+ * Dispatch recovered prompts held by deferRecoveredPrompts. A recovered turn
470
+ * can run tools at once, including the managed Fleet CLI, so it must not
471
+ * start before the supervisor's control socket accepts connections. Prompts
472
+ * never activated stay admitted-unstarted and recover again on next start.
473
+ */
474
+ activateRecoveredPrompts() {
475
+ for (const recovered of this.deferredRecovered.splice(0))
476
+ this.dispatchRecovered(recovered);
477
+ }
478
+ dispatchRecovered({ promptId, text }) {
479
+ this.queueDepth++;
480
+ const run = this.enqueue(promptId, text, undefined);
481
+ this.observation.publish();
482
+ void run.finally(() => { this.queueDepth = Math.max(0, this.queueDepth - 1); this.observation.publish(); });
483
+ }
417
484
  isAlive() {
418
485
  // child.killed only means kill() successfully SENT a signal. A process may
419
486
  // ignore SIGTERM and remain alive. exitCode or signalCode is the actual
@@ -430,6 +497,7 @@ export class AcpSession {
430
497
  return;
431
498
  this.steeringOccupied = true;
432
499
  this.refreshSteeringOccupancy();
500
+ this.observation.publish();
433
501
  }
434
502
  refreshSteeringOccupancy() {
435
503
  if (!this.steeringOccupied)
@@ -452,6 +520,7 @@ export class AcpSession {
452
520
  if (!this.steeringOccupied)
453
521
  return;
454
522
  this.steeringOccupied = false;
523
+ this.observation.publish();
455
524
  this.options.log(`[${this.options.name}] steering-started turn no longer holds the adapter (${reason})`);
456
525
  }
457
526
  snapshot() {
@@ -465,6 +534,9 @@ export class AcpSession {
465
534
  readiness: this.readiness === 'idle' && this.steeringOccupied
466
535
  ? 'running' : this.readiness,
467
536
  sessionId: this.sessionId,
537
+ // A shared turn is a human console turn if any member is: inbox traffic must not cut it short.
538
+ ...(this.activeTurn?.origin ? { activePromptSource: this.activeTurn.console
539
+ ? 'owner-admin-console' : this.activeTurn.origin.kind } : {}),
468
540
  lastError: this.lastError,
469
541
  pendingPermissionId: this.pendingPermissions.keys().next().value,
470
542
  runtimeModel: this.runtimeModel,
@@ -579,6 +651,11 @@ export class AcpSession {
579
651
  * steering response from becoming a tight replay loop.
580
652
  */
581
653
  async steerOrQueueWake(text, options) {
654
+ // A tool-boundary wait may outlive its original turn. Recheck the current
655
+ // typed source at delivery, since an Owner turn can begin during the wait.
656
+ // `console` covers a shared turn whose first member is not the console prompt.
657
+ if (options.origin?.kind === 'fleet-monitor' && this.activeTurn?.console)
658
+ return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
582
659
  if (this.stallRecoveryClaimed)
583
660
  return this.submitPrompt(text, { ...options, interrupt: false, steer: false });
584
661
  const steered = await this.steerPrompt(text);
@@ -644,6 +721,9 @@ export class AcpSession {
644
721
  * for it is what turned a busy agent into a timeout and then into "dead".
645
722
  */
646
723
  async queuePrompt(text, options = {}) {
724
+ // A guarded prompt must reach the model only through the release fence.
725
+ if (options.releaseGuard)
726
+ options = { ...options, interrupt: false, steer: false };
647
727
  if (this.stallRecoveryClaimed && options.origin?.kind === 'fleet-monitor')
648
728
  options = { ...options, interrupt: false, steer: false };
649
729
  if (this.cancelRecoveryReason)
@@ -666,12 +746,18 @@ export class AcpSession {
666
746
  }
667
747
  const promptId = randomUUID();
668
748
  const queuedBehind = this.queueDepth;
669
- this.admitToLedger(promptId, text, queuedBehind, options);
670
- this.queueDepth++;
671
- const run = this.promptTail.then(() => this.runPrompt(text, promptId, options.origin));
672
- this.promptTail = run.then(() => undefined, () => undefined);
673
- const completion = run.then(result => { this.queueDepth = Math.max(0, this.queueDepth - 1); return result; }, error => {
749
+ const run = this.observation.checkpoint(() => {
750
+ this.admitToLedger(promptId, text, queuedBehind, options);
751
+ this.queueDepth++;
752
+ return this.enqueue(promptId, text, options.origin, options.releaseGuard);
753
+ });
754
+ const completion = run.then(result => {
674
755
  this.queueDepth = Math.max(0, this.queueDepth - 1);
756
+ this.observation.publish();
757
+ return result;
758
+ }, error => {
759
+ this.queueDepth = Math.max(0, this.queueDepth - 1);
760
+ this.observation.publish();
675
761
  return turnResult(false, 'failed', error?.message ?? String(error));
676
762
  });
677
763
  return {
@@ -679,6 +765,57 @@ export class AcpSession {
679
765
  delivery: delivery ?? (queuedBehind > 0 ? 'queued' : 'started'),
680
766
  };
681
767
  }
768
+ /** The turn whose events and result answer this admitted prompt; itself unless a release shared it. */
769
+ sharedTurnId(promptId) {
770
+ return this.sharedTurns.get(promptId);
771
+ }
772
+ /**
773
+ * Queue an admitted prompt behind the current work. Each prompt keeps its
774
+ * own step in the chain, so its place in line is unchanged; what the step
775
+ * does when it is reached is release a whole snapshot, not one prompt.
776
+ */
777
+ enqueue(promptId, text, origin, releaseGuard) {
778
+ const entry = {
779
+ promptId, text, origin, ...conversationSource(origin), admittedAt: new Date().toISOString(),
780
+ command: this.advertisedCommands.has(commandName(text) ?? ''),
781
+ ...(releaseGuard ? { releaseGuard } : {}),
782
+ };
783
+ this.pendingPrompts.push(entry);
784
+ const run = this.promptTail.then(() => this.release(entry));
785
+ this.promptTail = run.then(() => undefined, () => undefined);
786
+ return run;
787
+ }
788
+ /**
789
+ * The turn boundary. The first waiting prompt to get here takes every
790
+ * pending prompt delivered with it, synchronously, and they share one adapter
791
+ * turn. A prompt admitted after that — while the turn runs, or while a
792
+ * cancellation settles — stays pending for the next boundary. A step whose
793
+ * prompt an earlier boundary already took just reports that turn's result.
794
+ */
795
+ release(entry) {
796
+ if (entry.released)
797
+ return entry.released;
798
+ const refused = refuseAtRelease(this.pendingPrompts, entry, Boolean(this.sessionId && this.isAlive()));
799
+ if (refused) {
800
+ this.conversation.appendSafe({
801
+ kind: 'turn.completed', sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId,
802
+ promptId: entry.promptId, turnId: entry.promptId,
803
+ payload: { outcome: 'cancelled', stopReason: RELEASE_GUARD_REFUSED },
804
+ });
805
+ entry.released = Promise.resolve(refused);
806
+ return entry.released;
807
+ }
808
+ const members = takeBatch(this.pendingPrompts, entry);
809
+ for (const member of members)
810
+ this.sharedTurns.set(member.promptId, members[0].promptId);
811
+ const run = this.runPrompt(members).finally(() => {
812
+ for (const member of members)
813
+ this.sharedTurns.delete(member.promptId);
814
+ });
815
+ for (const member of members)
816
+ member.released = run;
817
+ return run;
818
+ }
682
819
  /**
683
820
  * Prepare the session for a prompt that asked to pre-empt current work.
684
821
  *
@@ -760,11 +897,16 @@ export class AcpSession {
760
897
  const existing = this.conversation.receiptFor(command.commandId, bodyDigest);
761
898
  if (existing)
762
899
  return existing;
900
+ if (command.expectedSessionGeneration !== undefined && command.expectedSessionGeneration !== this.conversationSnapshot().sessionGeneration)
901
+ throw new Error('session_changed: Agent session changed. Record a new voice message.');
902
+ if (command.requireIdle && (this.queueDepth > 0 || this.conversationSnapshot().readiness !== 'idle'))
903
+ throw new Error('voice_busy: Agent is already working. Wait for the current turn.');
763
904
  const queued = await this.queuePrompt(command.text, {
764
905
  origin: { kind: 'owner-admin-console', commandId: command.commandId },
765
906
  actor: { browserSession: command.actorBrowserSession },
766
907
  });
767
908
  const receipt = {
909
+ sessionGeneration: this.sessionGeneration,
768
910
  commandId: command.commandId,
769
911
  promptId: queued.promptId,
770
912
  state: queued.queuedBehind > 0 ? 'queued' : 'starting',
@@ -792,6 +934,14 @@ export class AcpSession {
792
934
  * (`queuePrompt({ interrupt: true })`) needs the typed error, because only it
793
935
  * still owes an undelivered message a replay.
794
936
  */
937
+ liveVoiceSupported = true;
938
+ async interruptPrompt(generation, promptId) {
939
+ if (generation !== this.sessionGeneration)
940
+ throw new Error('session_changed: Agent session changed.');
941
+ if (!this.activeTurn?.promptIds.includes(promptId))
942
+ return { state: 'settled' };
943
+ return this.interrupt('local-console');
944
+ }
795
945
  async interrupt(source = 'local-console') {
796
946
  try {
797
947
  await this.cancelActive(source);
@@ -810,6 +960,8 @@ export class AcpSession {
810
960
  if (!this.sessionId)
811
961
  return;
812
962
  const active = this.activeTurn;
963
+ if (active)
964
+ active.fileDeliveryRevoked = true;
813
965
  const previousSource = active?.cancellationSource;
814
966
  if (active && (source === 'owner' || source === 'local-console' || !previousSource))
815
967
  active.cancellationSource = source;
@@ -902,6 +1054,7 @@ export class AcpSession {
902
1054
  this.allowPermission(pending.toolCallId, permissionId);
903
1055
  else
904
1056
  this.releasePermission(pending.toolCallId, permissionId);
1057
+ this.readiness = this.pendingPermissions.size > 0 ? 'awaiting_permission' : 'running';
905
1058
  this.events.emit('permission', {
906
1059
  turnId: this.activeTurn?.id,
907
1060
  origin: this.activeTurn?.origin,
@@ -918,7 +1071,7 @@ export class AcpSession {
918
1071
  promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
919
1072
  payload: { decision, decisionSource: 'manual', optionId },
920
1073
  });
921
- this.readiness = this.pendingPermissions.size > 0 ? 'awaiting_permission' : 'running';
1074
+ this.observation.publish();
922
1075
  return true;
923
1076
  }
924
1077
  /**
@@ -984,6 +1137,8 @@ export class AcpSession {
984
1137
  : { outcome: { outcome: 'cancelled' } });
985
1138
  const settled = decision === 'denied' && !rejectOption ? 'cancelled' : decision;
986
1139
  this.releasePermission(pending.toolCallId, permissionId);
1140
+ if (this.pendingPermissions.size === 0 && this.readiness === 'awaiting_permission')
1141
+ this.readiness = 'running';
987
1142
  this.events.emit('permission', {
988
1143
  turnId: this.activeTurn?.id,
989
1144
  origin: this.activeTurn?.origin,
@@ -1007,12 +1162,59 @@ export class AcpSession {
1007
1162
  ...(rejectOption ? { optionId: rejectOption.optionId } : {}),
1008
1163
  },
1009
1164
  });
1010
- if (this.pendingPermissions.size === 0 && this.readiness === 'awaiting_permission')
1011
- this.readiness = 'running';
1165
+ this.observation.publish();
1012
1166
  }
1013
1167
  exitResult() {
1014
1168
  return this.exit;
1015
1169
  }
1170
+ /** One attempt, tied to the managed live turn captured before any approval/read. */
1171
+ async sendFileToChat(input, signal, copy, policy) {
1172
+ const active = this.activeTurn, sessionId = this.sessionId;
1173
+ const check = () => {
1174
+ signal.throwIfAborted();
1175
+ if (!active || !sessionId || this.activeTurn !== active || this.sessionId !== sessionId || active.fileDeliveryRevoked
1176
+ || active.cancellationSource || this.closing || !this.isAlive() || this.replaying || this.steeringOccupied
1177
+ || active.origin?.kind === 'scheduled-loop' || policy.approval === 'deny')
1178
+ throw Error('CURRENT_CHAT_DELIVERY_UNAVAILABLE');
1179
+ };
1180
+ check();
1181
+ if (this.fileExportBusy)
1182
+ throw Error('FILE_DELIVERY_BUSY');
1183
+ this.fileExportBusy = true;
1184
+ const toolCallId = 'fleet-file-export-' + randomUUID();
1185
+ try {
1186
+ const onAbort = () => {
1187
+ for (const [id, pending] of this.pendingPermissions)
1188
+ if (pending.toolCallId === toolCallId)
1189
+ this.settlePendingAutomatically(id, pending, 'cancelled', undefined, 'File send cancelled');
1190
+ };
1191
+ signal.addEventListener('abort', onAbort, { once: true });
1192
+ let answer;
1193
+ try {
1194
+ answer = await this.requestPermission({ sessionId: sessionId, toolCall: {
1195
+ toolCallId, title: 'Send file to this ACP chat: ' + (input.filename ?? input.path), kind: 'read', status: 'pending',
1196
+ locations: [{ path: resolve(this.options.cwd, input.path) }],
1197
+ }, options: [{ optionId: 'send', name: 'Send file', kind: 'allow_once' }, { optionId: 'deny', name: 'Do not send', kind: 'reject_once' }] }, policy);
1198
+ }
1199
+ finally {
1200
+ signal.removeEventListener('abort', onAbort);
1201
+ }
1202
+ check();
1203
+ if (answer.outcome.outcome !== 'selected' || answer.outcome.optionId !== 'send')
1204
+ throw Error('FILE_SEND_DENIED');
1205
+ const attachment = await copy({ sessionGeneration: this.sessionGeneration, acpSessionId: sessionId, turnId: active.id });
1206
+ // Synchronous check + durable append establishes order with cancellation/stop.
1207
+ // No await, pending-send journal, replay or recovery exists after this point.
1208
+ check();
1209
+ this.conversation.append({ kind: 'file.attached', source: 'agent', sessionGeneration: this.sessionGeneration,
1210
+ acpSessionId: sessionId, promptId: active.id, turnId: active.id, payload: { attachment } });
1211
+ return attachment;
1212
+ }
1213
+ finally {
1214
+ this.releaseTool(toolCallId);
1215
+ this.fileExportBusy = false;
1216
+ }
1217
+ }
1016
1218
  async close() {
1017
1219
  this.closing = true;
1018
1220
  if (this.stallTimer)
@@ -1066,6 +1268,8 @@ export class AcpSession {
1066
1268
  const persisted = this.options.mode === 'resume' && existsSync(this.sessionFile)
1067
1269
  ? readFileSync(this.sessionFile, 'utf8').trim()
1068
1270
  : '';
1271
+ if (this.options.requireResume && (!persisted || !(this.agentCapabilities?.sessionCapabilities?.resume != null || this.agentCapabilities?.loadSession)))
1272
+ throw new Error('TASK_CONTEXT_RESUME_UNAVAILABLE');
1069
1273
  let advertisedConfigOptions;
1070
1274
  let advertisedModes;
1071
1275
  let advertisedModelId;
@@ -1171,6 +1375,7 @@ export class AcpSession {
1171
1375
  kind: 'session.state', sessionGeneration: this.sessionGeneration,
1172
1376
  acpSessionId: this.sessionId, payload: { status: 'idle' },
1173
1377
  });
1378
+ this.observation.publish();
1174
1379
  }
1175
1380
  captureRuntimeMetadata(options, modelId) {
1176
1381
  this.runtimeModel = runtimeSelector(options, 'model');
@@ -1283,8 +1488,9 @@ export class AcpSession {
1283
1488
  }
1284
1489
  }
1285
1490
  /** Keep the original queue slot (including startup) until recovery finishes. */
1286
- async runPrompt(text, turnId = randomUUID(), origin) {
1287
- const result = await this.runSinglePrompt(text, turnId, origin);
1491
+ async runPrompt(members) {
1492
+ const { text, promptId: turnId, origin } = members[0];
1493
+ const result = await this.runSinglePrompt(text, turnId, origin, members);
1288
1494
  const attempt = this.stallAttempt;
1289
1495
  if (!attempt || attempt.turnId !== turnId)
1290
1496
  return result;
@@ -1308,8 +1514,11 @@ export class AcpSession {
1308
1514
  attempt.recoveryStartedAt = Date.now();
1309
1515
  const recoveryOrigin = origin?.kind === 'scheduled-loop'
1310
1516
  ? origin : { kind: 'stall-watchdog' };
1311
- this.admitToLedger(attempt.recoveryId, STALL_RECOVERY_PROMPT, 0, { origin: recoveryOrigin });
1312
- const recovered = await this.runSinglePrompt(STALL_RECOVERY_PROMPT, attempt.recoveryId, recoveryOrigin);
1517
+ const recovery = this.observation.checkpoint(() => {
1518
+ this.admitToLedger(attempt.recoveryId, STALL_RECOVERY_PROMPT, 0, { origin: recoveryOrigin });
1519
+ return this.runSinglePrompt(STALL_RECOVERY_PROMPT, attempt.recoveryId, recoveryOrigin);
1520
+ });
1521
+ const recovered = await recovery;
1313
1522
  attempt.report(attempt.superseded ? 'superseded'
1314
1523
  : recovered.succeeded && attempt.resumed ? 'recovery_completed' : 'blocked_recovery');
1315
1524
  // A failed diagnostic turn must not make startup tear down the session.
@@ -1319,7 +1528,22 @@ export class AcpSession {
1319
1528
  return turnResult(true, 'cancelled', 'diagnostic recovery requires operator attention', undefined, 'stall-watchdog');
1320
1529
  }
1321
1530
  }
1322
- async runSinglePrompt(text, turnId = randomUUID(), origin) {
1531
+ /**
1532
+ * One adapter turn. `members` are the admitted prompts it carries, the first
1533
+ * being the turn's own id; each keeps its own started and completed records
1534
+ * so none is left open and none is reported delivered that was not sent.
1535
+ */
1536
+ async runSinglePrompt(text, turnId = randomUUID(), origin, members = [{ promptId: turnId, text, origin }]) {
1537
+ let completionPayload;
1538
+ const closeMembers = (payload) => {
1539
+ for (const member of members)
1540
+ this.conversation.appendSafe({
1541
+ kind: 'turn.completed', sessionGeneration: this.sessionGeneration,
1542
+ acpSessionId: this.sessionId, promptId: member.promptId, turnId, payload,
1543
+ });
1544
+ };
1545
+ // Not handed to the adapter and not closed: the members stay admitted, and
1546
+ // the next session generation dispatches them again.
1323
1547
  if (!this.sessionId || !this.isAlive())
1324
1548
  return turnResult(false, 'failed', this.lastError ?? 'ACP session is offline');
1325
1549
  this.readiness = 'running';
@@ -1327,18 +1551,20 @@ export class AcpSession {
1327
1551
  this.retryNativeTurnId = undefined;
1328
1552
  let settle;
1329
1553
  const settled = new Promise(resolve => { settle = resolve; });
1330
- this.activeTurn = { toolEvidence: new Map(), startedAt: Date.now(), toolIds: new Set(), lastProgressAt: 0, progressCount: 0, transportFailures: 0, boundaryUnknown: false, id: turnId, output: '', origin, settled, settle };
1554
+ this.activeTurn = { toolEvidence: new Map(), startedAt: Date.now(), toolIds: new Set(), lastProgressAt: 0, progressCount: 0, transportFailures: 0, boundaryUnknown: false, id: turnId, output: '', origin, settled, settle, promptIds: members.map(member => member.promptId), console: members.some(member => member.origin?.kind === 'owner-admin-console') };
1555
+ this.observation.publish();
1331
1556
  this.events.emit('state', { turnId, status: 'running', origin });
1332
- this.conversation.appendSafe({
1557
+ members.forEach((member, index) => this.conversation.appendSafe({
1333
1558
  kind: 'prompt.started', sessionGeneration: this.sessionGeneration,
1334
- acpSessionId: this.sessionId, promptId: turnId, turnId,
1335
- source: conversationSource(origin).source, payload: {},
1336
- });
1559
+ acpSessionId: this.sessionId, promptId: member.promptId, turnId,
1560
+ source: conversationSource(member.origin).source,
1561
+ payload: members.length > 1 ? { batch: { turnId, index, size: members.length } } : {},
1562
+ }));
1337
1563
  try {
1338
1564
  const response = await Promise.race([
1339
1565
  this.connection.agent.request(acp.methods.agent.session.prompt, {
1340
1566
  sessionId: this.sessionId,
1341
- prompt: promptContentBlocks(text, origin),
1567
+ prompt: batchContentBlocks(members),
1342
1568
  }),
1343
1569
  this.terminated,
1344
1570
  ]);
@@ -1349,15 +1575,11 @@ export class AcpSession {
1349
1575
  turnId, stopReason: response.stopReason, origin, cancellationSource,
1350
1576
  });
1351
1577
  this.events.emit('state', { status: 'idle' });
1352
- this.conversation.appendSafe({
1353
- kind: 'turn.completed', sessionGeneration: this.sessionGeneration,
1354
- acpSessionId: this.sessionId, promptId: turnId, turnId,
1355
- payload: {
1356
- outcome: classifyStopReason(response.stopReason),
1357
- stopReason: response.stopReason,
1358
- ...(cancellationSource ? { cancellationSource } : {}),
1359
- },
1360
- });
1578
+ completionPayload = {
1579
+ outcome: classifyStopReason(response.stopReason),
1580
+ stopReason: response.stopReason,
1581
+ ...(cancellationSource ? { cancellationSource } : {}),
1582
+ };
1361
1583
  // The prompt was accepted either way — the agent answered. Whether the
1362
1584
  // turn SUCCEEDED is a separate question, and only `stopReason` answers it.
1363
1585
  return turnResult(true, classifyStopReason(response.stopReason), response.stopReason, this.activeTurn?.id === turnId ? this.activeTurn.output : undefined, this.activeTurn?.id === turnId ? this.activeTurn.cancellationSource : undefined);
@@ -1377,11 +1599,7 @@ export class AcpSession {
1377
1599
  });
1378
1600
  if (this.isAlive())
1379
1601
  this.events.emit('state', { status: 'idle' });
1380
- this.conversation.appendSafe({
1381
- kind: 'turn.completed', sessionGeneration: this.sessionGeneration,
1382
- acpSessionId: this.sessionId, promptId: turnId, turnId,
1383
- payload: { outcome: 'failed', stopReason: this.lastError },
1384
- });
1602
+ completionPayload = { outcome: 'failed', stopReason: this.lastError };
1385
1603
  return turnResult(false, 'failed', this.lastError, this.activeTurn?.id === turnId ? this.activeTurn.output : undefined);
1386
1604
  }
1387
1605
  finally {
@@ -1397,10 +1615,15 @@ export class AcpSession {
1397
1615
  this.cancelEscalation = undefined;
1398
1616
  this.activeTurn = undefined;
1399
1617
  }
1618
+ if (completionPayload)
1619
+ closeMembers(completionPayload);
1620
+ this.observation.publish();
1400
1621
  }
1401
1622
  }
1402
1623
  async steerPrompt(text) {
1403
1624
  this.steeringWasUsed = true;
1625
+ if (this.activeTurn)
1626
+ this.activeTurn.fileDeliveryRevoked = true;
1404
1627
  if (!this.sessionId || !this.isAlive())
1405
1628
  return turnResult(false, 'failed', this.lastError ?? 'ACP session is offline');
1406
1629
  this.steeringRequests++;
@@ -1432,7 +1655,7 @@ export class AcpSession {
1432
1655
  this.steeringRequests--;
1433
1656
  }
1434
1657
  }
1435
- requestPermission(params) {
1658
+ requestPermission(params, neutralPolicy) {
1436
1659
  // `kinds` is a PRIORITY order. Scanning the agent's option array instead
1437
1660
  // (`options.find(o => kinds.includes(o.kind))`) hands the choice to whatever
1438
1661
  // order the agent happened to list, which is exactly how an automatic denial
@@ -1450,7 +1673,7 @@ export class AcpSession {
1450
1673
  // Permission is part of the tool lifecycle. Reserve before any policy or
1451
1674
  // human decision so a monitor wake cannot slip between request and answer.
1452
1675
  this.reservePermission(toolCallId, permissionId);
1453
- if (this.isEffectiveCodexProtectedMcpApproval(params)) {
1676
+ if (!neutralPolicy && this.isEffectiveCodexProtectedMcpApproval(params)) {
1454
1677
  // Protected MCP approval is already the tool's narrow gate. Never turn
1455
1678
  // this one decision into an adapter-wide standing grant.
1456
1679
  const option = choose(['allow_once']);
@@ -1461,9 +1684,9 @@ export class AcpSession {
1461
1684
  this.releasePermission(toolCallId, permissionId);
1462
1685
  return Promise.resolve(response);
1463
1686
  }
1464
- if (this.options.permissions.approval === 'allow' && this.withinAutomaticBoundary(params)) {
1687
+ if ((neutralPolicy ?? this.options.permissions).approval === 'allow' && this.withinAutomaticBoundary(params, neutralPolicy)) {
1465
1688
  const option = choose(['allow_always', 'allow_once']);
1466
- const response = this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${this.options.permissions.filesystem} boundary`);
1689
+ const response = this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${(neutralPolicy ?? this.options.permissions).filesystem} boundary`);
1467
1690
  if (option)
1468
1691
  this.allowPermission(toolCallId, permissionId);
1469
1692
  else
@@ -1473,8 +1696,8 @@ export class AcpSession {
1473
1696
  // A live grace window still counts as attended: the controller may be
1474
1697
  // mid-reconnect, and denying instantly is exactly what the grace prevents.
1475
1698
  const unattended = this.controllerCount === 0 && !this.controllerGrace
1476
- && this.options.permissions.unattended === 'deny';
1477
- if (this.options.permissions.approval === 'deny' || unattended) {
1699
+ && (neutralPolicy ?? this.options.permissions).unattended === 'deny';
1700
+ if ((neutralPolicy ?? this.options.permissions).approval === 'deny' || unattended) {
1478
1701
  // reject_once FIRST: `reject_always` teaches the agent a standing rule from
1479
1702
  // a decision no human made, so one unattended denial would silently disable
1480
1703
  // the tool for the rest of the session.
@@ -1487,42 +1710,20 @@ export class AcpSession {
1487
1710
  }
1488
1711
  const timeoutMs = this.options.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS;
1489
1712
  const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
1490
- this.readiness = 'awaiting_permission';
1491
- this.events.emit('permission', {
1492
- turnId: this.activeTurn?.id,
1493
- origin: this.activeTurn?.origin,
1494
- permissionId,
1495
- toolCallId: this.activeTurn?.origin?.kind === 'scheduled-loop'
1496
- ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
1497
- title: this.activeTurn?.origin?.kind === 'scheduled-loop'
1713
+ const presented = {
1714
+ toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : toolCallId,
1715
+ title: scheduledTurn(this.activeTurn)
1498
1716
  ? 'Scheduled-loop permission requested' : params.toolCall.title ?? 'Permission requested',
1499
- status: 'pending',
1500
1717
  options: params.options.map(option => ({
1501
1718
  optionId: option.optionId,
1502
- name: this.activeTurn?.origin?.kind === 'scheduled-loop' ? option.kind : option.name,
1719
+ name: scheduledTurn(this.activeTurn) ? option.kind : option.name,
1503
1720
  kind: option.kind,
1504
1721
  })),
1505
- });
1506
- this.conversation.appendSafe({
1507
- kind: 'permission.requested', sessionGeneration: this.sessionGeneration,
1508
- acpSessionId: this.sessionId, permissionId,
1509
- promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
1510
- toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
1511
- payload: {
1512
- toolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
1513
- title: scheduledTurn(this.activeTurn)
1514
- ? 'Scheduled-loop permission requested' : params.toolCall.title ?? 'Permission requested',
1515
- options: params.options.map(option => ({
1516
- optionId: option.optionId,
1517
- name: scheduledTurn(this.activeTurn) ? option.kind : option.name,
1518
- kind: option.kind,
1519
- })),
1520
- expiresAt,
1521
- },
1522
- });
1722
+ expiresAt,
1723
+ };
1523
1724
  return new Promise(resolve => {
1524
1725
  const pending = {
1525
- options: params.options, resolve,
1726
+ options: params.options, resolve, presented,
1526
1727
  toolCallId,
1527
1728
  eventToolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : toolCallId,
1528
1729
  };
@@ -1531,6 +1732,18 @@ export class AcpSession {
1531
1732
  }, timeoutMs);
1532
1733
  pending.expiry.unref?.();
1533
1734
  this.pendingPermissions.set(permissionId, pending);
1735
+ this.readiness = 'awaiting_permission';
1736
+ this.events.emit('permission', {
1737
+ turnId: this.activeTurn?.id, origin: this.activeTurn?.origin,
1738
+ permissionId, ...presented, status: 'pending',
1739
+ });
1740
+ this.conversation.appendSafe({
1741
+ kind: 'permission.requested', sessionGeneration: this.sessionGeneration,
1742
+ acpSessionId: this.sessionId, permissionId,
1743
+ promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
1744
+ toolCallId: presented.toolCallId, payload: presented,
1745
+ });
1746
+ this.observation.publish();
1534
1747
  });
1535
1748
  }
1536
1749
  /**
@@ -1575,8 +1788,8 @@ export class AcpSession {
1575
1788
  ? { outcome: { outcome: 'selected', optionId: option.optionId } }
1576
1789
  : { outcome: { outcome: 'cancelled' } };
1577
1790
  }
1578
- withinAutomaticBoundary(params) {
1579
- const filesystem = this.options.permissions.filesystem;
1791
+ withinAutomaticBoundary(params, policy) {
1792
+ const filesystem = (policy ?? this.options.permissions).filesystem;
1580
1793
  if (filesystem === 'unrestricted')
1581
1794
  return true;
1582
1795
  if (filesystem === 'read-only' && params.toolCall.kind !== 'read')
@@ -1707,9 +1920,19 @@ export class AcpSession {
1707
1920
  const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
1708
1921
  const messagePhase = update.sessionUpdate === 'agent_message_chunk'
1709
1922
  ? this.codexMessagePhase(update) : undefined;
1710
- this.recordConversationUpdate(update, scheduled, messagePhase === 'commentary');
1923
+ // Install live capability state before the durable append can synchronously
1924
+ // notify a subscriber that reenters observation at the new ledger barrier.
1711
1925
  if (update.sessionUpdate === 'config_option_update')
1712
1926
  this.captureRuntimeMetadata(update.configOptions);
1927
+ if (update.sessionUpdate === 'available_commands_update')
1928
+ this.advertisedCommands = new Set((update.availableCommands ?? []).map(command => command.name));
1929
+ this.recordConversationUpdate(update, scheduled);
1930
+ if (update.sessionUpdate === 'usage_update') {
1931
+ // Claude subscription rate-limit snapshot (claude-agent-acp forwards the SDK rate_limit_event).
1932
+ const rateLimit = update._meta?.['_claude/rateLimit'];
1933
+ if (rateLimit)
1934
+ recordClaudeRateLimit(this.options.stateDir, rateLimit);
1935
+ }
1713
1936
  switch (update.sessionUpdate) {
1714
1937
  case 'agent_message_chunk': {
1715
1938
  const phase = messagePhase;
@@ -1768,6 +1991,7 @@ export class AcpSession {
1768
1991
  default:
1769
1992
  break;
1770
1993
  }
1994
+ this.observation.publish();
1771
1995
  }
1772
1996
  /**
1773
1997
  * Codex ACP's phase extension is the only currently supported visibility
@@ -1785,13 +2009,17 @@ export class AcpSession {
1785
2009
  return undefined;
1786
2010
  return phase === 'commentary' || phase === 'final_answer' ? phase : 'ambiguous';
1787
2011
  }
1788
- /** Normalize every ACP update losslessly into the durable ledger. */
1789
- recordConversationUpdate(update, scheduled, commentary = false) {
2012
+ /**
2013
+ * Normalize every ACP update losslessly into the durable ledger. The ledger is the conversation the
2014
+ * owner reads, so the agent's commentary between its tools is kept as written, like its final answer.
2015
+ */
2016
+ recordConversationUpdate(update, scheduled) {
1790
2017
  const normalized = normalizeSessionUpdate(update, scheduled ? {
1791
2018
  redactText: SCHEDULED_LOOP_REDACTION,
1792
2019
  redactToolCallId: 'scheduled-loop-tool',
1793
- }
1794
- : commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
2020
+ } : {});
2021
+ if (normalized.kind === 'capabilities.updated' && 'commands' in normalized.payload)
2022
+ this.presentedCommands = normalized.payload.commands ?? [];
1795
2023
  this.conversation.appendSafe({
1796
2024
  kind: normalized.kind,
1797
2025
  sessionGeneration: this.sessionGeneration,
@@ -1805,6 +2033,10 @@ export class AcpSession {
1805
2033
  });
1806
2034
  }
1807
2035
  // ── conversation ledger access (AgentSession) ─────────────────────────────
2036
+ taskNoticeBinding() {
2037
+ return this.activeTurn && this.sessionId ? { sessionGeneration: this.sessionGeneration, acpSessionId: this.sessionId, promptId: this.activeTurn.id } : undefined;
2038
+ }
2039
+ recordTaskCreated(binding, notice) { appendTaskCreated(this.conversation, binding, notice); }
1808
2040
  conversationPage(request = {}) {
1809
2041
  const floor = Number(this.conversationStartCursor ?? 0);
1810
2042
  const requested = Number(request.after ?? 0);
@@ -1826,12 +2058,43 @@ export class AcpSession {
1826
2058
  snapshot: this.conversationSnapshot(),
1827
2059
  };
1828
2060
  }
2061
+ subscribeConversationFailure(listener) {
2062
+ return this.conversation.subscribeFailure(listener);
2063
+ }
2064
+ conversationReplayAvailable(expectedGeneration) {
2065
+ return expectedGeneration === this.sessionGeneration && this.conversation.generationDurable(expectedGeneration);
2066
+ }
2067
+ replayConversation(request) {
2068
+ if (request.expectedGeneration !== this.sessionGeneration)
2069
+ throw new Error('session generation changed');
2070
+ return this.conversation.replay(request);
2071
+ }
2072
+ observeConversation(listener) {
2073
+ return this.observation.observe(listener);
2074
+ }
2075
+ captureObservation() {
2076
+ const snapshot = this.snapshot();
2077
+ return {
2078
+ ...this.conversationSnapshot(),
2079
+ ...pendingInventory(this.pendingPrompts),
2080
+ ledgerBarrier: Number(this.conversation.lastCursor() ?? 0),
2081
+ alive: snapshot.alive,
2082
+ readiness: snapshot.alive ? snapshot.readiness : 'offline',
2083
+ activePromptIds: [...(this.activeTurn?.promptIds ?? [])],
2084
+ pendingPermissions: [...this.pendingPermissions].flatMap(([permissionId, pending]) => pending.presented ? [{ permissionId, ...pending.presented }] : []),
2085
+ commands: this.presentedCommands,
2086
+ runtimeModel: snapshot.runtimeModel,
2087
+ reasoningEffort: snapshot.reasoningEffort,
2088
+ permissionMode: snapshot.permissionMode,
2089
+ };
2090
+ }
1829
2091
  conversationSnapshot() {
1830
2092
  return {
1831
2093
  sessionGeneration: this.sessionGeneration,
1832
2094
  readiness: this.isAlive() ? this.readiness : 'offline',
1833
2095
  queueDepth: this.queueDepth,
1834
2096
  pendingPermissionIds: [...this.pendingPermissions.keys()],
2097
+ ...pendingInventory(this.pendingPrompts),
1835
2098
  ...(this.conversation.degraded ? { historyDegraded: true } : {}),
1836
2099
  };
1837
2100
  }