@wjarka/cezarion 0.15.1 → 0.16.0

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 (462) hide show
  1. package/README.md +167 -46
  2. package/dist/agent-config/catalog.d.ts +6 -2
  3. package/dist/agent-config/catalog.js +78 -3
  4. package/dist/agent-config/catalog.js.map +1 -1
  5. package/dist/agent-config/model-settings/shared.js +6 -3
  6. package/dist/agent-config/model-settings/shared.js.map +1 -1
  7. package/dist/agent-config/models.js +4 -0
  8. package/dist/agent-config/models.js.map +1 -1
  9. package/dist/agent-config/validate.js +6 -0
  10. package/dist/agent-config/validate.js.map +1 -1
  11. package/dist/artifacts/cli.js +10 -2
  12. package/dist/artifacts/cli.js.map +1 -1
  13. package/dist/automations/coordinator.d.ts +7 -0
  14. package/dist/automations/coordinator.js +22 -2
  15. package/dist/automations/coordinator.js.map +1 -1
  16. package/dist/automations/github-poller.d.ts +4 -4
  17. package/dist/automations/github-poller.js.map +1 -1
  18. package/dist/automations/schedule-runner.d.ts +130 -0
  19. package/dist/automations/schedule-runner.js +326 -0
  20. package/dist/automations/schedule-runner.js.map +1 -0
  21. package/dist/automations/scheduler.d.ts +38 -7
  22. package/dist/automations/scheduler.js +90 -22
  23. package/dist/automations/scheduler.js.map +1 -1
  24. package/dist/automations/store.d.ts +80 -3
  25. package/dist/automations/store.js +208 -16
  26. package/dist/automations/store.js.map +1 -1
  27. package/dist/automations/task-template.d.ts +42 -6
  28. package/dist/automations/task-template.js +152 -24
  29. package/dist/automations/task-template.js.map +1 -1
  30. package/dist/automations/types.d.ts +142 -6
  31. package/dist/automations/types.js +64 -6
  32. package/dist/automations/types.js.map +1 -1
  33. package/dist/autosave-holders.js +8 -12
  34. package/dist/autosave-holders.js.map +1 -1
  35. package/dist/ci-wait/client.d.ts +8 -1
  36. package/dist/ci-wait/client.js +43 -1
  37. package/dist/ci-wait/client.js.map +1 -1
  38. package/dist/ci-wait/controller.d.ts +83 -4
  39. package/dist/ci-wait/controller.js +76 -11
  40. package/dist/ci-wait/controller.js.map +1 -1
  41. package/dist/ci-wait/errors.d.ts +7 -1
  42. package/dist/ci-wait/errors.js +36 -0
  43. package/dist/ci-wait/errors.js.map +1 -1
  44. package/dist/ci-wait/github.d.ts +2 -0
  45. package/dist/ci-wait/github.js +3 -1
  46. package/dist/ci-wait/github.js.map +1 -1
  47. package/dist/ci-wait/mcp.d.ts +2 -98
  48. package/dist/ci-wait/mcp.js +11 -25
  49. package/dist/ci-wait/mcp.js.map +1 -1
  50. package/dist/ci-wait/supervisor.d.ts +2 -1
  51. package/dist/ci-wait/supervisor.js +2 -1
  52. package/dist/ci-wait/supervisor.js.map +1 -1
  53. package/dist/ci-wait/tools.d.ts +307 -0
  54. package/dist/ci-wait/tools.js +63 -0
  55. package/dist/ci-wait/tools.js.map +1 -0
  56. package/dist/config.d.ts +3 -0
  57. package/dist/config.js +7 -0
  58. package/dist/config.js.map +1 -1
  59. package/dist/contract/agent-config.d.ts +5 -0
  60. package/dist/contract/agent-profiles.d.ts +15 -0
  61. package/dist/contract/automation-schedule.d.ts +60 -0
  62. package/dist/contract/automations.d.ts +208 -36
  63. package/dist/contract/delegation.d.ts +96 -0
  64. package/dist/contract/discovery.d.ts +7 -0
  65. package/dist/contract/github.d.ts +1 -0
  66. package/dist/contract/health.d.ts +6 -0
  67. package/dist/contract/index.d.ts +3 -0
  68. package/dist/contract/index.js +1445 -745
  69. package/dist/contract/preview.d.ts +508 -0
  70. package/dist/contract/runs.d.ts +716 -15
  71. package/dist/contract/skills.d.ts +39 -0
  72. package/dist/contract/workflows.d.ts +6 -0
  73. package/dist/contract/workspace.d.ts +65 -0
  74. package/dist/contract/zoned-time.d.ts +35 -0
  75. package/dist/core/agent-env.d.ts +11 -0
  76. package/dist/core/agent-env.js +27 -5
  77. package/dist/core/agent-env.js.map +1 -1
  78. package/dist/core/agent-profiles.js +7 -0
  79. package/dist/core/agent-profiles.js.map +1 -1
  80. package/dist/core/agent-runner.d.ts +49 -1
  81. package/dist/core/agent-runner.js +2 -1
  82. package/dist/core/agent-runner.js.map +1 -1
  83. package/dist/core/backend-detect.d.ts +3 -2
  84. package/dist/core/backend-detect.js +27 -1
  85. package/dist/core/backend-detect.js.map +1 -1
  86. package/dist/core/claude-cli-runner.d.ts +1 -0
  87. package/dist/core/claude-cli-runner.js +69 -19
  88. package/dist/core/claude-cli-runner.js.map +1 -1
  89. package/dist/core/codex-app-server-runner.d.ts +1 -0
  90. package/dist/core/codex-app-server-runner.js +200 -25
  91. package/dist/core/codex-app-server-runner.js.map +1 -1
  92. package/dist/core/codex-app-server-transport.d.ts +7 -3
  93. package/dist/core/codex-app-server-transport.js +11 -9
  94. package/dist/core/codex-app-server-transport.js.map +1 -1
  95. package/dist/core/cursor-acp-runner.d.ts +1 -0
  96. package/dist/core/cursor-acp-runner.js +24 -9
  97. package/dist/core/cursor-acp-runner.js.map +1 -1
  98. package/dist/core/host-model-catalog.js +2 -0
  99. package/dist/core/host-model-catalog.js.map +1 -1
  100. package/dist/core/managed-session.js +2 -0
  101. package/dist/core/managed-session.js.map +1 -1
  102. package/dist/core/model-identity.js +2 -0
  103. package/dist/core/model-identity.js.map +1 -1
  104. package/dist/core/model-presets.js +3 -0
  105. package/dist/core/model-presets.js.map +1 -1
  106. package/dist/core/omp-model-catalog.d.ts +43 -0
  107. package/dist/core/omp-model-catalog.js +205 -0
  108. package/dist/core/omp-model-catalog.js.map +1 -0
  109. package/dist/core/omp-runner.d.ts +73 -0
  110. package/dist/core/omp-runner.js +990 -0
  111. package/dist/core/omp-runner.js.map +1 -0
  112. package/dist/core/omp-ui-mapper.d.ts +106 -0
  113. package/dist/core/omp-ui-mapper.js +882 -0
  114. package/dist/core/omp-ui-mapper.js.map +1 -0
  115. package/dist/core/opencode-server-runner.d.ts +18 -4
  116. package/dist/core/opencode-server-runner.js +337 -96
  117. package/dist/core/opencode-server-runner.js.map +1 -1
  118. package/dist/core/opencode-session-error.d.ts +11 -0
  119. package/dist/core/opencode-session-error.js +26 -0
  120. package/dist/core/opencode-session-error.js.map +1 -0
  121. package/dist/core/opencode-ui-mapper.js +4 -36
  122. package/dist/core/opencode-ui-mapper.js.map +1 -1
  123. package/dist/core/pi-runner.d.ts +1 -0
  124. package/dist/core/pi-runner.js +75 -12
  125. package/dist/core/pi-runner.js.map +1 -1
  126. package/dist/core/provider-auth.d.ts +9 -2
  127. package/dist/core/provider-auth.js +43 -6
  128. package/dist/core/provider-auth.js.map +1 -1
  129. package/dist/core/runner-factory.js +3 -0
  130. package/dist/core/runner-factory.js.map +1 -1
  131. package/dist/core/runner-model-catalog.js +1 -0
  132. package/dist/core/runner-model-catalog.js.map +1 -1
  133. package/dist/core/runner-stderr.d.ts +2 -0
  134. package/dist/core/runner-stderr.js +20 -0
  135. package/dist/core/runner-stderr.js.map +1 -0
  136. package/dist/core/session-process.d.ts +22 -0
  137. package/dist/core/session-process.js +202 -0
  138. package/dist/core/session-process.js.map +1 -0
  139. package/dist/core/ui-events.d.ts +1 -1
  140. package/dist/core/usage-limit.js +3 -60
  141. package/dist/core/usage-limit.js.map +1 -1
  142. package/dist/data-gitignore.d.ts +6 -0
  143. package/dist/data-gitignore.js +57 -0
  144. package/dist/data-gitignore.js.map +1 -0
  145. package/dist/delegation/capacity.d.ts +19 -0
  146. package/dist/delegation/capacity.js +34 -0
  147. package/dist/delegation/capacity.js.map +1 -0
  148. package/dist/delegation/cli.js +13 -4
  149. package/dist/delegation/cli.js.map +1 -1
  150. package/dist/delegation/conversations.d.ts +4 -2
  151. package/dist/delegation/conversations.js +8 -12
  152. package/dist/delegation/conversations.js.map +1 -1
  153. package/dist/delegation/credentials.d.ts +2 -0
  154. package/dist/delegation/credentials.js +10 -0
  155. package/dist/delegation/credentials.js.map +1 -1
  156. package/dist/delegation/destroy-observation.d.ts +42 -0
  157. package/dist/delegation/destroy-observation.js +115 -0
  158. package/dist/delegation/destroy-observation.js.map +1 -0
  159. package/dist/delegation/execution-identity.d.ts +6 -0
  160. package/dist/delegation/execution-identity.js +1 -1
  161. package/dist/delegation/execution-identity.js.map +1 -1
  162. package/dist/delegation/policy.d.ts +3 -2
  163. package/dist/delegation/policy.js +7 -5
  164. package/dist/delegation/policy.js.map +1 -1
  165. package/dist/delegation/process-liveness.d.ts +57 -20
  166. package/dist/delegation/process-liveness.js +114 -92
  167. package/dist/delegation/process-liveness.js.map +1 -1
  168. package/dist/delegation/provision.js +6 -6
  169. package/dist/delegation/provision.js.map +1 -1
  170. package/dist/delegation/results.js +4 -2
  171. package/dist/delegation/results.js.map +1 -1
  172. package/dist/delegation/retry-backoff.d.ts +23 -0
  173. package/dist/delegation/retry-backoff.js +18 -0
  174. package/dist/delegation/retry-backoff.js.map +1 -0
  175. package/dist/delegation/routes.d.ts +64 -58
  176. package/dist/delegation/scratch-cleanup.d.ts +29 -0
  177. package/dist/delegation/scratch-cleanup.js +143 -0
  178. package/dist/delegation/scratch-cleanup.js.map +1 -0
  179. package/dist/delegation/service.d.ts +20 -1
  180. package/dist/delegation/service.js +233 -70
  181. package/dist/delegation/service.js.map +1 -1
  182. package/dist/delegation/transport.d.ts +12 -6
  183. package/dist/delegation/workspace.d.ts +23 -4
  184. package/dist/delegation/workspace.js +147 -31
  185. package/dist/delegation/workspace.js.map +1 -1
  186. package/dist/git-worktree-lock-helper.js +8 -3
  187. package/dist/git-worktree-lock-helper.js.map +1 -1
  188. package/dist/git-worktree-lock.d.ts +6 -1
  189. package/dist/git-worktree-lock.js +10 -4
  190. package/dist/git-worktree-lock.js.map +1 -1
  191. package/dist/git-worktree-release.d.ts +20 -0
  192. package/dist/git-worktree-release.js +27 -0
  193. package/dist/git-worktree-release.js.map +1 -0
  194. package/dist/git-worktree.d.ts +8 -2
  195. package/dist/git-worktree.js +19 -2
  196. package/dist/git-worktree.js.map +1 -1
  197. package/dist/handoff.d.ts +2 -2
  198. package/dist/handoff.js +1 -1
  199. package/dist/index.js +109 -76
  200. package/dist/index.js.map +1 -1
  201. package/dist/paths.d.ts +1 -1
  202. package/dist/paths.js +5 -1
  203. package/dist/paths.js.map +1 -1
  204. package/dist/preview/cdp.d.ts +13 -0
  205. package/dist/preview/cdp.js +69 -0
  206. package/dist/preview/cdp.js.map +1 -0
  207. package/dist/preview/chromium.d.ts +75 -0
  208. package/dist/preview/chromium.js +394 -0
  209. package/dist/preview/chromium.js.map +1 -0
  210. package/dist/preview/dev-server.d.ts +68 -0
  211. package/dist/preview/dev-server.js +341 -0
  212. package/dist/preview/dev-server.js.map +1 -0
  213. package/dist/preview/host.d.ts +154 -0
  214. package/dist/preview/host.js +681 -0
  215. package/dist/preview/host.js.map +1 -0
  216. package/dist/preview/registration.d.ts +63 -0
  217. package/dist/preview/registration.js +109 -0
  218. package/dist/preview/registration.js.map +1 -0
  219. package/dist/preview/session.d.ts +73 -0
  220. package/dist/preview/session.js +347 -0
  221. package/dist/preview/session.js.map +1 -0
  222. package/dist/preview/url.d.ts +6 -0
  223. package/dist/preview/url.js +17 -0
  224. package/dist/preview/url.js.map +1 -0
  225. package/dist/runs/agent-tmpdir-removal.d.ts +15 -0
  226. package/dist/runs/agent-tmpdir-removal.js +60 -0
  227. package/dist/runs/agent-tmpdir-removal.js.map +1 -0
  228. package/dist/runs/agent-tmpdir.d.ts +12 -1
  229. package/dist/runs/agent-tmpdir.js +123 -34
  230. package/dist/runs/agent-tmpdir.js.map +1 -1
  231. package/dist/runs/delegation-state.d.ts +5 -1
  232. package/dist/runs/delegation-state.js +9 -0
  233. package/dist/runs/delegation-state.js.map +1 -1
  234. package/dist/runs/event-history.d.ts +4 -4
  235. package/dist/runs/event-history.js +97 -117
  236. package/dist/runs/event-history.js.map +1 -1
  237. package/dist/runs/history-compressor.d.ts +29 -0
  238. package/dist/runs/history-compressor.js +116 -0
  239. package/dist/runs/history-compressor.js.map +1 -0
  240. package/dist/runs/history-file.d.ts +36 -0
  241. package/dist/runs/history-file.js +463 -0
  242. package/dist/runs/history-file.js.map +1 -0
  243. package/dist/runs/human-ask-summary.js +18 -13
  244. package/dist/runs/human-ask-summary.js.map +1 -1
  245. package/dist/runs/legacy-index.d.ts +62 -0
  246. package/dist/runs/legacy-index.js +168 -0
  247. package/dist/runs/legacy-index.js.map +1 -0
  248. package/dist/runs/raw-record.d.ts +52 -0
  249. package/dist/runs/raw-record.js +145 -0
  250. package/dist/runs/raw-record.js.map +1 -0
  251. package/dist/runs/retention.d.ts +13 -6
  252. package/dist/runs/retention.js +42 -13
  253. package/dist/runs/retention.js.map +1 -1
  254. package/dist/runs/run-claims.d.ts +23 -0
  255. package/dist/runs/run-claims.js +24 -0
  256. package/dist/runs/run-claims.js.map +1 -0
  257. package/dist/runs/run-database.d.ts +385 -0
  258. package/dist/runs/run-database.js +930 -0
  259. package/dist/runs/run-database.js.map +1 -0
  260. package/dist/runs/run-index.d.ts +35 -9
  261. package/dist/runs/run-index.js +200 -25
  262. package/dist/runs/run-index.js.map +1 -1
  263. package/dist/runs/run-row.d.ts +44 -0
  264. package/dist/runs/run-row.js +84 -0
  265. package/dist/runs/run-row.js.map +1 -0
  266. package/dist/runs/run-search.d.ts +29 -0
  267. package/dist/runs/run-search.js +63 -0
  268. package/dist/runs/run-search.js.map +1 -0
  269. package/dist/runs/store-open-error.d.ts +24 -0
  270. package/dist/runs/store-open-error.js +61 -0
  271. package/dist/runs/store-open-error.js.map +1 -0
  272. package/dist/runs/store.d.ts +667 -32
  273. package/dist/runs/store.js +2153 -324
  274. package/dist/runs/store.js.map +1 -1
  275. package/dist/runs/transcript-facts.d.ts +77 -0
  276. package/dist/runs/transcript-facts.js +302 -0
  277. package/dist/runs/transcript-facts.js.map +1 -0
  278. package/dist/runs/webhook.js +5 -4
  279. package/dist/runs/webhook.js.map +1 -1
  280. package/dist/runs/worker-execution.d.ts +22 -0
  281. package/dist/runs/worker-execution.js +30 -0
  282. package/dist/runs/worker-execution.js.map +1 -0
  283. package/dist/server/capabilities.d.ts +1 -0
  284. package/dist/server/capabilities.js +2 -0
  285. package/dist/server/capabilities.js.map +1 -1
  286. package/dist/server/cockpit-ownership.d.ts +8 -0
  287. package/dist/server/cockpit-ownership.js +19 -0
  288. package/dist/server/cockpit-ownership.js.map +1 -1
  289. package/dist/server/forge/github.d.ts +1 -4
  290. package/dist/server/forge/github.js +47 -23
  291. package/dist/server/forge/github.js.map +1 -1
  292. package/dist/server/git.js +1 -1
  293. package/dist/server/git.js.map +1 -1
  294. package/dist/server/open-in-app.js +1 -0
  295. package/dist/server/open-in-app.js.map +1 -1
  296. package/dist/server/preview-socket.d.ts +36 -0
  297. package/dist/server/preview-socket.js +126 -0
  298. package/dist/server/preview-socket.js.map +1 -0
  299. package/dist/server/project-context.d.ts +31 -4
  300. package/dist/server/project-context.js +73 -9
  301. package/dist/server/project-context.js.map +1 -1
  302. package/dist/server/provider-action-gate.js +1 -0
  303. package/dist/server/provider-action-gate.js.map +1 -1
  304. package/dist/server/repo-branches.d.ts +4 -3
  305. package/dist/server/repo-branches.js +5 -4
  306. package/dist/server/repo-branches.js.map +1 -1
  307. package/dist/server/server.d.ts +4500 -2727
  308. package/dist/server/server.js +651 -202
  309. package/dist/server/server.js.map +1 -1
  310. package/dist/server/upgrade-router.d.ts +23 -0
  311. package/dist/server/upgrade-router.js +53 -0
  312. package/dist/server/upgrade-router.js.map +1 -0
  313. package/dist/server/validators.d.ts +6 -1
  314. package/dist/server/validators.js +4 -4
  315. package/dist/server/validators.js.map +1 -1
  316. package/dist/server/ws.d.ts +16 -16
  317. package/dist/server/ws.js +56 -54
  318. package/dist/server/ws.js.map +1 -1
  319. package/dist/skills-remote.d.ts +14 -0
  320. package/dist/skills-remote.js +91 -15
  321. package/dist/skills-remote.js.map +1 -1
  322. package/dist/task-cli/cli.js +40 -16
  323. package/dist/task-cli/cli.js.map +1 -1
  324. package/dist/task-cli/discovery.d.ts +4 -3
  325. package/dist/task-cli/discovery.js +4 -3
  326. package/dist/task-cli/discovery.js.map +1 -1
  327. package/dist/task-cli/http.d.ts +2 -0
  328. package/dist/task-cli/http.js +10 -4
  329. package/dist/task-cli/http.js.map +1 -1
  330. package/dist/task-cli/projections.d.ts +5 -2
  331. package/dist/task-cli/projections.js +13 -0
  332. package/dist/task-cli/projections.js.map +1 -1
  333. package/dist/task-cli/watch.d.ts +15 -2
  334. package/dist/task-cli/watch.js +31 -9
  335. package/dist/task-cli/watch.js.map +1 -1
  336. package/dist/ui-state.d.ts +4 -3
  337. package/dist/ui-state.js +8 -3
  338. package/dist/ui-state.js.map +1 -1
  339. package/dist/workflows/grouped-command.d.ts +31 -0
  340. package/dist/workflows/grouped-command.js +143 -0
  341. package/dist/workflows/grouped-command.js.map +1 -0
  342. package/dist/workflows/run.d.ts +223 -42
  343. package/dist/workflows/run.js +1316 -398
  344. package/dist/workflows/run.js.map +1 -1
  345. package/dist/workflows/types.d.ts +12 -0
  346. package/dist/workflows/types.js +20 -0
  347. package/dist/workflows/types.js.map +1 -1
  348. package/dist/workspace/agent-accounts.d.ts +5 -1
  349. package/dist/workspace/agent-accounts.js +1 -0
  350. package/dist/workspace/agent-accounts.js.map +1 -1
  351. package/dist/workspace/agent-profiles.js +1 -1
  352. package/dist/workspace/agent-profiles.js.map +1 -1
  353. package/dist/workspace/config.d.ts +3 -1
  354. package/dist/workspace/config.js +1 -0
  355. package/dist/workspace/config.js.map +1 -1
  356. package/dist/worktree-setup.d.ts +85 -0
  357. package/dist/worktree-setup.js +164 -0
  358. package/dist/worktree-setup.js.map +1 -0
  359. package/package.json +7 -5
  360. package/scripts/benchmark-run-store-fixture.ts +428 -0
  361. package/scripts/benchmark-run-store.ts +743 -0
  362. package/scripts/benchmark-stuck-destroys.ts +143 -0
  363. package/scripts/mock-autonomous.mjs +11 -0
  364. package/scripts/mock-ci-tool.mjs +20 -5
  365. package/scripts/mock-claude.mjs +110 -1
  366. package/scripts/mock-codex-app-server.mjs +117 -2
  367. package/scripts/mock-codex-coalesce-steer.mjs +59 -0
  368. package/scripts/mock-codex-reverse-steer.mjs +130 -0
  369. package/scripts/mock-cursor-acp.mjs +75 -1
  370. package/scripts/mock-omp-rpc.mjs +605 -0
  371. package/scripts/mock-opencode-serve.mjs +312 -22
  372. package/scripts/mock-pi-rpc.mjs +109 -3
  373. package/scripts/mock-runner-crash.mjs +42 -0
  374. package/scripts/mock-silent-tail.mjs +78 -0
  375. package/scripts/mock-turn-messages.mjs +5 -0
  376. package/scripts/omp-ci-wait.mjs +29 -0
  377. package/scripts/omp-restrict-delegation.yml +4 -0
  378. package/scripts/pi-ci-wait.mjs +12 -9
  379. package/scripts/seed-archive-profile.ts +3 -3
  380. package/web/dist/assets/agent-accounts-CFOBMVHc.js +1 -0
  381. package/web/dist/assets/alert-dialog-BrpNUwMt.js +1 -0
  382. package/web/dist/assets/app-window-CSHwDX9N.js +1 -0
  383. package/web/dist/assets/button-C_rd7CPp.js +43 -0
  384. package/web/dist/assets/compare-variants-D6WXhvXA.js +1 -0
  385. package/web/dist/assets/diff-DrEARXTV.js +3 -0
  386. package/web/dist/assets/diff-controls-B1-j7nFg.js +1 -0
  387. package/web/dist/assets/{diff-view-DPn6bQ5W.js → diff-view-DvGLjdTk.js} +3 -3
  388. package/web/dist/assets/dropdown-menu-BegOZFrj.js +1 -0
  389. package/web/dist/assets/format-DpNe3S0A.js +1 -0
  390. package/web/dist/assets/git-toolbar-Cw4ljmhB.js +1 -0
  391. package/web/dist/assets/github-CfV5sqL0.js +1 -0
  392. package/web/dist/assets/{image-preview-DJ8cgb1Z.js → image-preview-FOEfqpZP.js} +1 -1
  393. package/web/dist/assets/index-DgWWlOLy.js +13 -0
  394. package/web/dist/assets/index-fBFnuvxu.css +1 -0
  395. package/web/dist/assets/{nav-row-styles-3j64kH5s.js → nav-row-styles-vp2JbhuJ.js} +1 -1
  396. package/web/dist/assets/play-CmVAj_fu.js +1 -0
  397. package/web/dist/assets/popover-BohP6WTx.js +1 -0
  398. package/web/dist/assets/preview-pane-connected-qTA5KNoJ.js +3 -0
  399. package/web/dist/assets/preview-state-vuMviq6q.js +1 -0
  400. package/web/dist/assets/prompt-templates-Cubdut_C.js +15 -0
  401. package/web/dist/assets/repo-git-Cf4t5PD0.js +1 -0
  402. package/web/dist/assets/{run-diff-5-RRdcVz.js → run-diff-BpkUW37Z.js} +3 -3
  403. package/web/dist/assets/run-header-68WHf9wV.js +1 -0
  404. package/web/dist/assets/skills-DiVIgKeU.js +1 -0
  405. package/web/dist/assets/square-terminal-B6RYYieR.js +1 -0
  406. package/web/dist/assets/status-dot-CqTJHSP9.js +1 -0
  407. package/web/dist/assets/{tab-link-K0hjzEXR.js → tab-link-B07Hu4QX.js} +1 -1
  408. package/web/dist/assets/task-changes--lQF6EYf.js +1 -0
  409. package/web/dist/assets/task-commits-Cx0qy4DN.js +1 -0
  410. package/web/dist/assets/task-files-D50s-gKW.js +2 -0
  411. package/web/dist/assets/task-github-item-DaqmSwYT.js +1 -0
  412. package/web/dist/assets/task-thread-BPQ64lui.js +7 -0
  413. package/web/dist/assets/{task-thread-Bw_nQuTx.css → task-thread-zNE2Q9E-.css} +1 -1
  414. package/web/dist/assets/textarea-D20O4Pw0.js +1 -0
  415. package/web/dist/assets/thread-state-DWcFMrvb.js +4 -0
  416. package/web/dist/assets/use-hand-to-agent-state-BLIGmwbd.css +1 -0
  417. package/web/dist/assets/use-hand-to-agent-state-Cy9eyyfL.js +1 -0
  418. package/web/dist/assets/{utils-fzNfbPzA.js → utils-JsL3ZsJf.js} +1 -1
  419. package/web/dist/assets/workflows-jwG7uxqi.js +11 -0
  420. package/web/dist/assets/{zoomable-image-CDmZ_Rtl.js → zoomable-image-BJJ64KGc.js} +1 -1
  421. package/web/dist/cezarion-lockup-dark.svg +1 -13
  422. package/web/dist/cezarion-lockup-light.svg +1 -13
  423. package/web/dist/cezarion-mark-dark.svg +1 -12
  424. package/web/dist/cezarion-mark-light.svg +1 -12
  425. package/web/dist/index.html +16 -21
  426. package/web/dist/assets/agent-accounts-CU2ZDv24.js +0 -1
  427. package/web/dist/assets/alert-dialog-C-xYE_dg.js +0 -43
  428. package/web/dist/assets/chevron-right-BENZ-1ZN.js +0 -1
  429. package/web/dist/assets/collapsible-DD9J91Iv.js +0 -1
  430. package/web/dist/assets/compare-variants-5FmqpFSN.js +0 -1
  431. package/web/dist/assets/diff-controls-Bj0YrioE.js +0 -1
  432. package/web/dist/assets/diff-rE8rm75J2.js +0 -3
  433. package/web/dist/assets/diff-stat-BKDCSVfJ.js +0 -1
  434. package/web/dist/assets/dropdown-menu-Ch5BYQh_.js +0 -1
  435. package/web/dist/assets/format-dBiNDNVe.js +0 -1
  436. package/web/dist/assets/git-toolbar-ClVvf5iF.js +0 -1
  437. package/web/dist/assets/github-BkNOyour.css +0 -1
  438. package/web/dist/assets/github-DeZMeMwJ.js +0 -1
  439. package/web/dist/assets/github-item-detail-W5F6GVjF.js +0 -1
  440. package/web/dist/assets/github-sidebar-model-DcMu-IK2.js +0 -1
  441. package/web/dist/assets/highlighter-CdHqIwFr.js +0 -3
  442. package/web/dist/assets/index-CatemBEL.js +0 -8
  443. package/web/dist/assets/index-MvfD8bZJ.css +0 -1
  444. package/web/dist/assets/markdown-zdKeLIYA.js +0 -2
  445. package/web/dist/assets/mobile-run-bar-BCfmFi8V.js +0 -1
  446. package/web/dist/assets/prompt-templates-CBPp39IU.js +0 -15
  447. package/web/dist/assets/reference-chip-D6owncJT.js +0 -1
  448. package/web/dist/assets/repo-git-CtdkZFey.js +0 -1
  449. package/web/dist/assets/run-header-BcjoDi0W.js +0 -1
  450. package/web/dist/assets/sheet-ChnYVrp1.js +0 -1
  451. package/web/dist/assets/skills-C8YKp2vr.js +0 -1
  452. package/web/dist/assets/task-changes-BW8YdWG8.js +0 -1
  453. package/web/dist/assets/task-commits-q2kB2y6L.js +0 -1
  454. package/web/dist/assets/task-files-pPL6_3Cr.js +0 -2
  455. package/web/dist/assets/task-github-item-BXF5R52U.js +0 -1
  456. package/web/dist/assets/task-thread-DtkFL3EC.js +0 -10
  457. package/web/dist/assets/textarea-DxlkE-Yd.js +0 -1
  458. package/web/dist/assets/token-metrics-DIAjrPlM.js +0 -1
  459. package/web/dist/assets/use-desktop-CrDny2uG.js +0 -1
  460. package/web/dist/assets/use-submit-shortcut-KWJsC906.js +0 -1
  461. package/web/dist/assets/workflows-TmcPEj2K.js +0 -11
  462. /package/web/dist/assets/{lib-BQXq3kEf2.js → lib-BQXq3kEf.js} +0 -0
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Stuck-destroy benchmark (hearsay-tools/cezarion#879): what permanently stuck worker destroys
3
+ * cost an otherwise idle cezar. Run it unchanged before and after a change to the cleanup loops.
4
+ *
5
+ * npm run bench:stuck-destroys -w @wjarka/cezarion # 20 workers, 10 minutes
6
+ * node --import tsx packages/cezar/scripts/benchmark-stuck-destroys.ts --workers 0 --minutes 10
7
+ *
8
+ * Flags: --workers (20) --minutes (10) --out (a JSON file for the result row).
9
+ *
10
+ * Method:
11
+ * - One process builds what `serve` builds for a project: `RunStore` (keepLive), `RunManager` and
12
+ * `DelegationService`, on a temp Git repo, with `CEZ_DRY_RUN=1` and production cadence (no
13
+ * timer is overridden).
14
+ * - Each worker is settled the way a finished worker is: a complete execution checkpoint, status
15
+ * `review`, a real owned worktree and a local scratch dir. A `sleep` child holds each of the
16
+ * two, so neither can ever be removed: the destroy stays incomplete and the scratch is retained.
17
+ * - Every worker is destroyed once through the human route, as Clean up does. Only then does the
18
+ * window start, so the measurement is the retry and reprobe loops alone.
19
+ * - CPU is this process (`process.cpuUsage`, every thread) plus the children it reaped during the
20
+ * window (`/proc/self/stat` cutime/cstime: the git processes the loops spawn). The holders are
21
+ * reaped only after the window. Event-loop delay is what an HTTP request would wait.
22
+ * - Compare N workers against `--workers 0`; the difference is what the stuck workers add.
23
+ */
24
+ import { execFileSync, spawn, type ChildProcess } from 'node:child_process';
25
+ import { randomUUID } from 'node:crypto';
26
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
27
+ import { cpus, tmpdir } from 'node:os';
28
+ import { join } from 'node:path';
29
+ import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
30
+ import { setTimeout as sleep } from 'node:timers/promises';
31
+ import { parseArgs } from 'node:util';
32
+ import type { RunStore as RunStoreInstance } from '../src/runs/store.ts';
33
+ import type { RunManager as RunManagerInstance } from '../src/workflows/run.ts';
34
+
35
+ process.env.CEZ_DRY_RUN = '1';
36
+ process.env.CEZ_DELEGATION = '1';
37
+ process.env.CEZ_AUTONAME = '0';
38
+ // The repo's own sandbox for ~/.cezar, so a benchmark never touches the user's registry; one this
39
+ // run creates is removed with the rest of its teardown.
40
+ const ownHome = process.env.CEZ_HOME ? undefined : mkdtempSync(join(tmpdir(), 'cez-bench-home-'));
41
+ if (ownHome) process.env.CEZ_HOME = ownHome;
42
+
43
+ const { values } = parseArgs({ options: {
44
+ workers: { type: 'string', default: '20' },
45
+ minutes: { type: 'string', default: '10' },
46
+ out: { type: 'string' },
47
+ } });
48
+ const workers = Number(values.workers), minutes = Number(values.minutes);
49
+ if (!Number.isInteger(workers) || workers < 0 || !(minutes > 0)) throw new Error('--workers must be a whole number >= 0 and --minutes > 0');
50
+
51
+ const { RunStore } = await import('../src/runs/store.ts');
52
+ const { RunManager } = await import('../src/workflows/run.ts');
53
+ const { DelegationService } = await import('../src/delegation/service.ts');
54
+ const { planOwnedWorkspace, ensureOwnedWorkspace } = await import('../src/delegation/workspace.ts');
55
+ const { WorkspaceSemaphore } = await import('../src/workspace/semaphore.ts');
56
+ const { agentTmpDir } = await import('../src/runs/agent-tmpdir.ts');
57
+
58
+ /** Clock ticks of this process's reaped children (`cutime + cstime`), in ms; 0 off Linux. */
59
+ function childrenCpuMs(): number {
60
+ if (process.platform !== 'linux') return 0;
61
+ const stat = readFileSync('/proc/self/stat', 'utf8');
62
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
63
+ // After the comm field, cutime and cstime are fields 16 and 17 of stat(5), i.e. index 13 and 14 here.
64
+ const ticks = Number(fields[13]) + Number(fields[14]);
65
+ const hz = Number(execFileSync('getconf', ['CLK_TCK'], { encoding: 'utf8' }).trim()) || 100;
66
+ return ticks * 1000 / hz;
67
+ }
68
+
69
+ const git = (cwd: string, ...args: string[]) => execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
70
+ const root = mkdtempSync(join(tmpdir(), 'cez-bench-stuck-'));
71
+ git(root, 'init', '-q', '-b', 'main');
72
+ git(root, 'config', 'gc.auto', '0');
73
+ git(root, 'config', 'maintenance.auto', 'false');
74
+ git(root, '-c', 'user.name=bench', '-c', 'user.email=bench@local', 'commit', '--allow-empty', '-qm', 'base');
75
+ const sha = git(root, 'rev-parse', 'HEAD');
76
+ const dataDir = join(root, '.ai/cezar');
77
+
78
+ // Teardown runs however the run ends, so a failed setup, destroy or measurement leaves no
79
+ // `sleep` holder and no temporary repository behind.
80
+ const holders: ChildProcess[] = [];
81
+ let store: RunStoreInstance | undefined, manager: RunManagerInstance | undefined, detach: (() => void) | undefined;
82
+ try {
83
+ store = RunStore.open(dataDir, { keepLive: true });
84
+ manager = new RunManager(store, root, { semaphore: new WorkspaceSemaphore({ initial: { maxParallel: 1 } }) });
85
+ const service = new DelegationService();
86
+ detach = service.registerProject({ id: 'bench', root, store, manager });
87
+ const parent = store.createRun({ title: 'parent', task: 'parent', workflow: 'quick-task', steps: [] });
88
+ store.updateRun(parent.id, { status: 'waiting', delegation: { role: 'root', permissions: ['spawn'], receipts: [] } });
89
+
90
+ const hold = (cwd: string) => { holders.push(spawn('sleep', ['86400'], { cwd, stdio: 'ignore' })); };
91
+ const ids: string[] = [];
92
+ for (let i = 0; i < workers; i++) {
93
+ const id = randomUUID();
94
+ const workspace = await planOwnedWorkspace(root, id, sha);
95
+ store.createOwnedRun({ title: `worker ${i}`, task: 'stuck', workflow: 'quick-task', runner: 'claude', steps: [{ id: 'task', name: 'Task', kind: 'agent' }] },
96
+ parent.id, randomUUID(), { role: 'worker', permissions: [], parentRunId: parent.id, workspace }, 'a'.repeat(64));
97
+ const generation = store.commitWorkerExecutionStart(id);
98
+ const created = await ensureOwnedWorkspace(root, store.getRun(id)!);
99
+ store.updateRun(id, { status: 'review', worktreePath: created.path, branch: created.branch });
100
+ if (!store.commitWorkerExecutionComplete(id, generation)) throw new Error(`worker ${id} did not complete`);
101
+ const scratch = agentTmpDir(dataDir, id);
102
+ mkdirSync(scratch, { recursive: true });
103
+ hold(created.path); hold(scratch);
104
+ ids.push(id);
105
+ }
106
+ // What boot does after recovery: arm pending destroys and the scratch reprobes.
107
+ service.armDestroyRetries('bench');
108
+ for (const id of ids) {
109
+ const result = await service.destroyForHuman('bench', id).catch((error: Error) => ({ state: 'error', error: error.message }));
110
+ if (result.state === 'complete') throw new Error(`worker ${id} was not held: its destroy completed`);
111
+ }
112
+
113
+ const delay = monitorEventLoopDelay({ resolution: 10 });
114
+ delay.enable();
115
+ const cpu0 = process.cpuUsage(), children0 = childrenCpuMs(), wall0 = performance.now();
116
+ await sleep(minutes * 60_000);
117
+ const cpu = process.cpuUsage(cpu0), children = childrenCpuMs() - children0, wall = performance.now() - wall0;
118
+ delay.disable();
119
+
120
+ const opened = store;
121
+ const pending = ids.filter(id => { const run = opened.getRun(id); return run?.delegation?.role === 'worker' && run.delegation.destroy?.phase !== 'complete'; }).length;
122
+ const row = {
123
+ workers, minutes, pendingDestroys: pending,
124
+ userMs: Math.round(cpu.user / 1000), systemMs: Math.round(cpu.system / 1000), childrenMs: Math.round(children),
125
+ cpuPercent: Number(((cpu.user + cpu.system) / 1000 / wall * 100).toFixed(2)),
126
+ cpuWithChildrenPercent: Number((((cpu.user + cpu.system) / 1000 + children) / wall * 100).toFixed(2)),
127
+ loopDelayP99Ms: Number((delay.percentile(99) / 1e6).toFixed(1)), loopDelayMaxMs: Number((delay.max / 1e6).toFixed(1)),
128
+ cores: cpus().length, node: process.version,
129
+ };
130
+ console.log('| workers | minutes | pending | user ms | system ms | children ms | CPU % | CPU % incl. children | loop p99 ms | loop max ms |');
131
+ console.log('| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |');
132
+ console.log(`| ${row.workers} | ${row.minutes} | ${row.pendingDestroys} | ${row.userMs} | ${row.systemMs} | ${row.childrenMs} | ${row.cpuPercent} | ${row.cpuWithChildrenPercent} | ${row.loopDelayP99Ms} | ${row.loopDelayMaxMs} |`);
133
+ if (values.out) writeFileSync(values.out, `${JSON.stringify(row, null, 2)}\n`);
134
+
135
+ } finally {
136
+ for (const child of holders) child.kill('SIGKILL');
137
+ detach?.();
138
+ manager?.dispose();
139
+ store?.close();
140
+ rmSync(root, { recursive: true, force: true });
141
+ if (ownHome) rmSync(ownHome, { recursive: true, force: true });
142
+ }
143
+ process.exit(0);
@@ -0,0 +1,11 @@
1
+ // Shared responses only; each caller encodes its own native runner wire (#426).
2
+ let cap = false;
3
+ export function autonomousReply(prompt) {
4
+ const askCap = prompt.includes('mock:autonomous-ask-cap') || prompt.includes('mock:autonomous-readiness-idle');
5
+ if (prompt.includes('mock:autonomous-cap') || askCap) cap = true;
6
+ if (askCap) return 'CEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}';
7
+ if (prompt.startsWith('Continue working autonomously until the task is fully complete.')) {
8
+ return cap ? 'Still working.' : 'Autonomous work finished.\nCEZ:DONE';
9
+ }
10
+ return 'Still working.';
11
+ }
@@ -1,23 +1,30 @@
1
1
  // Offline harness fixtures share the real bundled MCP protocol, never a fake receipt.
2
2
  import { writeFileSync, existsSync } from 'node:fs';
3
3
  import { basename } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
6
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
7
 
8
8
  export async function probeCiTool(backend, wire, pr = process.env.CEZ_MOCK_CI_PR) {
9
9
  if (!pr) return;
10
+ return probeCezarTool(backend, wire, 'cezar_wait_for_ci', { pr });
11
+ }
12
+
13
+ // Calls one cezar tool the way the real harness would: through the wired MCP server (or Pi/OMP extension).
14
+ export async function probeCezarTool(backend, wire, name, args) {
10
15
  let result;
11
- if (backend === 'pi') {
16
+ if (backend === 'pi' || backend === 'omp') {
12
17
  const extensions = wire.flatMap((arg, index) => arg === '--extension' ? [wire[index + 1]] : []);
13
- const path = extensions.find(path => basename(path) === 'pi-ci-wait.mjs');
18
+ const path = extensions.find(path => basename(path) === `${backend}-ci-wait.mjs`);
14
19
  if (!path) throw new Error('CI extension absent');
15
20
  // The real Pi loader compiles TS when running from source; installed script uses JS.
16
21
  if (existsSync(new URL('../src/ci-wait/mcp.ts', import.meta.url))) { const { register } = await import('tsx/esm/api'); register(); }
17
22
  const { default: extension } = await import(pathToFileURL(path));
18
23
  const tools = [];
19
24
  extension({ registerTool(tool) { tools.push(tool); } });
20
- result = { names: tools.map(tool => tool.name), response: await tools[0].execute('ci-1', { pr }) };
25
+ const tool = tools.find(tool => tool.name === name);
26
+ if (!tool) throw new Error(`${name} not registered`);
27
+ result = { names: tools.map(tool => tool.name), response: await tool.execute('ci-1', args) };
21
28
  } else {
22
29
  let server; let env;
23
30
  if (backend === 'claude') {
@@ -39,7 +46,7 @@ export async function probeCiTool(backend, wire, pr = process.env.CEZ_MOCK_CI_PR
39
46
  try {
40
47
  await client.connect(new StdioClientTransport({ command: server.command, args: server.args, env, stderr: 'pipe' }));
41
48
  const list = await client.listTools();
42
- result = { names: list.tools.map(tool => tool.name), response: await client.callTool({ name: 'cezar_wait_for_ci', arguments: { pr } }) };
49
+ result = { names: list.tools.map(tool => tool.name), response: await client.callTool({ name, arguments: args }) };
43
50
  } finally { await client.close(); }
44
51
  }
45
52
  if (process.env.CEZ_MOCK_CI_RESULT) writeFileSync(process.env.CEZ_MOCK_CI_RESULT, JSON.stringify(result));
@@ -51,3 +58,11 @@ export async function ciPrompt(backend, wire, text) {
51
58
  const result = await probeCiTool(backend, wire, pr);
52
59
  return JSON.stringify(result.response);
53
60
  }
61
+
62
+ // `mock:preview-serve <port>` registers the e2e fixture app (`node preview-app.mjs <port>`) as a live preview.
63
+ export async function previewPrompt(backend, wire, text) {
64
+ const port = Number(/mock:preview-serve\s+(\d+)/.exec(text)?.[1]);
65
+ const fixture = fileURLToPath(new URL('../../web/e2e/fixtures/preview-app.mjs', import.meta.url));
66
+ const result = await probeCezarTool(backend, wire, 'cezar_preview_serve', { command: `node "${fixture}" ${port}`, port, label: 'web' });
67
+ return JSON.stringify(result.response);
68
+ }
@@ -14,6 +14,13 @@ function watchdogStall(prompt) {
14
14
  if (prompt.includes('held-pipe')) {
15
15
  watchdogSpawn(process.execPath, ['-e', 'setTimeout(() => {}, 5000)'], { stdio: ['ignore', process.stdout, process.stderr] });
16
16
  }
17
+ if (prompt.includes('leftover')) {
18
+ // hearsay-tools/cezarion#890: one child stays in the session's process group and ignores
19
+ // SIGTERM; one leaves the group with setsid. Both exit on their own as a cleanup backstop.
20
+ const leftover = (file) => `process.on('SIGTERM',()=>{}); require('fs').writeFileSync(${JSON.stringify(file)}, String(process.pid)); setTimeout(()=>{},20000)`;
21
+ watchdogSpawn(process.execPath, ['-e', leftover('leftover-group.pid')], { stdio: 'ignore' });
22
+ watchdogSpawn(process.execPath, ['-e', leftover('leftover-session.pid')], { stdio: 'ignore', detached: true }).unref();
23
+ }
17
24
  return true;
18
25
  }
19
26
 
@@ -27,6 +34,15 @@ import { createInterface } from 'node:readline';
27
34
  import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
28
35
 
29
36
  import * as parityGateFs from 'node:fs';
37
+ async function waitForMockRelease(fallbackMs) {
38
+ const file = process.env.CEZ_MOCK_RELEASE_FILE;
39
+ if (!file) { if (fallbackMs) await sleep(fallbackMs); return; }
40
+ const deadline = Date.now() + 15_000;
41
+ while (!parityGateFs.existsSync(file)) {
42
+ if (Date.now() > deadline) throw new Error('CEZ_MOCK_RELEASE_FILE was not created');
43
+ await sleep(10);
44
+ }
45
+ }
30
46
 
31
47
  // #401: let the test observe the actual monitoring park before releasing late wire frames.
32
48
  async function afterParityPark(prompt) {
@@ -118,7 +134,55 @@ function writeHandoffAndTodo() {
118
134
  }
119
135
 
120
136
  async function respond(userText, imageCount, uuid) {
137
+ if (userText.includes('mock:crash-stderr')) {
138
+ const { crashWithStderr } = await import('./mock-runner-crash.mjs');
139
+ if (crashWithStderr(userText, '{"type":"assistant","message":')) return;
140
+ }
121
141
  emitReplay(uuid, userText);
142
+ if (userText.includes('mock:autonomous') || userText.startsWith('Continue working autonomously until the task is fully complete.')) {
143
+ const { autonomousReply } = await import('./mock-autonomous.mjs');
144
+ const text = autonomousReply(userText);
145
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text }] } });
146
+ emit({ type: 'result', subtype: 'success', result: text, user_message_uuids: [uuid].filter(Boolean), usage: { input_tokens: 10, output_tokens: 5 } });
147
+ return;
148
+ }
149
+ if (userText.includes('mock:silent-tail') || userText.includes('mock:tool-tail') || userText.includes('Your last turn ended without a message to the user.')) {
150
+ const silent = await import('./mock-silent-tail.mjs');
151
+ silent.noteSilentTailPrompt(userText);
152
+ if (silent.isFinalMessageNudge(userText)) {
153
+ const kind = silent.finalMessageNudgeKind();
154
+ if (kind === 'ack-only') return;
155
+ if (kind === 'late') await silent.sleep(silent.LATE_REPLY_MS);
156
+ if (kind === 'silent') {
157
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'thinking', thinking: silent.SILENT_TAIL_REASONING }] } });
158
+ emit({ type: 'result', subtype: 'success', result: '', user_message_uuids: [uuid].filter(Boolean), usage: { input_tokens: 10, output_tokens: 5 } });
159
+ } else {
160
+ if (kind === 'slow-done') {
161
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text: silent.SLOW_DONE_PREFIX }] } });
162
+ await silent.sleep(silent.SLOW_DONE_TAIL_MS);
163
+ }
164
+ const text = kind === 'standing' ? silent.FINAL_MESSAGE_STANDING : silent.SILENT_TAIL_DONE;
165
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text }] } });
166
+ emit({ type: 'result', subtype: 'success', result: text, user_message_uuids: [uuid].filter(Boolean), usage: { input_tokens: 10, output_tokens: 5 } });
167
+ }
168
+ return;
169
+ }
170
+ if (silent.isSilentTailScenario(userText)) {
171
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text: silent.SILENT_TAIL_OPENING }] } });
172
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_silent_gh', name: 'Bash', input: { command: 'gh issue create' } }] } });
173
+ emit({ type: 'user', message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_silent_gh', content: 'created' }] } });
174
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'thinking', thinking: silent.SILENT_TAIL_REASONING }] } });
175
+ emit({ type: 'result', subtype: 'success', result: silent.SILENT_TAIL_OPENING, user_message_uuids: [uuid].filter(Boolean), usage: { input_tokens: 20, output_tokens: 10 } });
176
+ return;
177
+ }
178
+ if (silent.isToolTailScenario(userText)) {
179
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text: silent.TOOL_TAIL_OPENING }] } });
180
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'tool_use', id: 'toolu_tool_tail', name: 'Bash', input: { command: 'git status --short' } }] } });
181
+ emit({ type: 'user', message: { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'toolu_tool_tail', content: ' M src/example.ts' }] } });
182
+ emit({ type: 'result', subtype: 'success', result: silent.TOOL_TAIL_OPENING, user_message_uuids: [uuid].filter(Boolean), usage: { input_tokens: 20, output_tokens: 10 } });
183
+ return;
184
+ }
185
+ }
122
186
  // `mock:steer-tool` → one tool call; lines written while it runs are consumed after
123
187
  // it (replayed with their uuid) and settled by the SAME result, like Claude 2.1.280 (#505).
124
188
  if (userText.includes('mock:steer-tool')) {
@@ -146,8 +210,15 @@ async function respond(userText, imageCount, uuid) {
146
210
  return;
147
211
  }
148
212
  // #401: result-only full text; mapResult and v1 use the same fallback.
213
+ if (userText.includes('mock:turn-messages:')) {
214
+ const { turnMessages } = await import('./mock-turn-messages.mjs');
215
+ const messages = turnMessages(userText);
216
+ for (const text of messages) emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text }] } });
217
+ emit({ type: 'result', subtype: 'success', result: messages.join('\n'), usage: { input_tokens: 10, output_tokens: 5 } });
218
+ return;
219
+ }
149
220
  if (!userText.includes('[cez-namer]') && userText.includes('mock:ask-snapshot')) {
150
- emit({ type: 'result', subtype: 'success', result: 'Using the CEZ:ASK structured question format instead:\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}', usage: { input_tokens: 10, output_tokens: 5 } });
221
+ emit({ type: 'result', subtype: 'success', result: userText.includes('mock:ask-snapshot-bad') ? 'CEZ:ASK {not valid json' : 'Using the CEZ:ASK structured question format instead:\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}', usage: { input_tokens: 10, output_tokens: 5 } });
151
222
  return;
152
223
  }
153
224
  if (!userText.includes('[cez-namer]') && userText.includes('mock:ask-prose')) {
@@ -161,6 +232,13 @@ async function respond(userText, imageCount, uuid) {
161
232
  emit({ type: 'result', subtype: 'success', result: text, usage: { input_tokens: 20, output_tokens: 10 } });
162
233
  return;
163
234
  }
235
+ if (!userText.includes('[cez-namer]') && userText.includes('mock:preview-serve')) {
236
+ const { previewPrompt } = await import('./mock-ci-tool.mjs');
237
+ const text = await previewPrompt('claude', process.argv.slice(2), userText);
238
+ emit({ type: 'assistant', message: { role: 'assistant', content: [{ type: 'text', text }] } });
239
+ emit({ type: 'result', subtype: 'success', result: text, usage: { input_tokens: 20, output_tokens: 10 } });
240
+ return;
241
+ }
164
242
  turn += 1;
165
243
  await sleep(250);
166
244
  // `mock:done` anywhere in the message → the reply ends with the CEZ:DONE
@@ -271,6 +349,37 @@ async function respond(userText, imageCount, uuid) {
271
349
  emit({ type: 'result', subtype: 'success', result: 'busy complete' });
272
350
  return;
273
351
  }
352
+ if (userText.includes('mock:hold-done')) {
353
+ await waitForMockRelease(400);
354
+ const held = 'parity hold-done: content after the pause\n\nCEZ:DONE';
355
+ emit({
356
+ type: 'assistant',
357
+ message: { role: 'assistant', content: [{ type: 'text', text: held }], usage: { input_tokens: 20, output_tokens: 10 } },
358
+ });
359
+ emit({ type: 'result', subtype: 'success', result: held, usage: { input_tokens: 20, output_tokens: 10 }, total_cost_usd: 0.0001 });
360
+ return;
361
+ }
362
+ if (userText.includes('mock:hold-ask')) {
363
+ await waitForMockRelease(400);
364
+ const ask = 'Pick one.\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}';
365
+ emit({
366
+ type: 'assistant',
367
+ message: { role: 'assistant', content: [{ type: 'text', text: ask }], usage: { input_tokens: 20, output_tokens: 10 } },
368
+ });
369
+ emit({ type: 'result', subtype: 'success', result: ask, usage: { input_tokens: 20, output_tokens: 10 }, total_cost_usd: 0.0001 });
370
+ return;
371
+ }
372
+ if (userText.includes('mock:hold-gated')) {
373
+ await waitForMockRelease(250);
374
+ const gated = 'parity hold-gated: content after the pause';
375
+ emit({
376
+ type: 'assistant',
377
+ message: { role: 'assistant', content: [{ type: 'text', text: gated }], usage: { input_tokens: 20, output_tokens: 10 } },
378
+ });
379
+ await sleep(250);
380
+ emit({ type: 'result', subtype: 'success', result: gated, usage: { input_tokens: 20, output_tokens: 10 }, total_cost_usd: 0.0001 });
381
+ return;
382
+ }
274
383
  if (userText.includes('mock:hold')) {
275
384
  await sleep(250);
276
385
  const held = 'parity hold: content after the pause';
@@ -14,6 +14,13 @@ function watchdogStall(prompt) {
14
14
  if (prompt.includes('held-pipe')) {
15
15
  watchdogSpawn(process.execPath, ['-e', 'setTimeout(() => {}, 5000)'], { stdio: ['ignore', process.stdout, process.stderr] });
16
16
  }
17
+ if (prompt.includes('leftover')) {
18
+ // hearsay-tools/cezarion#890: one child stays in the session's process group and ignores
19
+ // SIGTERM; one leaves the group with setsid. Both exit on their own as a cleanup backstop.
20
+ const leftover = (file) => `process.on('SIGTERM',()=>{}); require('fs').writeFileSync(${JSON.stringify(file)}, String(process.pid)); setTimeout(()=>{},20000)`;
21
+ watchdogSpawn(process.execPath, ['-e', leftover('leftover-group.pid')], { stdio: 'ignore' });
22
+ watchdogSpawn(process.execPath, ['-e', leftover('leftover-session.pid')], { stdio: 'ignore', detached: true }).unref();
23
+ }
17
24
  return true;
18
25
  }
19
26
 
@@ -42,12 +49,15 @@ const write = (obj) => process.stdout.write(`${JSON.stringify(obj)}\n`);
42
49
  // #505: like the real app-server, any active main-thread turn accepts `turn/steer`.
43
50
  // Steers accepted by an ordinary scripted turn are read just before it completes:
44
51
  // a userMessage item (echoing clientUserMessageId as clientId) and an echo reply.
52
+ let autonomousTurn = 0;
45
53
  let activeTurnId = null;
46
54
  let pendingSteers = [];
47
55
  let steerEchoSerial = 0;
48
56
  const emit = (obj) => {
49
57
  const ending = (obj.method === 'turn/completed' || obj.method === 'turn/failed') && (!obj.params?.threadId || obj.params.threadId === 'th_mock_1');
50
- if (ending && obj.method === 'turn/completed' && pendingSteers.length) {
58
+ // #486 review: CEZ_MOCK_CODEX_NO_STEER_ECHO=1 admits the steer (RPC result) but never
59
+ // echoes its userMessage, so an accepted-but-unread follow-up can complete the turn.
60
+ if (ending && obj.method === 'turn/completed' && pendingSteers.length && process.env.CEZ_MOCK_CODEX_NO_STEER_ECHO !== '1') {
51
61
  const turnId = activeTurnId ?? 'turn_mock_1';
52
62
  for (const entry of pendingSteers.splice(0)) {
53
63
  const item = { type: 'userMessage', id: `item_user_generic_${++steerEchoSerial}`, clientId: entry.clientId, content: [{ type: 'text', text: entry.text }] };
@@ -71,6 +81,15 @@ let ciWire;
71
81
  let steerTurn = null;
72
82
  let steerSerial = 0;
73
83
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
84
+ async function waitForMockRelease(fallbackMs) {
85
+ const file = process.env.CEZ_MOCK_RELEASE_FILE;
86
+ if (!file) { if (fallbackMs) await sleep(fallbackMs); return; }
87
+ const deadline = Date.now() + 15_000;
88
+ while (!parityGateFs.existsSync(file)) {
89
+ if (Date.now() > deadline) throw new Error('CEZ_MOCK_RELEASE_FILE was not created');
90
+ await sleep(10);
91
+ }
92
+ }
74
93
  const completeSteerTurn = (turnId) => {
75
94
  steerTurn = null;
76
95
  emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: turnId, status: 'completed' } } });
@@ -190,6 +209,12 @@ rl.on('line', async (line) => {
190
209
  emit({ id: msg.id, result: { thread: { id: msg.params?.threadId }, sandbox } });
191
210
  }
192
211
  } else if (msg.method === 'turn/start') {
212
+ const openingCrashText = msg.params?.input?.map?.((part) => part.text ?? '').join('\n') ?? '';
213
+ if (openingCrashText.includes('mock:crash-stderr-pre-ack')) {
214
+ const { crashWithStderr } = await import('./mock-runner-crash.mjs');
215
+ crashWithStderr(openingCrashText, '{"method":"turn/started","params":');
216
+ return;
217
+ }
193
218
  if (rejectedPermissionOverride) permissionWarning();
194
219
  activeTurnId = 'turn_mock_1';
195
220
  // owned-input-delivery.testkit.ts patches the exact `emit(...)` line below; keep it verbatim.
@@ -197,9 +222,17 @@ rl.on('line', async (line) => {
197
222
  emit({ id: msg.id, result: { turn: { id: 'turn_mock_1' } } });
198
223
  }
199
224
  const turnText = msg.params?.input?.map?.((part) => part.text ?? '').join('\n') ?? '';
225
+ let silent;
226
+ if (turnText.includes('mock:silent-tail') || turnText.includes('Your last turn ended without a message to the user.')) {
227
+ silent = await import('./mock-silent-tail.mjs');
228
+ silent.noteSilentTailPrompt(turnText);
229
+ if (silent.isAckOnlyNudge(turnText)) return;
230
+ if (silent.isLateNudge(turnText) || silent.isLateTurnStartNudge(turnText)) await silent.sleep(silent.LATE_REPLY_MS);
231
+ }
200
232
  if (!turnText.includes('mock:no-progress-ack-only')) {
201
233
  emit({ method: 'turn/started', params: { turn: { id: 'turn_mock_1', status: 'inProgress', items: [] } } });
202
234
  }
235
+ if (silent?.isLateTurnStartNudge(turnText)) await silent.sleep(silent.LATE_TURN_START_HOLD_MS);
203
236
  // The real app-server records the turn's own input as a userMessage item,
204
237
  // echoing clientUserMessageId as clientId (probe 0.155.1, #505).
205
238
  const opening = { type: 'userMessage', id: `item_user_open_${++steerEchoSerial}`, clientId: msg.params?.clientUserMessageId ?? null, content: [{ type: 'text', text: turnText }] };
@@ -207,6 +240,49 @@ rl.on('line', async (line) => {
207
240
  emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: opening } });
208
241
  emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: opening } });
209
242
  }
243
+ if (turnText.includes('mock:crash-stderr')) {
244
+ const { crashWithStderr } = await import('./mock-runner-crash.mjs');
245
+ await new Promise(resolve => setTimeout(resolve, 30));
246
+ if (crashWithStderr(turnText, '{"method":"item/commandExecution/outputDelta","params":')) return;
247
+ }
248
+ if (turnText.includes('mock:autonomous') || turnText.startsWith('Continue working autonomously until the task is fully complete.')) {
249
+ const { autonomousReply } = await import('./mock-autonomous.mjs');
250
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: `autonomous-${++autonomousTurn}`, text: autonomousReply(turnText) } } });
251
+ emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: 'turn_mock_1', status: 'completed' } } });
252
+ return;
253
+ }
254
+ if (turnText.includes('mock:silent-tail') || turnText.includes('mock:tool-tail') || turnText.includes('Your last turn ended without a message to the user.')) {
255
+ const silent = await import('./mock-silent-tail.mjs');
256
+ silent.noteSilentTailPrompt(turnText);
257
+ if (silent.isFinalMessageNudge(turnText)) {
258
+ const kind = silent.finalMessageNudgeKind();
259
+ if (kind === 'silent') {
260
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'reasoning', id: 'silent-nudge-rsn', summary: silent.SILENT_TAIL_REASONING, content: silent.SILENT_TAIL_REASONING } } });
261
+ } else {
262
+ if (kind === 'slow-done') {
263
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'silent-nudge-prefix', text: silent.SLOW_DONE_PREFIX } } });
264
+ await silent.sleep(silent.SLOW_DONE_TAIL_MS);
265
+ }
266
+ const text = kind === 'standing' ? silent.FINAL_MESSAGE_STANDING : silent.SILENT_TAIL_DONE;
267
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'silent-nudge-done', text } } });
268
+ }
269
+ emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: 'turn_mock_1', status: 'completed' } } });
270
+ return;
271
+ }
272
+ if (silent.isSilentTailScenario(turnText)) {
273
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'silent-open', text: silent.SILENT_TAIL_OPENING } } });
274
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'commandExecution', id: 'silent-gh', command: ['bash', '-lc', 'gh issue create'], cwd: '/repo', status: 'completed', exitCode: 0 } } });
275
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'reasoning', id: 'silent-rsn', summary: silent.SILENT_TAIL_REASONING, content: silent.SILENT_TAIL_REASONING } } });
276
+ emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: 'turn_mock_1', status: 'completed' } } });
277
+ return;
278
+ }
279
+ if (silent.isToolTailScenario(turnText)) {
280
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'tool-tail-open', text: silent.TOOL_TAIL_OPENING } } });
281
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'commandExecution', id: 'tool-tail-git', command: ['bash', '-lc', 'git status --short'], cwd: '/repo', status: 'completed', exitCode: 0 } } });
282
+ emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: 'turn_mock_1', status: 'completed' } } });
283
+ return;
284
+ }
285
+ }
210
286
  // Native thread attribution from collab-agent-tool-call.ndjson (#121/#401).
211
287
  if (turnText.includes('mock:subagent-after-park')) {
212
288
  emit({ method: 'item/started', params: { threadId: 'th_mock_1', item: { type: 'collabAgentToolCall', id: 'park-spawn', tool: 'spawnAgent', status: 'inProgress', receiverThreadIds: ['th_park_child'] } } });
@@ -219,8 +295,14 @@ rl.on('line', async (line) => {
219
295
  return;
220
296
  }
221
297
  // #401: agentMessage completion without deltas (same item envelope as baseline).
298
+ if (turnText.includes('mock:turn-messages:')) {
299
+ const { turnMessages } = await import('./mock-turn-messages.mjs');
300
+ for (const text of turnMessages(turnText)) emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: `item_multi_${++echoSerial}`, text } } });
301
+ emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
302
+ return;
303
+ }
222
304
  if (turnText.includes('mock:ask-snapshot')) {
223
- emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'ask-snapshot', text: 'Using the CEZ:ASK structured question format instead:\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}' } } });
305
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'ask-snapshot', text: turnText.includes('mock:ask-snapshot-bad') ? 'CEZ:ASK {not valid json' : 'Using the CEZ:ASK structured question format instead:\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}' } } });
224
306
  emit({ method: 'turn/completed', params: { threadId: 'th_mock_1', turn: { id: 'turn_mock_1', status: 'completed' } } });
225
307
  return;
226
308
  }
@@ -293,6 +375,26 @@ rl.on('line', async (line) => {
293
375
  } });
294
376
  return;
295
377
  }
378
+ if (turnText.includes('mock:hold-done')) {
379
+ await waitForMockRelease(400);
380
+ const full = 'parity hold-done: content after the pause\n\nCEZ:DONE';
381
+ emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_hd1', text: '' } } });
382
+ emit({ method: 'item/agentMessage/delta', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', itemId: 'item_hd1', delta: full } });
383
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_hd1', text: full } } });
384
+ emit({ method: 'thread/tokenUsage/updated', params: { threadId: 'th_mock_1', tokenUsage: { total: { totalTokens: 30, inputTokens: 20, outputTokens: 10 }, last: { totalTokens: 30, inputTokens: 20, outputTokens: 10 } } } });
385
+ emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
386
+ return;
387
+ }
388
+ if (turnText.includes('mock:hold-ask')) {
389
+ await waitForMockRelease(400);
390
+ const ask = 'Pick one.\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}';
391
+ emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_ha1', text: '' } } });
392
+ emit({ method: 'item/agentMessage/delta', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', itemId: 'item_ha1', delta: ask } });
393
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_ha1', text: ask } } });
394
+ emit({ method: 'thread/tokenUsage/updated', params: { threadId: 'th_mock_1', tokenUsage: { total: { totalTokens: 30, inputTokens: 20, outputTokens: 10 }, last: { totalTokens: 30, inputTokens: 20, outputTokens: 10 } } } });
395
+ emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
396
+ return;
397
+ }
296
398
  if (turnText.includes('mock:done')) {
297
399
  // A turn that DECLARES the task complete, so the run reaches cezar's
298
400
  // review gate instead of parking for the user. A markerless turn-end
@@ -315,6 +417,16 @@ rl.on('line', async (line) => {
315
417
  emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
316
418
  return;
317
419
  }
420
+ if (turnText.includes('mock:hold-gated')) {
421
+ await waitForMockRelease(250);
422
+ const gated = 'parity hold-gated: content after the pause';
423
+ emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_hg1', text: '' } } });
424
+ emit({ method: 'item/agentMessage/delta', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', itemId: 'item_hg1', delta: gated } });
425
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_hg1', text: gated } } });
426
+ emit({ method: 'thread/tokenUsage/updated', params: { threadId: 'th_mock_1', tokenUsage: { total: { totalTokens: 30, inputTokens: 20, outputTokens: 10 }, last: { totalTokens: 30, inputTokens: 20, outputTokens: 10 } } } });
427
+ emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
428
+ return;
429
+ }
318
430
  if (turnText.includes('mock:hold')) {
319
431
  // The `turn/start` response and `turn/started` above are the ack. Holding
320
432
  // the content AND `turn/completed` behind it is what makes harness parity
@@ -413,6 +525,9 @@ rl.on('line', async (line) => {
413
525
  emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'commandExecution', id: 'item_c1', command: ['bash', '-lc', 'git status --short'], cwd: '/repo', status: 'inProgress' } } });
414
526
  emit({ method: 'item/commandExecution/outputDelta', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', itemId: 'item_c1', delta: ' M src/example.ts\n' } });
415
527
  emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'commandExecution', id: 'item_c1', command: ['bash', '-lc', 'git status --short'], cwd: '/repo', status: 'completed', exitCode: 0 } } });
528
+ emit({ method: 'item/started', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_m2', text: '' } } });
529
+ emit({ method: 'item/agentMessage/delta', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', itemId: 'item_m2', delta: 'Done with the first pass.' } });
530
+ emit({ method: 'item/completed', params: { threadId: 'th_mock_1', turnId: 'turn_mock_1', item: { type: 'agentMessage', id: 'item_m2', text: 'Done with the first pass.' } } });
416
531
  emit({ method: 'thread/tokenUsage/updated', params: { threadId: 'th_mock_1', tokenUsage: { total: { totalTokens: 1500, inputTokens: 1200, outputTokens: 300 }, last: { totalTokens: 1500, inputTokens: 1200, outputTokens: 300 } } } });
417
532
  emit({ method: 'turn/completed', params: { turn: { id: 'turn_mock_1', status: 'completed' } } });
418
533
  }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ // #486 review: buffers each `turn/steer` RPC response and writes it in the SAME
3
+ // stdout write as the following `turn/completed` (or `turn/failed`) notification,
4
+ // preserving order. Reproduces Codex humanPromptsPending lagging a coalesced chunk.
5
+ import { spawn } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, join } from 'node:path';
8
+
9
+ const child = spawn(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), 'mock-codex-app-server.mjs'), ...process.argv.slice(2)], {
10
+ stdio: ['pipe', 'pipe', 'inherit'],
11
+ env: process.env,
12
+ });
13
+ const steerIds = new Set();
14
+ let stdinBuf = '';
15
+ process.stdin.setEncoding('utf8');
16
+ process.stdin.on('data', chunk => {
17
+ child.stdin.write(chunk);
18
+ stdinBuf += chunk;
19
+ let idx;
20
+ while ((idx = stdinBuf.indexOf('\n')) >= 0) {
21
+ const line = stdinBuf.slice(0, idx);
22
+ stdinBuf = stdinBuf.slice(idx + 1);
23
+ try {
24
+ const msg = JSON.parse(line);
25
+ if (msg.method === 'turn/steer' && typeof msg.id === 'number') steerIds.add(msg.id);
26
+ } catch { /* ignore a partial or non-JSON line */ }
27
+ }
28
+ });
29
+ process.stdin.on('end', () => child.stdin.end());
30
+
31
+ let outBuf = '';
32
+ const held = [];
33
+ child.stdout.setEncoding('utf8');
34
+ child.stdout.on('data', chunk => {
35
+ outBuf += chunk;
36
+ let idx;
37
+ while ((idx = outBuf.indexOf('\n')) >= 0) {
38
+ const line = outBuf.slice(0, idx + 1);
39
+ outBuf = outBuf.slice(idx + 1);
40
+ let msg;
41
+ try { msg = JSON.parse(line); } catch { process.stdout.write(line); continue; }
42
+ if (typeof msg.id === 'number' && steerIds.has(msg.id) && (msg.result !== undefined || msg.error !== undefined)) {
43
+ steerIds.delete(msg.id);
44
+ held.push(line);
45
+ continue;
46
+ }
47
+ if ((msg.method === 'turn/completed' || msg.method === 'turn/failed') && held.length) {
48
+ process.stdout.write(held.splice(0).join('') + line);
49
+ continue;
50
+ }
51
+ process.stdout.write(line);
52
+ }
53
+ });
54
+ child.on('exit', (code, signal) => {
55
+ if (outBuf) process.stdout.write(outBuf);
56
+ if (held.length) process.stdout.write(held.join(''));
57
+ if (signal) process.kill(process.pid, signal);
58
+ else process.exit(code ?? 0);
59
+ });