@wjarka/cezarion 0.16.0 → 0.17.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 (907) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +594 -207
  3. package/dist/agent-config/account-identity.d.ts +3 -19
  4. package/dist/agent-config/account-identity.js +8 -5
  5. package/dist/agent-config/account-identity.js.map +1 -1
  6. package/dist/agent-config/catalog.d.ts +3 -14
  7. package/dist/agent-config/catalog.js.map +1 -1
  8. package/dist/agent-config/files.d.ts +2 -6
  9. package/dist/agent-config/files.js +2 -6
  10. package/dist/agent-config/files.js.map +1 -1
  11. package/dist/agent-config/model-settings/types.d.ts +1 -1
  12. package/dist/agent-config/seed.d.ts +11 -3
  13. package/dist/agent-config/seed.js +52 -48
  14. package/dist/agent-config/seed.js.map +1 -1
  15. package/dist/agent-config/service.js +3 -2
  16. package/dist/agent-config/service.js.map +1 -1
  17. package/dist/agent-config/validate.d.ts +1 -4
  18. package/dist/agent-config/validate.js.map +1 -1
  19. package/dist/application-update/helper.d.ts +2 -2
  20. package/dist/application-update/helper.js +7 -12
  21. package/dist/application-update/helper.js.map +1 -1
  22. package/dist/application-update/launcher.d.ts +4 -0
  23. package/dist/application-update/launcher.js +18 -12
  24. package/dist/application-update/launcher.js.map +1 -1
  25. package/dist/application-update/service.d.ts +3 -3
  26. package/dist/application-update/service.js +1 -1
  27. package/dist/application-update/service.js.map +1 -1
  28. package/dist/archive-on-merge-setting.d.ts +15 -0
  29. package/dist/archive-on-merge-setting.js +14 -0
  30. package/dist/archive-on-merge-setting.js.map +1 -0
  31. package/dist/artifacts/cli.d.ts +8 -0
  32. package/dist/artifacts/cli.js +21 -2
  33. package/dist/artifacts/cli.js.map +1 -1
  34. package/dist/artifacts/resolve.js +2 -1
  35. package/dist/artifacts/resolve.js.map +1 -1
  36. package/dist/artifacts/store.js.map +1 -1
  37. package/dist/automations/automation-cli.d.ts +59 -0
  38. package/dist/automations/automation-cli.js +509 -0
  39. package/dist/automations/automation-cli.js.map +1 -0
  40. package/dist/automations/builtin-skill.d.ts +7 -0
  41. package/dist/automations/builtin-skill.js +14 -0
  42. package/dist/automations/builtin-skill.js.map +1 -0
  43. package/dist/automations/github-baseline.d.ts +3 -0
  44. package/dist/automations/github-baseline.js +17 -0
  45. package/dist/automations/github-baseline.js.map +1 -0
  46. package/dist/automations/github-poller.d.ts +23 -4
  47. package/dist/automations/github-poller.js +157 -88
  48. package/dist/automations/github-poller.js.map +1 -1
  49. package/dist/automations/prompts.d.ts +12 -0
  50. package/dist/automations/prompts.js +160 -0
  51. package/dist/automations/prompts.js.map +1 -0
  52. package/dist/automations/schedule-runner.d.ts +43 -24
  53. package/dist/automations/schedule-runner.js +207 -20
  54. package/dist/automations/schedule-runner.js.map +1 -1
  55. package/dist/automations/scheduler.d.ts +10 -2
  56. package/dist/automations/scheduler.js +200 -54
  57. package/dist/automations/scheduler.js.map +1 -1
  58. package/dist/automations/service.d.ts +777 -0
  59. package/dist/automations/service.js +444 -0
  60. package/dist/automations/service.js.map +1 -0
  61. package/dist/automations/store.d.ts +17 -7
  62. package/dist/automations/store.js +88 -17
  63. package/dist/automations/store.js.map +1 -1
  64. package/dist/automations/task-template.d.ts +7 -2
  65. package/dist/automations/task-template.js +93 -44
  66. package/dist/automations/task-template.js.map +1 -1
  67. package/dist/automations/tool-adapter.d.ts +6 -0
  68. package/dist/automations/tool-adapter.js +108 -0
  69. package/dist/automations/tool-adapter.js.map +1 -0
  70. package/dist/automations/types.d.ts +1 -405
  71. package/dist/automations/types.js +1 -218
  72. package/dist/automations/types.js.map +1 -1
  73. package/dist/autosave-cleanup.d.ts +26 -0
  74. package/dist/autosave-cleanup.js +87 -0
  75. package/dist/autosave-cleanup.js.map +1 -0
  76. package/dist/autosave-git.d.ts +3 -0
  77. package/dist/autosave-git.js +67 -35
  78. package/dist/autosave-git.js.map +1 -1
  79. package/dist/autosave-group.d.ts +8 -0
  80. package/dist/autosave-group.js +61 -0
  81. package/dist/autosave-group.js.map +1 -0
  82. package/dist/autosave-holders.d.ts +31 -4
  83. package/dist/autosave-holders.js +92 -7
  84. package/dist/autosave-holders.js.map +1 -1
  85. package/dist/autosave-windows.d.ts +14 -0
  86. package/dist/autosave-windows.js +36 -2
  87. package/dist/autosave-windows.js.map +1 -1
  88. package/dist/ci-wait/client.d.ts +20 -3
  89. package/dist/ci-wait/client.js +40 -44
  90. package/dist/ci-wait/client.js.map +1 -1
  91. package/dist/ci-wait/controller.d.ts +622 -36
  92. package/dist/ci-wait/controller.js +152 -20
  93. package/dist/ci-wait/controller.js.map +1 -1
  94. package/dist/ci-wait/errors.d.ts +19 -0
  95. package/dist/ci-wait/errors.js +180 -2
  96. package/dist/ci-wait/errors.js.map +1 -1
  97. package/dist/ci-wait/github.d.ts +25 -1
  98. package/dist/ci-wait/github.js +76 -9
  99. package/dist/ci-wait/github.js.map +1 -1
  100. package/dist/ci-wait/mcp.d.ts +0 -1
  101. package/dist/ci-wait/mcp.js +0 -2
  102. package/dist/ci-wait/mcp.js.map +1 -1
  103. package/dist/ci-wait/supervisor.d.ts +7 -0
  104. package/dist/ci-wait/supervisor.js +113 -17
  105. package/dist/ci-wait/supervisor.js.map +1 -1
  106. package/dist/ci-wait/tools.d.ts +536 -16
  107. package/dist/ci-wait/tools.js +165 -20
  108. package/dist/ci-wait/tools.js.map +1 -1
  109. package/dist/config.d.ts +3 -17
  110. package/dist/config.js +4 -60
  111. package/dist/config.js.map +1 -1
  112. package/dist/contract/active-provider.d.ts +12 -0
  113. package/dist/contract/agent-profiles.d.ts +2 -10
  114. package/dist/contract/ask-schema.d.ts +22 -4
  115. package/dist/contract/ask.d.ts +6 -4
  116. package/dist/contract/attachments.d.ts +35 -0
  117. package/dist/contract/attention.d.ts +46 -37
  118. package/dist/contract/automation-requests.d.ts +308 -0
  119. package/dist/contract/automation-schedule.d.ts +40 -11
  120. package/dist/contract/automation-storage.d.ts +512 -0
  121. package/dist/contract/automations.d.ts +421 -40
  122. package/dist/contract/ci-wait.d.ts +41 -4
  123. package/dist/contract/config-requests.d.ts +36 -0
  124. package/dist/contract/conversations.d.ts +24 -10
  125. package/dist/contract/defined.d.ts +8 -0
  126. package/dist/contract/delegation.d.ts +312 -8
  127. package/dist/contract/events.d.ts +24 -3
  128. package/dist/contract/github-requests.d.ts +26 -0
  129. package/dist/contract/github.d.ts +60 -27
  130. package/dist/contract/handoff.d.ts +43 -0
  131. package/dist/contract/health.d.ts +2 -2
  132. package/dist/contract/image-path.d.ts +6 -0
  133. package/dist/contract/index.d.ts +31 -0
  134. package/dist/contract/index.js +3756 -1525
  135. package/dist/contract/live-coordination.d.ts +158 -0
  136. package/dist/contract/live-limits.d.ts +8 -0
  137. package/dist/contract/live.d.ts +123 -0
  138. package/dist/contract/pending-ask.d.ts +58 -0
  139. package/dist/contract/permissions.d.ts +54 -0
  140. package/dist/contract/preview.d.ts +33 -0
  141. package/dist/contract/project-limits.d.ts +33 -0
  142. package/dist/contract/projects.d.ts +486 -20
  143. package/dist/contract/provider-requests.d.ts +77 -0
  144. package/dist/contract/query-requests.d.ts +52 -0
  145. package/dist/contract/repo-requests.d.ts +12 -0
  146. package/dist/contract/run-labels.d.ts +8 -0
  147. package/dist/contract/run-limits.d.ts +8 -0
  148. package/dist/contract/run-predicates.d.ts +32 -0
  149. package/dist/contract/run-requests.d.ts +33 -0
  150. package/dist/contract/run-search.d.ts +193 -0
  151. package/dist/contract/runs.d.ts +2232 -1894
  152. package/dist/contract/scheduled-tasks.d.ts +187 -0
  153. package/dist/contract/skills.d.ts +22 -772
  154. package/dist/contract/team-skill-ref.d.ts +10 -0
  155. package/dist/{core → contract}/tool-display.d.ts +4 -10
  156. package/dist/contract/ui-state-requests.d.ts +60 -0
  157. package/dist/contract/version.d.ts +3 -0
  158. package/dist/contract/voice.d.ts +213 -0
  159. package/dist/contract/waits.d.ts +1204 -0
  160. package/dist/contract/workflow-file.d.ts +37 -0
  161. package/dist/contract/workflow-requests.d.ts +7 -0
  162. package/dist/contract/workflows.d.ts +1 -2
  163. package/dist/contract/workspace.d.ts +34 -82
  164. package/dist/contract/zoned-time.d.ts +2 -11
  165. package/dist/core/agent-env.d.ts +4 -16
  166. package/dist/core/agent-env.js +14 -16
  167. package/dist/core/agent-env.js.map +1 -1
  168. package/dist/core/agent-profiles.d.ts +28 -40
  169. package/dist/core/agent-profiles.js +56 -51
  170. package/dist/core/agent-profiles.js.map +1 -1
  171. package/dist/core/agent-runner.d.ts +88 -44
  172. package/dist/core/agent-runner.js +17 -18
  173. package/dist/core/agent-runner.js.map +1 -1
  174. package/dist/core/ask.d.ts +3 -0
  175. package/dist/core/ask.js +12 -0
  176. package/dist/core/ask.js.map +1 -1
  177. package/dist/core/backend-detect.js +16 -2
  178. package/dist/core/backend-detect.js.map +1 -1
  179. package/dist/core/claude-cli-runner.d.ts +23 -4
  180. package/dist/core/claude-cli-runner.js +280 -76
  181. package/dist/core/claude-cli-runner.js.map +1 -1
  182. package/dist/core/claude-model-catalog.d.ts +2 -10
  183. package/dist/core/claude-model-catalog.js +2 -10
  184. package/dist/core/claude-model-catalog.js.map +1 -1
  185. package/dist/core/claude-ui-mapper.d.ts +2 -2
  186. package/dist/core/claude-ui-mapper.js +94 -2
  187. package/dist/core/claude-ui-mapper.js.map +1 -1
  188. package/dist/core/codex-app-server-runner.d.ts +3 -4
  189. package/dist/core/codex-app-server-runner.js +146 -51
  190. package/dist/core/codex-app-server-runner.js.map +1 -1
  191. package/dist/core/codex-app-server-transport.d.ts +4 -3
  192. package/dist/core/codex-app-server-transport.js +11 -7
  193. package/dist/core/codex-app-server-transport.js.map +1 -1
  194. package/dist/core/codex-approvals.d.ts +12 -0
  195. package/dist/core/codex-approvals.js +161 -0
  196. package/dist/core/codex-approvals.js.map +1 -0
  197. package/dist/core/codex-permissions.d.ts +1 -1
  198. package/dist/core/codex-ui-mapper.js +45 -1
  199. package/dist/core/codex-ui-mapper.js.map +1 -1
  200. package/dist/core/cursor-acp-runner.d.ts +1 -1
  201. package/dist/core/cursor-acp-runner.js +39 -15
  202. package/dist/core/cursor-acp-runner.js.map +1 -1
  203. package/dist/core/cursor-cli-flags.d.ts +19 -0
  204. package/dist/core/cursor-cli-flags.js +47 -0
  205. package/dist/core/cursor-cli-flags.js.map +1 -0
  206. package/dist/core/cursor-model-catalog.d.ts +22 -0
  207. package/dist/core/cursor-model-catalog.js +53 -24
  208. package/dist/core/cursor-model-catalog.js.map +1 -1
  209. package/dist/core/cursor-print-capabilities.d.ts +16 -0
  210. package/dist/core/cursor-print-capabilities.js +141 -0
  211. package/dist/core/cursor-print-capabilities.js.map +1 -0
  212. package/dist/core/cursor-print-process.d.ts +31 -0
  213. package/dist/core/cursor-print-process.js +117 -0
  214. package/dist/core/cursor-print-process.js.map +1 -0
  215. package/dist/core/cursor-print-runner.d.ts +31 -0
  216. package/dist/core/cursor-print-runner.js +555 -0
  217. package/dist/core/cursor-print-runner.js.map +1 -0
  218. package/dist/core/cursor-print-tool-catalog.d.ts +10 -0
  219. package/dist/core/cursor-print-tool-catalog.js +91 -0
  220. package/dist/core/cursor-print-tool-catalog.js.map +1 -0
  221. package/dist/core/cursor-print-ui-mapper.d.ts +47 -0
  222. package/dist/core/cursor-print-ui-mapper.js +553 -0
  223. package/dist/core/cursor-print-ui-mapper.js.map +1 -0
  224. package/dist/core/cursor-runner.d.ts +24 -0
  225. package/dist/core/cursor-runner.js +149 -0
  226. package/dist/core/cursor-runner.js.map +1 -0
  227. package/dist/core/cursor-ui-mapper.js +5 -2
  228. package/dist/core/cursor-ui-mapper.js.map +1 -1
  229. package/dist/core/defined.d.ts +1 -0
  230. package/dist/core/defined.js +2 -0
  231. package/dist/core/defined.js.map +1 -0
  232. package/dist/core/hook-items.d.ts +14 -0
  233. package/dist/core/hook-items.js +13 -0
  234. package/dist/core/hook-items.js.map +1 -0
  235. package/dist/core/host-model-catalog.d.ts +2 -9
  236. package/dist/core/host-model-catalog.js +2 -9
  237. package/dist/core/host-model-catalog.js.map +1 -1
  238. package/dist/core/managed-session.js +15 -3
  239. package/dist/core/managed-session.js.map +1 -1
  240. package/dist/core/model-identity.d.ts +4 -21
  241. package/dist/core/model-identity.js.map +1 -1
  242. package/dist/core/model-presets.d.ts +2 -13
  243. package/dist/core/model-presets.js +2 -13
  244. package/dist/core/model-presets.js.map +1 -1
  245. package/dist/core/omp-model-catalog.js.map +1 -1
  246. package/dist/core/omp-runner.d.ts +6 -7
  247. package/dist/core/omp-runner.js +92 -33
  248. package/dist/core/omp-runner.js.map +1 -1
  249. package/dist/core/omp-ui-mapper.js +1 -1
  250. package/dist/core/omp-ui-mapper.js.map +1 -1
  251. package/dist/core/opencode-model-catalog.js.map +1 -1
  252. package/dist/core/opencode-permissions.d.ts +52 -0
  253. package/dist/core/opencode-permissions.js +101 -0
  254. package/dist/core/opencode-permissions.js.map +1 -0
  255. package/dist/core/opencode-server-runner.d.ts +10 -9
  256. package/dist/core/opencode-server-runner.js +286 -28
  257. package/dist/core/opencode-server-runner.js.map +1 -1
  258. package/dist/core/opencode-session-error.d.ts +15 -7
  259. package/dist/core/opencode-session-error.js +25 -9
  260. package/dist/core/opencode-session-error.js.map +1 -1
  261. package/dist/core/opencode-ui-mapper.js +2 -2
  262. package/dist/core/opencode-ui-mapper.js.map +1 -1
  263. package/dist/core/pi-model-catalog.js.map +1 -1
  264. package/dist/core/pi-runner.d.ts +12 -4
  265. package/dist/core/pi-runner.js +171 -63
  266. package/dist/core/pi-runner.js.map +1 -1
  267. package/dist/core/pi-ui-mapper.js +1 -1
  268. package/dist/core/pi-ui-mapper.js.map +1 -1
  269. package/dist/core/process-usage.d.ts +5 -0
  270. package/dist/core/process-usage.js +24 -4
  271. package/dist/core/process-usage.js.map +1 -1
  272. package/dist/core/provider-auth.d.ts +16 -107
  273. package/dist/core/provider-auth.js +16 -132
  274. package/dist/core/provider-auth.js.map +1 -1
  275. package/dist/core/runner-factory.d.ts +4 -1
  276. package/dist/core/runner-factory.js +3 -3
  277. package/dist/core/runner-factory.js.map +1 -1
  278. package/dist/core/runner-runtime.d.ts +22 -0
  279. package/dist/core/runner-runtime.js +66 -5
  280. package/dist/core/runner-runtime.js.map +1 -1
  281. package/dist/core/runner-tool-grants.d.ts +21 -0
  282. package/dist/core/runner-tool-grants.js +64 -0
  283. package/dist/core/runner-tool-grants.js.map +1 -0
  284. package/dist/core/secret-redaction.d.ts +3 -20
  285. package/dist/core/secret-redaction.js +3 -20
  286. package/dist/core/secret-redaction.js.map +1 -1
  287. package/dist/core/session-process.d.ts +22 -2
  288. package/dist/core/session-process.js +170 -12
  289. package/dist/core/session-process.js.map +1 -1
  290. package/dist/core/shell-env.d.ts +2 -15
  291. package/dist/core/shell-env.js +2 -15
  292. package/dist/core/shell-env.js.map +1 -1
  293. package/dist/core/ui-events.d.ts +60 -47
  294. package/dist/core/ui-events.js +0 -23
  295. package/dist/core/ui-events.js.map +1 -1
  296. package/dist/core/usage-limit.js.map +1 -1
  297. package/dist/data-gitignore.js +3 -1
  298. package/dist/data-gitignore.js.map +1 -1
  299. package/dist/delegation/capacity.d.ts +4 -8
  300. package/dist/delegation/capacity.js +4 -8
  301. package/dist/delegation/capacity.js.map +1 -1
  302. package/dist/delegation/cli.js +123 -21
  303. package/dist/delegation/cli.js.map +1 -1
  304. package/dist/delegation/context.d.ts +19 -3
  305. package/dist/delegation/context.js +217 -22
  306. package/dist/delegation/context.js.map +1 -1
  307. package/dist/delegation/conversations.d.ts +4 -3
  308. package/dist/delegation/conversations.js +7 -5
  309. package/dist/delegation/conversations.js.map +1 -1
  310. package/dist/delegation/destroy-observation.d.ts +1 -1
  311. package/dist/delegation/destroy-observation.js.map +1 -1
  312. package/dist/delegation/effort.js.map +1 -1
  313. package/dist/delegation/execution-identity.d.ts +1 -1
  314. package/dist/delegation/execution-identity.js +41 -7
  315. package/dist/delegation/execution-identity.js.map +1 -1
  316. package/dist/delegation/idle-test-app.d.ts +4 -0
  317. package/dist/delegation/idle-test-app.js +174 -0
  318. package/dist/delegation/idle-test-app.js.map +1 -0
  319. package/dist/delegation/input.js.map +1 -1
  320. package/dist/delegation/park-notice.d.ts +42 -0
  321. package/dist/delegation/park-notice.js +71 -0
  322. package/dist/delegation/park-notice.js.map +1 -0
  323. package/dist/delegation/policy.d.ts +2 -0
  324. package/dist/delegation/policy.js +9 -1
  325. package/dist/delegation/policy.js.map +1 -1
  326. package/dist/delegation/process-liveness.d.ts +10 -15
  327. package/dist/delegation/process-liveness.js +11 -35
  328. package/dist/delegation/process-liveness.js.map +1 -1
  329. package/dist/delegation/provision.js +21 -19
  330. package/dist/delegation/provision.js.map +1 -1
  331. package/dist/delegation/questions.d.ts +3 -1
  332. package/dist/delegation/questions.js +10 -5
  333. package/dist/delegation/questions.js.map +1 -1
  334. package/dist/delegation/readiness.d.ts +2 -2
  335. package/dist/delegation/results.d.ts +4 -1
  336. package/dist/delegation/results.js +49 -18
  337. package/dist/delegation/results.js.map +1 -1
  338. package/dist/delegation/retry-backoff.d.ts +8 -0
  339. package/dist/delegation/retry-backoff.js +8 -0
  340. package/dist/delegation/retry-backoff.js.map +1 -1
  341. package/dist/delegation/routes.d.ts +114 -30
  342. package/dist/delegation/routes.js +4 -1
  343. package/dist/delegation/routes.js.map +1 -1
  344. package/dist/delegation/scratch-cleanup.d.ts +4 -5
  345. package/dist/delegation/scratch-cleanup.js +10 -8
  346. package/dist/delegation/scratch-cleanup.js.map +1 -1
  347. package/dist/delegation/service.d.ts +12 -4
  348. package/dist/delegation/service.js +76 -28
  349. package/dist/delegation/service.js.map +1 -1
  350. package/dist/delegation/transport.d.ts +154 -70
  351. package/dist/delegation/wait.d.ts +11 -0
  352. package/dist/delegation/wait.js +24 -0
  353. package/dist/delegation/wait.js.map +1 -1
  354. package/dist/delegation/windows-test-app.d.ts +5 -0
  355. package/dist/delegation/windows-test-app.js +63 -0
  356. package/dist/delegation/windows-test-app.js.map +1 -0
  357. package/dist/delegation/windows-workspace-identity.d.ts +3 -0
  358. package/dist/delegation/windows-workspace-identity.js +22 -0
  359. package/dist/delegation/windows-workspace-identity.js.map +1 -0
  360. package/dist/delegation/workspace.d.ts +5 -10
  361. package/dist/delegation/workspace.js +136 -75
  362. package/dist/delegation/workspace.js.map +1 -1
  363. package/dist/detached-process.d.ts +103 -0
  364. package/dist/detached-process.js +301 -0
  365. package/dist/detached-process.js.map +1 -0
  366. package/dist/discovery/catalog.d.ts +2 -1
  367. package/dist/discovery/catalog.js.map +1 -1
  368. package/dist/discovery/cli.js +3 -1
  369. package/dist/discovery/cli.js.map +1 -1
  370. package/dist/git-diff-base.d.ts +6 -61
  371. package/dist/git-diff-base.js +6 -70
  372. package/dist/git-diff-base.js.map +1 -1
  373. package/dist/git-index-copy.d.ts +4 -0
  374. package/dist/git-index-copy.js +10 -0
  375. package/dist/git-index-copy.js.map +1 -0
  376. package/dist/git-worktree-lock.d.ts +2 -4
  377. package/dist/git-worktree-lock.js +2 -4
  378. package/dist/git-worktree-lock.js.map +1 -1
  379. package/dist/git-worktree-release.d.ts +2 -2
  380. package/dist/git-worktree-release.js.map +1 -1
  381. package/dist/git-worktree.d.ts +47 -76
  382. package/dist/git-worktree.js +240 -94
  383. package/dist/git-worktree.js.map +1 -1
  384. package/dist/handoff.d.ts +51 -41
  385. package/dist/handoff.js +130 -78
  386. package/dist/handoff.js.map +1 -1
  387. package/dist/index.js +60 -39
  388. package/dist/index.js.map +1 -1
  389. package/dist/merge-observations.d.ts +13 -0
  390. package/dist/merge-observations.js +55 -0
  391. package/dist/merge-observations.js.map +1 -0
  392. package/dist/open-url.js +4 -14
  393. package/dist/open-url.js.map +1 -1
  394. package/dist/paths.d.ts +10 -13
  395. package/dist/paths.js +36 -17
  396. package/dist/paths.js.map +1 -1
  397. package/dist/planner.d.ts +2 -1
  398. package/dist/planner.js +9 -6
  399. package/dist/planner.js.map +1 -1
  400. package/dist/preview/agent-address.d.ts +34 -0
  401. package/dist/preview/agent-address.js +51 -0
  402. package/dist/preview/agent-address.js.map +1 -0
  403. package/dist/preview/agent-marker.d.ts +17 -0
  404. package/dist/preview/agent-marker.js +71 -0
  405. package/dist/preview/agent-marker.js.map +1 -0
  406. package/dist/preview/cdp-proxy.d.ts +54 -0
  407. package/dist/preview/cdp-proxy.js +151 -0
  408. package/dist/preview/cdp-proxy.js.map +1 -0
  409. package/dist/preview/cdp.d.ts +39 -3
  410. package/dist/preview/cdp.js +155 -57
  411. package/dist/preview/cdp.js.map +1 -1
  412. package/dist/preview/chromium.d.ts +18 -6
  413. package/dist/preview/chromium.js +112 -25
  414. package/dist/preview/chromium.js.map +1 -1
  415. package/dist/preview/dev-server.d.ts +1 -1
  416. package/dist/preview/dev-server.js +20 -44
  417. package/dist/preview/dev-server.js.map +1 -1
  418. package/dist/preview/host.d.ts +70 -3
  419. package/dist/preview/host.js +314 -28
  420. package/dist/preview/host.js.map +1 -1
  421. package/dist/preview/registration.d.ts +10 -5
  422. package/dist/preview/registration.js +2 -0
  423. package/dist/preview/registration.js.map +1 -1
  424. package/dist/preview/session.d.ts +2 -0
  425. package/dist/preview/session.js +10 -0
  426. package/dist/preview/session.js.map +1 -1
  427. package/dist/process-wait/exit-file.d.ts +29 -0
  428. package/dist/process-wait/exit-file.js +67 -0
  429. package/dist/process-wait/exit-file.js.map +1 -0
  430. package/dist/process-wait/supervisor.d.ts +30 -0
  431. package/dist/process-wait/supervisor.js +71 -0
  432. package/dist/process-wait/supervisor.js.map +1 -0
  433. package/dist/release/snapshot.d.ts +6 -6
  434. package/dist/runs/agent-tmpdir.d.ts +51 -36
  435. package/dist/runs/agent-tmpdir.js +389 -132
  436. package/dist/runs/agent-tmpdir.js.map +1 -1
  437. package/dist/runs/auto-name.d.ts +6 -6
  438. package/dist/runs/auto-name.js +1 -1
  439. package/dist/runs/auto-name.js.map +1 -1
  440. package/dist/runs/client-request.d.ts +3 -3
  441. package/dist/runs/client-request.js +3 -3
  442. package/dist/runs/client-request.js.map +1 -1
  443. package/dist/runs/delegation-state.d.ts +11 -2
  444. package/dist/runs/delegation-state.js.map +1 -1
  445. package/dist/runs/event-history-agent-context.d.ts +14 -0
  446. package/dist/runs/event-history-agent-context.js +105 -0
  447. package/dist/runs/event-history-agent-context.js.map +1 -0
  448. package/dist/runs/event-history-context.d.ts +15 -0
  449. package/dist/runs/event-history-context.js +109 -0
  450. package/dist/runs/event-history-context.js.map +1 -0
  451. package/dist/runs/event-history.d.ts +4 -1
  452. package/dist/runs/event-history.js +29 -130
  453. package/dist/runs/event-history.js.map +1 -1
  454. package/dist/runs/history-stream.d.ts +2 -0
  455. package/dist/runs/history-stream.js +62 -0
  456. package/dist/runs/history-stream.js.map +1 -0
  457. package/dist/runs/legacy-index.d.ts +6 -19
  458. package/dist/runs/legacy-index.js +6 -19
  459. package/dist/runs/legacy-index.js.map +1 -1
  460. package/dist/runs/live-item-snapshots.d.ts +13 -0
  461. package/dist/runs/live-item-snapshots.js +79 -0
  462. package/dist/runs/live-item-snapshots.js.map +1 -0
  463. package/dist/runs/loaded-run-healing.d.ts +3 -0
  464. package/dist/runs/loaded-run-healing.js +17 -0
  465. package/dist/runs/loaded-run-healing.js.map +1 -0
  466. package/dist/runs/outcome-commit.d.ts +98 -0
  467. package/dist/runs/outcome-commit.js +132 -0
  468. package/dist/runs/outcome-commit.js.map +1 -0
  469. package/dist/runs/pull-requests.d.ts +14 -0
  470. package/dist/runs/pull-requests.js +96 -0
  471. package/dist/runs/pull-requests.js.map +1 -0
  472. package/dist/runs/raw-record.d.ts +3 -15
  473. package/dist/runs/raw-record.js +3 -15
  474. package/dist/runs/raw-record.js.map +1 -1
  475. package/dist/runs/retention.d.ts +2 -2
  476. package/dist/runs/retention.js +7 -0
  477. package/dist/runs/retention.js.map +1 -1
  478. package/dist/runs/review-gate.d.ts +1 -1
  479. package/dist/runs/review-gate.js.map +1 -1
  480. package/dist/runs/run-claims.d.ts +2 -9
  481. package/dist/runs/run-claims.js.map +1 -1
  482. package/dist/runs/run-database.d.ts +29 -34
  483. package/dist/runs/run-database.js +124 -46
  484. package/dist/runs/run-database.js.map +1 -1
  485. package/dist/runs/run-index.d.ts +6 -25
  486. package/dist/runs/run-index.js +25 -38
  487. package/dist/runs/run-index.js.map +1 -1
  488. package/dist/runs/run-row.d.ts +5 -34
  489. package/dist/runs/run-row.js +13 -38
  490. package/dist/runs/run-row.js.map +1 -1
  491. package/dist/runs/store.d.ts +1597 -744
  492. package/dist/runs/store.js +1231 -739
  493. package/dist/runs/store.js.map +1 -1
  494. package/dist/runs/task-markers.d.ts +1 -0
  495. package/dist/runs/task-markers.js +5 -1
  496. package/dist/runs/task-markers.js.map +1 -1
  497. package/dist/runs/task-refs.d.ts +3 -3
  498. package/dist/runs/transcript-facts-fold.d.ts +64 -0
  499. package/dist/runs/transcript-facts-fold.js +109 -0
  500. package/dist/runs/transcript-facts-fold.js.map +1 -0
  501. package/dist/runs/transcript-facts-load.d.ts +11 -0
  502. package/dist/runs/transcript-facts-load.js +66 -0
  503. package/dist/runs/transcript-facts-load.js.map +1 -0
  504. package/dist/runs/transcript-facts-queue.d.ts +30 -0
  505. package/dist/runs/transcript-facts-queue.js +227 -0
  506. package/dist/runs/transcript-facts-queue.js.map +1 -0
  507. package/dist/runs/transcript-facts-worker.d.ts +15 -0
  508. package/dist/runs/transcript-facts-worker.js +26 -0
  509. package/dist/runs/transcript-facts-worker.js.map +1 -0
  510. package/dist/runs/transcript-facts.d.ts +13 -51
  511. package/dist/runs/transcript-facts.js +86 -202
  512. package/dist/runs/transcript-facts.js.map +1 -1
  513. package/dist/runs/ui-event-sink.d.ts +5 -30
  514. package/dist/runs/ui-event-sink.js +8 -30
  515. package/dist/runs/ui-event-sink.js.map +1 -1
  516. package/dist/runs/webhook.d.ts +104 -38
  517. package/dist/runs/webhook.js +180 -77
  518. package/dist/runs/webhook.js.map +1 -1
  519. package/dist/server/app-type.d.ts +2 -17
  520. package/dist/server/archive-on-merge.d.ts +35 -0
  521. package/dist/server/archive-on-merge.js +202 -0
  522. package/dist/server/archive-on-merge.js.map +1 -0
  523. package/dist/server/capabilities.d.ts +3 -15
  524. package/dist/server/capabilities.js +6 -19
  525. package/dist/server/capabilities.js.map +1 -1
  526. package/dist/server/checkout-progress.d.ts +10 -0
  527. package/dist/server/checkout-progress.js +27 -0
  528. package/dist/server/checkout-progress.js.map +1 -0
  529. package/dist/server/checkout.d.ts +10 -19
  530. package/dist/server/checkout.js +15 -45
  531. package/dist/server/checkout.js.map +1 -1
  532. package/dist/server/cockpit-ownership.d.ts +2 -0
  533. package/dist/server/cockpit-ownership.js +9 -0
  534. package/dist/server/cockpit-ownership.js.map +1 -1
  535. package/dist/server/cold-repo-handles.d.ts +2 -7
  536. package/dist/server/cold-repo-handles.js +2 -7
  537. package/dist/server/cold-repo-handles.js.map +1 -1
  538. package/dist/server/completion-actions.d.ts +30 -0
  539. package/dist/server/completion-actions.js +204 -0
  540. package/dist/server/completion-actions.js.map +1 -0
  541. package/dist/server/create-project.d.ts +71 -0
  542. package/dist/server/create-project.js +448 -0
  543. package/dist/server/create-project.js.map +1 -0
  544. package/dist/server/forge/github-filters.d.ts +7 -1
  545. package/dist/server/forge/github-filters.js +66 -51
  546. package/dist/server/forge/github-filters.js.map +1 -1
  547. package/dist/server/forge/github-merge-requirements.d.ts +18 -0
  548. package/dist/server/forge/github-merge-requirements.js +137 -0
  549. package/dist/server/forge/github-merge-requirements.js.map +1 -0
  550. package/dist/server/forge/github.d.ts +98 -111
  551. package/dist/server/forge/github.js +625 -306
  552. package/dist/server/forge/github.js.map +1 -1
  553. package/dist/server/forge/types.d.ts +25 -22
  554. package/dist/server/fs-browse.d.ts +3 -28
  555. package/dist/server/fs-browse.js.map +1 -1
  556. package/dist/server/git-changes.d.ts +5 -15
  557. package/dist/server/git-changes.js +3 -50
  558. package/dist/server/git-changes.js.map +1 -1
  559. package/dist/server/git.d.ts +3 -1
  560. package/dist/server/git.js +12 -0
  561. package/dist/server/git.js.map +1 -1
  562. package/dist/server/github.d.ts +1 -1
  563. package/dist/server/github.js +1 -1
  564. package/dist/server/github.js.map +1 -1
  565. package/dist/server/launch-key.d.ts +2 -4
  566. package/dist/server/launch-key.js +2 -4
  567. package/dist/server/launch-key.js.map +1 -1
  568. package/dist/server/live-routes.d.ts +68 -0
  569. package/dist/server/live-routes.js +147 -0
  570. package/dist/server/live-routes.js.map +1 -0
  571. package/dist/server/open-in-app.d.ts +4 -13
  572. package/dist/server/open-in-app.js +5 -41
  573. package/dist/server/open-in-app.js.map +1 -1
  574. package/dist/server/open-in-targets.d.ts +42 -0
  575. package/dist/server/open-in-targets.js +110 -0
  576. package/dist/server/open-in-targets.js.map +1 -0
  577. package/dist/server/open-in-terminal.d.ts +3 -0
  578. package/dist/server/open-in-terminal.js +6 -26
  579. package/dist/server/open-in-terminal.js.map +1 -1
  580. package/dist/server/preview-cdp-socket.d.ts +21 -0
  581. package/dist/server/preview-cdp-socket.js +112 -0
  582. package/dist/server/preview-cdp-socket.js.map +1 -0
  583. package/dist/server/preview-socket.d.ts +11 -11
  584. package/dist/server/preview-socket.js +22 -22
  585. package/dist/server/preview-socket.js.map +1 -1
  586. package/dist/server/preview-sockets.d.ts +14 -0
  587. package/dist/server/preview-sockets.js +18 -0
  588. package/dist/server/preview-sockets.js.map +1 -0
  589. package/dist/server/project-context.d.ts +54 -18
  590. package/dist/server/project-context.js +178 -5
  591. package/dist/server/project-context.js.map +1 -1
  592. package/dist/server/provider-action-gate.d.ts +0 -2
  593. package/dist/server/provider-action-gate.js +0 -21
  594. package/dist/server/provider-action-gate.js.map +1 -1
  595. package/dist/server/provider-auth-runtime.d.ts +3 -18
  596. package/dist/server/provider-auth-runtime.js +3 -18
  597. package/dist/server/provider-auth-runtime.js.map +1 -1
  598. package/dist/server/repo-branches.d.ts +3 -9
  599. package/dist/server/repo-branches.js +6 -22
  600. package/dist/server/repo-branches.js.map +1 -1
  601. package/dist/server/run-event-feed.d.ts +20 -0
  602. package/dist/server/run-event-feed.js +190 -0
  603. package/dist/server/run-event-feed.js.map +1 -0
  604. package/dist/server/server-deps.d.ts +37 -0
  605. package/dist/server/server-deps.js +114 -0
  606. package/dist/server/server-deps.js.map +1 -0
  607. package/dist/server/server.d.ts +14082 -11998
  608. package/dist/server/server.js +785 -1647
  609. package/dist/server/server.js.map +1 -1
  610. package/dist/server/start-run-options.d.ts +82 -0
  611. package/dist/server/start-run-options.js +111 -0
  612. package/dist/server/start-run-options.js.map +1 -0
  613. package/dist/server/validators.d.ts +3 -29
  614. package/dist/server/validators.js +1 -2
  615. package/dist/server/validators.js.map +1 -1
  616. package/dist/server/ws.d.ts +3 -23
  617. package/dist/server/ws.js +3 -23
  618. package/dist/server/ws.js.map +1 -1
  619. package/dist/server-install/engine.d.ts +6 -9
  620. package/dist/server-install/engine.js +2 -9
  621. package/dist/server-install/engine.js.map +1 -1
  622. package/dist/server-install/platforms/ubuntu-vps.d.ts +1 -1
  623. package/dist/server-install/state.d.ts +2 -4
  624. package/dist/server-install/state.js +2 -4
  625. package/dist/server-install/state.js.map +1 -1
  626. package/dist/server-install/steps.d.ts +2 -5
  627. package/dist/server-install/steps.js +3 -6
  628. package/dist/server-install/steps.js.map +1 -1
  629. package/dist/server-install/types.d.ts +3 -6
  630. package/dist/server-install/types.js +2 -5
  631. package/dist/server-install/types.js.map +1 -1
  632. package/dist/server-install/ui.d.ts +2 -8
  633. package/dist/server-install/ui.js +3 -3
  634. package/dist/server-install/ui.js.map +1 -1
  635. package/dist/session-context.d.ts +64 -0
  636. package/dist/session-context.js +264 -0
  637. package/dist/session-context.js.map +1 -0
  638. package/dist/skills-provision.d.ts +62 -0
  639. package/dist/skills-provision.js +325 -0
  640. package/dist/skills-provision.js.map +1 -0
  641. package/dist/skills-remote.d.ts +29 -27
  642. package/dist/skills-remote.js +236 -100
  643. package/dist/skills-remote.js.map +1 -1
  644. package/dist/skills.d.ts +25 -36
  645. package/dist/skills.js +43 -54
  646. package/dist/skills.js.map +1 -1
  647. package/dist/subcommands.d.ts +11 -0
  648. package/dist/subcommands.js +24 -0
  649. package/dist/subcommands.js.map +1 -0
  650. package/dist/task-cli/cli.js +78 -25
  651. package/dist/task-cli/cli.js.map +1 -1
  652. package/dist/task-cli/discovery.d.ts +4 -2
  653. package/dist/task-cli/discovery.js +14 -13
  654. package/dist/task-cli/discovery.js.map +1 -1
  655. package/dist/task-cli/hint.d.ts +20 -0
  656. package/dist/task-cli/hint.js +14 -0
  657. package/dist/task-cli/hint.js.map +1 -0
  658. package/dist/task-cli/http.d.ts +1 -0
  659. package/dist/task-cli/http.js +3 -1
  660. package/dist/task-cli/http.js.map +1 -1
  661. package/dist/task-cli/projections.d.ts +14 -11
  662. package/dist/task-cli/projections.js +15 -11
  663. package/dist/task-cli/projections.js.map +1 -1
  664. package/dist/task-cli/watch.d.ts +18 -10
  665. package/dist/task-cli/watch.js +128 -20
  666. package/dist/task-cli/watch.js.map +1 -1
  667. package/dist/ui-state.d.ts +17 -3
  668. package/dist/ui-state.js +56 -9
  669. package/dist/ui-state.js.map +1 -1
  670. package/dist/update-check.d.ts +0 -10
  671. package/dist/update-check.js +1 -16
  672. package/dist/update-check.js.map +1 -1
  673. package/dist/voice/decide.d.ts +32 -0
  674. package/dist/voice/decide.js +297 -0
  675. package/dist/voice/decide.js.map +1 -0
  676. package/dist/voice/decision-log.d.ts +52 -0
  677. package/dist/voice/decision-log.js +107 -0
  678. package/dist/voice/decision-log.js.map +1 -0
  679. package/dist/voice/engine.d.ts +119 -0
  680. package/dist/voice/engine.js +60 -0
  681. package/dist/voice/engine.js.map +1 -0
  682. package/dist/voice/fake-engine.d.ts +10 -0
  683. package/dist/voice/fake-engine.js +192 -0
  684. package/dist/voice/fake-engine.js.map +1 -0
  685. package/dist/voice/jev-engine.d.ts +17 -0
  686. package/dist/voice/jev-engine.js +110 -0
  687. package/dist/voice/jev-engine.js.map +1 -0
  688. package/dist/voice/pending-ask.d.ts +17 -0
  689. package/dist/voice/pending-ask.js +50 -0
  690. package/dist/voice/pending-ask.js.map +1 -0
  691. package/dist/voice/routes.d.ts +115 -0
  692. package/dist/voice/routes.js +91 -0
  693. package/dist/voice/routes.js.map +1 -0
  694. package/dist/voice/sentence.d.ts +21 -0
  695. package/dist/voice/sentence.js +61 -0
  696. package/dist/voice/sentence.js.map +1 -0
  697. package/dist/voice/thresholds.d.ts +28 -0
  698. package/dist/voice/thresholds.js +29 -0
  699. package/dist/voice/thresholds.js.map +1 -0
  700. package/dist/webhooks/cli.d.ts +18 -0
  701. package/dist/webhooks/cli.js +145 -0
  702. package/dist/webhooks/cli.js.map +1 -0
  703. package/dist/webhooks/prompt.d.ts +5 -0
  704. package/dist/webhooks/prompt.js +26 -0
  705. package/dist/webhooks/prompt.js.map +1 -0
  706. package/dist/workflows/active-run.d.ts +167 -0
  707. package/dist/workflows/active-run.js +21 -0
  708. package/dist/workflows/active-run.js.map +1 -0
  709. package/dist/workflows/continuation-admission.d.ts +913 -0
  710. package/dist/workflows/continuation-admission.js +265 -0
  711. package/dist/workflows/continuation-admission.js.map +1 -0
  712. package/dist/workflows/execution-account.d.ts +13 -0
  713. package/dist/workflows/execution-account.js +18 -0
  714. package/dist/workflows/execution-account.js.map +1 -0
  715. package/dist/workflows/input-acknowledgement.d.ts +152 -0
  716. package/dist/workflows/input-acknowledgement.js +38 -0
  717. package/dist/workflows/input-acknowledgement.js.map +1 -0
  718. package/dist/workflows/line-order.d.ts +5 -0
  719. package/dist/workflows/line-order.js +25 -0
  720. package/dist/workflows/line-order.js.map +1 -0
  721. package/dist/workflows/outcome-batches.d.ts +20 -0
  722. package/dist/workflows/outcome-batches.js +25 -0
  723. package/dist/workflows/outcome-batches.js.map +1 -0
  724. package/dist/workflows/question-routing.d.ts +8 -0
  725. package/dist/workflows/question-routing.js +18 -0
  726. package/dist/workflows/question-routing.js.map +1 -0
  727. package/dist/workflows/run-scheduling.d.ts +35 -0
  728. package/dist/workflows/run-scheduling.js +61 -0
  729. package/dist/workflows/run-scheduling.js.map +1 -0
  730. package/dist/workflows/run-states.d.ts +190 -0
  731. package/dist/workflows/run-states.js +295 -0
  732. package/dist/workflows/run-states.js.map +1 -0
  733. package/dist/workflows/run.d.ts +1296 -278
  734. package/dist/workflows/run.js +5730 -3265
  735. package/dist/workflows/run.js.map +1 -1
  736. package/dist/workflows/turn-end.d.ts +116 -0
  737. package/dist/workflows/turn-end.js +68 -0
  738. package/dist/workflows/turn-end.js.map +1 -0
  739. package/dist/workflows/types.d.ts +35 -130
  740. package/dist/workflows/types.js +47 -100
  741. package/dist/workflows/types.js.map +1 -1
  742. package/dist/workflows/waits.d.ts +74 -0
  743. package/dist/workflows/waits.js +197 -0
  744. package/dist/workflows/waits.js.map +1 -0
  745. package/dist/workspace/agent-accounts.d.ts +5 -38
  746. package/dist/workspace/agent-accounts.js +14 -40
  747. package/dist/workspace/agent-accounts.js.map +1 -1
  748. package/dist/workspace/agent-profiles.d.ts +40 -16
  749. package/dist/workspace/agent-profiles.js +77 -17
  750. package/dist/workspace/agent-profiles.js.map +1 -1
  751. package/dist/workspace/config.d.ts +48 -49
  752. package/dist/workspace/config.js +68 -72
  753. package/dist/workspace/config.js.map +1 -1
  754. package/dist/workspace/migrations.d.ts +3 -13
  755. package/dist/workspace/migrations.js.map +1 -1
  756. package/dist/workspace/projects-cli.d.ts +1 -1
  757. package/dist/workspace/projects-cli.js.map +1 -1
  758. package/dist/workspace/projects.d.ts +15 -18
  759. package/dist/workspace/projects.js +20 -16
  760. package/dist/workspace/projects.js.map +1 -1
  761. package/dist/workspace/semaphore.d.ts +11 -45
  762. package/dist/workspace/semaphore.js +6 -18
  763. package/dist/workspace/semaphore.js.map +1 -1
  764. package/dist/workspace/ui-state.d.ts +6 -4
  765. package/dist/workspace/ui-state.js +6 -4
  766. package/dist/workspace/ui-state.js.map +1 -1
  767. package/dist/workspace/webhooks.d.ts +40 -0
  768. package/dist/workspace/webhooks.js +146 -0
  769. package/dist/workspace/webhooks.js.map +1 -0
  770. package/dist/worktree-manifest.d.ts +165 -0
  771. package/dist/worktree-manifest.js +530 -0
  772. package/dist/worktree-manifest.js.map +1 -0
  773. package/dist/worktree-setup.js.map +1 -1
  774. package/package.json +6 -5
  775. package/scripts/benchmark-run-store-fixture.ts +7 -3
  776. package/scripts/check-pack.mjs +3 -1
  777. package/scripts/inline-contract.mjs +80 -27
  778. package/scripts/install-as-command.mjs +3 -1
  779. package/scripts/measure-transcript-facts-health.mjs +87 -0
  780. package/scripts/measure-transcript-facts-lag.mjs +68 -0
  781. package/scripts/mock-ci-tool.mjs +49 -6
  782. package/scripts/mock-claude-model-refusal.mjs +42 -0
  783. package/scripts/mock-claude.mjs +833 -591
  784. package/scripts/mock-codex-app-server.mjs +785 -418
  785. package/scripts/mock-cursor-acp.mjs +296 -125
  786. package/scripts/mock-cursor-print.mjs +256 -0
  787. package/scripts/mock-deferred-turn-start.mjs +16 -0
  788. package/scripts/mock-delivery-control.d.mts +23 -0
  789. package/scripts/mock-delivery-control.mjs +80 -0
  790. package/scripts/mock-env-record.mjs +11 -0
  791. package/scripts/mock-failure-control.d.mts +13 -0
  792. package/scripts/mock-failure-control.mjs +68 -0
  793. package/scripts/mock-multi-pr.mjs +7 -0
  794. package/scripts/mock-nudge.mjs +45 -0
  795. package/scripts/mock-omp-rpc.mjs +429 -255
  796. package/scripts/mock-opencode-serve.mjs +972 -557
  797. package/scripts/mock-pi-rpc.mjs +558 -331
  798. package/scripts/mock-runner-crash.mjs +18 -2
  799. package/scripts/mock-session-context.d.mts +5 -0
  800. package/scripts/mock-session-context.mjs +56 -0
  801. package/scripts/mock-silent-tail.mjs +10 -3
  802. package/scripts/pi-default-tools.mjs +22 -0
  803. package/scripts/pi-project-skills.mjs +59 -0
  804. package/scripts/probe-cursor-print.mjs +627 -0
  805. package/scripts/transcript-facts-fixture.mjs +53 -0
  806. package/scripts/voice-replay.ts +50 -0
  807. package/scripts/windows-workspace-identity.cs +208 -0
  808. package/scripts/windows-workspace-identity.ps1 +10 -0
  809. package/web/dist/assets/alert-dialog-DfctKUMw.js +1 -0
  810. package/web/dist/assets/button-CeupvoKG.js +41 -0
  811. package/web/dist/assets/centered-state-hDsyXrnH.js +1 -0
  812. package/web/dist/assets/cez-idle-D3aj_9lp.js +1 -0
  813. package/web/dist/assets/diff-BnIXZK3i2.js +3 -0
  814. package/web/dist/assets/diff-controls-GewpW9aF.js +1 -0
  815. package/web/dist/assets/diff-view-Byo7gpvW.js +11 -0
  816. package/web/dist/assets/dropdown-menu-COj_J5wd.js +6 -0
  817. package/web/dist/assets/eye-B8okNWXU.js +1 -0
  818. package/web/dist/assets/git-toolbar-DY0z0n4S.js +1 -0
  819. package/web/dist/assets/github-DwU6ALRK.js +1 -0
  820. package/web/dist/assets/{image-preview-FOEfqpZP.js → image-preview-C_t9q45f.js} +1 -1
  821. package/web/dist/assets/index-BOfE8pyD.js +34 -0
  822. package/web/dist/assets/index-D9HpsmdW.css +1 -0
  823. package/web/dist/assets/live-protocol-Uce2NwEX.js +64 -0
  824. package/web/dist/assets/live-worker-BizfZSoh.ts +33 -0
  825. package/web/dist/assets/markdown-BBDDg-4t.js +1 -0
  826. package/web/dist/assets/nav-row-styles-AoIhhIwf.js +1 -0
  827. package/web/dist/assets/popover-D56fhiXj.js +1 -0
  828. package/web/dist/assets/preview-pane-connected-BmVqfSU5.js +3 -0
  829. package/web/dist/assets/reference-chip-gQNk-4jc.js +1 -0
  830. package/web/dist/assets/repo-git-B5ECDUuv.js +1 -0
  831. package/web/dist/assets/{repo-git-CoKp-g9K.css → repo-git-DVXNOJDT.css} +1 -1
  832. package/web/dist/assets/run-header-C0Y1NvEM.js +2 -0
  833. package/web/dist/assets/run-header-CC7VUTg9.css +1 -0
  834. package/web/dist/assets/skills-DUeTkZ9z.js +1 -0
  835. package/web/dist/assets/status-dot-1PR0i69P.js +1 -0
  836. package/web/dist/assets/tab-link-DmWk4l5O.js +1 -0
  837. package/web/dist/assets/task-changes-DKZXuW89.js +1 -0
  838. package/web/dist/assets/task-commits-BZ6KxWdF.js +1 -0
  839. package/web/dist/assets/task-files-DlF7Apcn.js +2 -0
  840. package/web/dist/assets/task-github-item-CVPo3geZ.js +1 -0
  841. package/web/dist/assets/task-thread-DtQ6uGfQ.css +1 -0
  842. package/web/dist/assets/task-thread-mm4-ECyo.js +9 -0
  843. package/web/dist/assets/thread-state-CIw2BlhT.js +4 -0
  844. package/web/dist/assets/use-hand-to-agent-state-BCDjzHnG.js +1 -0
  845. package/web/dist/assets/use-hand-to-agent-state-C7esPJ-N.css +1 -0
  846. package/web/dist/assets/workflows-D8vYD-ql.js +7 -0
  847. package/web/dist/assets/zoomable-image-CxQSsfcn.js +1 -0
  848. package/web/dist/index.html +13 -17
  849. package/web/dist/live-worker.js +4 -0
  850. package/dist/core/tool-display.js +0 -179
  851. package/dist/core/tool-display.js.map +0 -1
  852. package/dist/runs/human-ask-summary.d.ts +0 -9
  853. package/dist/runs/human-ask-summary.js +0 -39
  854. package/dist/runs/human-ask-summary.js.map +0 -1
  855. package/dist/runs/run-search.d.ts +0 -29
  856. package/dist/runs/run-search.js +0 -63
  857. package/dist/runs/run-search.js.map +0 -1
  858. package/dist/skills-banner.d.ts +0 -22
  859. package/dist/skills-banner.js +0 -41
  860. package/dist/skills-banner.js.map +0 -1
  861. package/dist/skills-update.d.ts +0 -88
  862. package/dist/skills-update.js +0 -392
  863. package/dist/skills-update.js.map +0 -1
  864. package/dist/todos.d.ts +0 -48
  865. package/dist/todos.js +0 -201
  866. package/dist/todos.js.map +0 -1
  867. package/web/dist/assets/agent-accounts-CFOBMVHc.js +0 -1
  868. package/web/dist/assets/alert-dialog-BrpNUwMt.js +0 -1
  869. package/web/dist/assets/button-C_rd7CPp.js +0 -43
  870. package/web/dist/assets/compare-variants-D6WXhvXA.js +0 -1
  871. package/web/dist/assets/diff-DrEARXTV.js +0 -3
  872. package/web/dist/assets/diff-controls-B1-j7nFg.js +0 -1
  873. package/web/dist/assets/diff-view-DvGLjdTk.js +0 -6
  874. package/web/dist/assets/dropdown-menu-BegOZFrj.js +0 -1
  875. package/web/dist/assets/format-DpNe3S0A.js +0 -1
  876. package/web/dist/assets/git-toolbar-Cw4ljmhB.js +0 -1
  877. package/web/dist/assets/github-CfV5sqL0.js +0 -1
  878. package/web/dist/assets/index-DgWWlOLy.js +0 -13
  879. package/web/dist/assets/index-fBFnuvxu.css +0 -1
  880. package/web/dist/assets/nav-row-styles-vp2JbhuJ.js +0 -1
  881. package/web/dist/assets/popover-BohP6WTx.js +0 -1
  882. package/web/dist/assets/preview-pane-connected-qTA5KNoJ.js +0 -3
  883. package/web/dist/assets/preview-state-vuMviq6q.js +0 -1
  884. package/web/dist/assets/prompt-templates-Cubdut_C.js +0 -15
  885. package/web/dist/assets/repo-git-Cf4t5PD0.js +0 -1
  886. package/web/dist/assets/run-diff-BpkUW37Z.js +0 -3
  887. package/web/dist/assets/run-header-68WHf9wV.js +0 -1
  888. package/web/dist/assets/run-header-8GObNx6F.css +0 -1
  889. package/web/dist/assets/search-x-I9Q8pgUa.js +0 -1
  890. package/web/dist/assets/skills-DFBiAuJG.js +0 -1
  891. package/web/dist/assets/skills-DiVIgKeU.js +0 -1
  892. package/web/dist/assets/square-terminal-B6RYYieR.js +0 -1
  893. package/web/dist/assets/status-dot-CqTJHSP9.js +0 -1
  894. package/web/dist/assets/tab-link-B07Hu4QX.js +0 -1
  895. package/web/dist/assets/task-changes--lQF6EYf.js +0 -1
  896. package/web/dist/assets/task-commits-Cx0qy4DN.js +0 -1
  897. package/web/dist/assets/task-files-D50s-gKW.js +0 -2
  898. package/web/dist/assets/task-github-item-DaqmSwYT.js +0 -1
  899. package/web/dist/assets/task-thread-BPQ64lui.js +0 -7
  900. package/web/dist/assets/task-thread-zNE2Q9E-.css +0 -1
  901. package/web/dist/assets/textarea-D20O4Pw0.js +0 -1
  902. package/web/dist/assets/thread-state-DWcFMrvb.js +0 -4
  903. package/web/dist/assets/use-hand-to-agent-state-BLIGmwbd.css +0 -1
  904. package/web/dist/assets/use-hand-to-agent-state-Cy9eyyfL.js +0 -1
  905. package/web/dist/assets/utils-JsL3ZsJf.js +0 -64
  906. package/web/dist/assets/workflows-jwG7uxqi.js +0 -11
  907. package/web/dist/assets/zoomable-image-BJJ64KGc.js +0 -1
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ // Load only when configured: ordinary mock turns retain their event-loop ordering.
3
+ const failureControl = process.env.CEZ_MOCK_FAILURE_CONTROL === undefined ? undefined
4
+ : (await import('./mock-failure-control.mjs')).readFailureControl();
5
+ const delivery = process.env.CEZ_MOCK_DELIVERY_CONTROL === undefined ? undefined
6
+ : (await import('./mock-delivery-control.mjs')).readDeliveryControl(['reject', 'hold-ack', 'hold-response']);
2
7
  // Keep this fixture standalone: lifecycle tests copy the runner into a temp directory.
3
8
  import { spawn as watchdogSpawn } from 'node:child_process';
4
- import { writeFileSync as watchdogWritePid } from 'node:fs';
9
+ import { renameSync as watchdogRename, writeFileSync as watchdogWrite } from 'node:fs';
10
+ // Publish a pid atomically (temporary sibling, then rename): a test reading it sees no
11
+ // file or a whole pid, never the empty file whose Number() is 0 (hearsay-tools/cezarion#953).
12
+ function watchdogWritePid(file, pid) { const tmp = `${file}.${process.pid}.tmp`; watchdogWrite(tmp, pid); watchdogRename(tmp, file); }
13
+ let inputClosed = false;
5
14
  function watchdogStall(prompt) {
6
15
  if (!prompt.includes('mock:no-progress')) return false;
7
- watchdogWritePid('watchdog.pid', String(process.pid));
16
+ if (prompt.includes('input-closed')) {
17
+ inputClosed = true;
18
+ watchdogWritePid('input-closed.pid', String(process.pid));
19
+ setTimeout(() => process.exit(0), 300);
20
+ }
8
21
  if (prompt.includes('ignore-term')) {
9
22
  process.removeAllListeners('SIGTERM');
10
23
  process.on('SIGTERM', () => {});
@@ -17,10 +30,12 @@ function watchdogStall(prompt) {
17
30
  if (prompt.includes('leftover')) {
18
31
  // hearsay-tools/cezarion#890: one child stays in the session's process group and ignores
19
32
  // 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)`;
33
+ const leftover = (file) => `process.on('SIGTERM',()=>{}); const fs = require('fs'); fs.writeFileSync(${JSON.stringify(`${file}.tmp`)}, String(process.pid)); fs.renameSync(${JSON.stringify(`${file}.tmp`)}, ${JSON.stringify(file)}); setTimeout(()=>{},20000)`;
21
34
  watchdogSpawn(process.execPath, ['-e', leftover('leftover-group.pid')], { stdio: 'ignore' });
22
35
  watchdogSpawn(process.execPath, ['-e', leftover('leftover-session.pid')], { stdio: 'ignore', detached: true }).unref();
23
36
  }
37
+ // Publish readiness after mode setup: another process may signal us as soon as it sees it.
38
+ watchdogWritePid('watchdog.pid', String(process.pid));
24
39
  return true;
25
40
  }
26
41
 
@@ -32,10 +47,10 @@ function watchdogStall(prompt) {
32
47
  // arrive over SSE afterwards, so a correct stream (v1 and v2 alike) must
33
48
  // take its turn-end from `session.idle`, never from the HTTP response.
34
49
  import { createHash } from 'node:crypto';
35
- // Aliased: owned-input-delivery.testkit.ts prepends its own `existsSync`/`writeFileSync` import,
36
- // and a second import of the same name is a SyntaxError.
37
50
  import { appendFileSync, existsSync as sessionStoreExists, mkdirSync, readFileSync, writeFileSync as sessionStoreWrite } from 'node:fs';
38
51
  import { createServer } from 'node:http';
52
+ // Dynamic, like every sibling import here, so a test's copy of this mock can repoint it.
53
+ const { answerNudgeBody, silentNudge } = await import('./mock-nudge.mjs');
39
54
  import { tmpdir } from 'node:os';
40
55
  import { dirname, join } from 'node:path';
41
56
 
@@ -59,6 +74,7 @@ if (failStarts > 0) {
59
74
  if (process.env.CEZ_MOCK_ARGS_FILE && process.env.OPENCODE_CONFIG_CONTENT) appendFileSync(process.env.CEZ_MOCK_ARGS_FILE, JSON.stringify({ type: 'runtime-config', config: JSON.parse(process.env.OPENCODE_CONFIG_CONTENT) }) + '\n');
60
75
  if (process.env.CEZ_MOCK_CI_PR) { const { probeCiTool } = await import('./mock-ci-tool.mjs'); await probeCiTool('opencode', JSON.parse(process.env.OPENCODE_CONFIG_CONTENT ?? '{}')); }
61
76
  import * as parityGateFs from 'node:fs';
77
+ import { withoutSessionContextBody } from './mock-session-context.mjs';
62
78
 
63
79
  // #401: let the test observe the actual monitoring park before releasing late wire frames.
64
80
  async function afterParityPark(prompt) {
@@ -86,6 +102,9 @@ const hostname = arg('--hostname', '127.0.0.1');
86
102
  const DEFAULT_SESSION_ID = 'ses_mock_1';
87
103
  let SESSION_ID = DEFAULT_SESSION_ID;
88
104
  const MESSAGE_ID = 'msg_mock_1';
105
+ // hearsay-tools/cezarion#1265: the captured FrontmatterError text, verbatim,
106
+ // for a skill whose frontmatter holds `foo: "unterminated`.
107
+ const FRONTMATTER_ERROR = '/home/agent/.agents/skills/bad4/SKILL.md: Failed to parse YAML frontmatter: unexpected end of the stream within a double quoted scalar at line 6, column 1:\n \n ^';
89
108
 
90
109
  function sessionStorePath() {
91
110
  if (process.env.CEZ_MOCK_OPENCODE_SESSIONS_FILE) return process.env.CEZ_MOCK_OPENCODE_SESSIONS_FILE;
@@ -167,11 +186,37 @@ const write = (event) => {
167
186
  // assistant message names it as parentID. Steers are read just before the turn's
168
187
  // idle, unless the turn is `late` (the upstream lost wake: never read).
169
188
  let turnActive = false;
189
+ // The heartbeats of a held `mock:ack-only` turn (hearsay-tools/cezarion#1301).
190
+ let heldBeats;
170
191
  let turnLate = false;
171
192
  let steers = [];
172
193
  let steerSerial = 0;
194
+ // CEZ_MOCK_OPENCODE_FOLLOW_ON_MS: the server reads steers in a follow-on run that starts that long
195
+ // after the idle, and a prompt posted in between waits behind that run.
196
+ const followOnMs = Number(process.env.CEZ_MOCK_OPENCODE_FOLLOW_ON_MS ?? 0);
197
+ let followOn = null;
198
+ const runFollowOn = (held) => {
199
+ followOn = sleep(followOnMs).then(() => {
200
+ turnActive = true;
201
+ for (const steer of held) {
202
+ const id = `msg_steer_reply_${++steerSerial}`;
203
+ write({ type: 'message.updated', properties: { info: { ...info({}), id, parentID: steer.userId } } });
204
+ write({ type: 'message.part.updated', properties: { part: { id: `prt_${id}`, messageID: id, sessionID: SESSION_ID, type: 'text', text: steer.text, time: { start: 1, end: 2 } } } });
205
+ }
206
+ turnActive = false;
207
+ followOn = null;
208
+ write({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
209
+ });
210
+ };
173
211
  const send = (event) => {
174
212
  const ownIdle = event?.type === 'session.idle' && (!event.properties?.sessionID || event.properties.sessionID === SESSION_ID);
213
+ if (ownIdle && followOnMs > 0 && steers.length && !turnLate) {
214
+ const held = steers;
215
+ steers = []; turnActive = false;
216
+ write(event);
217
+ runFollowOn(held);
218
+ return;
219
+ }
175
220
  if (ownIdle) {
176
221
  if (!turnLate) for (const steer of steers) {
177
222
  const id = `msg_steer_reply_${++steerSerial}`;
@@ -209,6 +254,31 @@ const sendQuestionPart = (input) =>
209
254
  },
210
255
  },
211
256
  });
257
+ /** hearsay-tools/cezarion#1266: OpenCode 1.18.35 `permission.asked`. No `session.idle`
258
+ * follows: the asking tool waits until `POST /permission/{id}/reply` answers it. */
259
+ const pendingPermissionAsks = new Set();
260
+ // hearsay-tools/cezarion#1266 lifecycle scenarios (harness parity P5, P7).
261
+ let failPermissionReplies = 0;
262
+ let classifyPending = false;
263
+ async function waitForMockFile(file) {
264
+ // bound: hang guard for a test that never creates its gate file; the wait itself is the file.
265
+ const deadline = Date.now() + 15_000;
266
+ while (!parityGateFs.existsSync(file)) {
267
+ if (Date.now() > deadline) throw new Error(`${file} was not created`);
268
+ // bound: poll interval; the gate file is written by another process, with no event to await.
269
+ await new Promise(r => setTimeout(r, 10));
270
+ }
271
+ }
272
+ const textPart = (id, text) => send({ type: 'message.part.updated', properties: { part: {
273
+ id, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 },
274
+ } } });
275
+ const sendPermissionAsk = (id, permission, patterns, always) => {
276
+ pendingPermissionAsks.add(id);
277
+ send({ type: 'permission.asked', properties: {
278
+ id, sessionID: SESSION_ID, permission, patterns, metadata: {}, always,
279
+ tool: { messageID: MESSAGE_ID, callID: 'call_mock_bash' },
280
+ } });
281
+ };
212
282
  // The user message of the prompt being answered: opencode 1.18.32 creates it at the
213
283
  // POST and names it as the assistant's parentID (#505 probe).
214
284
  let currentUserId;
@@ -230,13 +300,93 @@ const info = (extra) => ({
230
300
  let autonomousTurn = 0;
231
301
  let autonomousCap = false;
232
302
  let autonomousReadinessIdle = false;
233
- const server = createServer((req, res) => {
303
+ const bodyRoutes = [
304
+ [(req, url) => req.method === 'POST' && /^\/question\/[^/]+\/(reply|reject)$/.test(url), replyToQuestion],
305
+ [(req, url) => req.method === 'POST' && /^\/permission\/([^/]+)\/reply$/.test(url), replyToPermission],
306
+ [(req, url) => req.method === 'GET' && /^\/session\/([^/]+)$/.test(url), replyWithSession],
307
+ [(req, url) => req.method === 'PATCH' && /^\/session\/([^/]+)$/.test(url), replyWithPatchedSession],
308
+ [(req, url) => req.method === 'POST' && url === '/session', replyWithNewSession],
309
+ [matchesSessionPrompt, handleSessionPrompt],
310
+ ];
311
+ const earlyReplies = [
312
+ [body => body.includes('mock:steer-tool'), replyWithSteerTool],
313
+ [body => body.includes('mock:steer-late'), replyWithLateSteer],
314
+ [body => body.includes('mock:cezar-tool'), replyWithCezarTool],
315
+ [body => body.includes('mock:ci-wait'), replyWithCiWait],
316
+ [body => body.includes('mock:multi-pr-refs'), replyWithMultiplePrs],
317
+ [body => body.includes('mock:turn-messages:'), replyWithTurnMessages],
318
+ [body => body.includes('mock:agent-echo'), replyWithEcho],
319
+ [body => body.includes('mock:subagent-after-park'), replyWithParkedSubagent],
320
+ [body => body.includes('mock:subagent'), replyWithSubagent],
321
+ ];
322
+ const lateReplies = [
323
+ [body => body.includes('mock:split-text'), replyWithSplitText],
324
+ [body => body.includes('mock:autonomous') || body.includes('Continue working autonomously until the task is fully complete.'), replyAutonomously],
325
+ [body => body.includes('mock:silent-tail') || body.includes('mock:tool-tail') || silentNudge(body), replyWithSilentTail],
326
+ [body => body.includes('mock:approval-resolved') || body.includes('mock:approval-turn-end'), replyWithSettledApproval],
327
+ [body => body.includes('mock:approval-reply-fails'), replyWithFailedApprovalReply],
328
+ [body => body.includes('mock:permission-malformed'), replyWithMalformedPermission],
329
+ [body => body.includes('mock:permission-classify'), replyWithClassifyingPermission],
330
+ [body => body.includes('mock:permission-late'), replyWithLatePermission],
331
+ [body => body.includes('mock:permission-default'), replyWithDefaultPermission],
332
+ [body => body.includes('mock:approval'), replyWithApproval],
333
+ [body => body.includes('mock:ask-snapshot'), replyWithAskSnapshot],
334
+ [body => body.includes('mock:ask-prose'), replyWithAskProse],
335
+ [body => body.includes('mock:ask-bad'), replyWithMalformedAsk],
336
+ [body => body.includes('mock:ask'), replyWithAsk],
337
+ [body => body.includes('mock:hold-done'), replyWithHeldDone],
338
+ [body => body.includes('mock:done'), replyWithDone],
339
+ [body => body.includes('mock:hold-ask'), replyWithHeldAsk],
340
+ [body => watchdogStall(body), acknowledgeWatchdog],
341
+ [body => body.includes('mock:busy-progress'), replyWithBusyProgress],
342
+ [body => body.includes('mock:hold-gated'), replyWithGatedHold],
343
+ [body => body.includes('mock:hold'), replyWithHold],
344
+ ];
345
+ function matchesSessionPrompt(req, url) {
346
+ const match = req.method === 'POST' && /^\/session\/([^/]+)\/(prompt_async|message)$/.exec(url);
347
+ return match && getSession(match[1]);
348
+ }
349
+ async function dispatchReply(replies, body) {
350
+ for (const [matches, reply] of replies) {
351
+ if (matches(body) && await reply(body) !== false) return true;
352
+ }
353
+ return false;
354
+ }
355
+
356
+ const server = createServer(handleRequest);
357
+
358
+ function handleRequest(req, res) {
234
359
  const url = req.url ?? '';
360
+ if (inputClosed && req.method === 'POST') { req.socket.destroy(); return; }
235
361
  if (req.method === 'GET' && url === '/question') {
236
362
  res.writeHead(200, { 'content-type': 'application/json' });
237
363
  res.end(JSON.stringify(pendingQuestions));
238
364
  return;
239
365
  }
366
+ // Only user-written config, as the real server returns it: this user configured `bash: ask`,
367
+ // so `mock:approval` reaches the cockpit and the built-in `mock:permission-default` ask does not.
368
+ if (req.method === 'GET' && url === '/config') {
369
+ const answer = () => {
370
+ res.writeHead(200, { 'content-type': 'application/json' });
371
+ res.end(JSON.stringify({ permission: { bash: 'ask' } }));
372
+ };
373
+ if (!classifyPending) { answer(); return; }
374
+ // P7: hold the config read, settle the ask natively while cezar classifies it, then send a
375
+ // marker ask. Files beside CEZ_MOCK_RELEASE_FILE hand each step to the test and back.
376
+ classifyPending = false;
377
+ const release = process.env.CEZ_MOCK_RELEASE_FILE;
378
+ void (async () => {
379
+ parityGateFs.writeFileSync(`${release}.config-read`, '');
380
+ await waitForMockFile(`${release}.settle`);
381
+ pendingPermissionAsks.delete('per_mock_classify');
382
+ send({ type: 'permission.replied', properties: { sessionID: SESSION_ID, requestID: 'per_mock_classify', reply: 'reject' } });
383
+ textPart('prt_classify_settled', 'OpenCode settled the ask itself.');
384
+ await waitForMockFile(release);
385
+ answer();
386
+ sendPermissionAsk('per_mock_marker', 'bash', ['marker'], ['marker']);
387
+ })();
388
+ return;
389
+ }
240
390
  if (req.method === 'GET' && url.startsWith('/event')) {
241
391
  res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' });
242
392
  sse = res;
@@ -245,562 +395,821 @@ const server = createServer((req, res) => {
245
395
  }
246
396
  let body = '';
247
397
  req.on('data', (chunk) => (body += chunk));
248
- req.on('end', async () => {
249
- if (process.env.CEZ_MOCK_ARGS_FILE) appendFileSync(process.env.CEZ_MOCK_ARGS_FILE, `${JSON.stringify({ method: req.method, url, body: body ? JSON.parse(body) : undefined })}\n`);
250
- if (req.method === 'POST' && /^\/question\/[^/]+\/(reply|reject)$/.test(url)) {
251
- // Native question reply resumes the held turn, then its own session.idle
252
- // ends it (same SSE lifecycle shape as the baseline; never the HTTP ack).
253
- const answered = pendingQuestions.some(question => question.id === 'q_mock_1');
254
- pendingQuestions.length = 0;
255
- if (answered && url.endsWith('/reply')) setTimeout(() => {
256
- send({ type: 'message.part.updated', properties: { part: {
257
- id: 'prt_answer', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: `human answer: ${body}${answerDone ? '\n\nCEZ:DONE' : ''}`,
258
- } } });
259
- // #505 review: CEZ_MOCK_OPENCODE_REPLY_HOLD_MS keeps the answered turn running.
260
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), Number(process.env.CEZ_MOCK_OPENCODE_REPLY_HOLD_MS ?? 0));
261
- }, 30);
262
- const acknowledge = () => { res.writeHead(200, { 'content-type': 'application/json' }); res.end('{}'); };
263
- if (lateQuestionReply) setTimeout(acknowledge, 250);
264
- else acknowledge();
265
- return;
266
- }
267
- const sessionGet = req.method === 'GET' && /^\/session\/([^/]+)$/.exec(url);
268
- if (sessionGet) {
269
- const session = getSession(sessionGet[1]);
270
- if (!session) {
271
- res.writeHead(404, { 'content-type': 'application/json' });
272
- res.end(JSON.stringify({ error: 'NotFoundError' }));
273
- return;
274
- }
275
- res.writeHead(200, { 'content-type': 'application/json' });
276
- res.end(JSON.stringify({
277
- id: session.id,
278
- title: session.title,
279
- ...(Array.isArray(session.permission) ? { permission: session.permission } : {}),
280
- }));
281
- return;
282
- }
283
- const sessionPatch = req.method === 'PATCH' && /^\/session\/([^/]+)$/.exec(url);
284
- if (sessionPatch) {
285
- const session = patchSession(sessionPatch[1], body ? JSON.parse(body) : {});
286
- if (!session) {
287
- res.writeHead(404, { 'content-type': 'application/json' });
288
- res.end(JSON.stringify({ error: 'NotFoundError' }));
289
- return;
290
- }
291
- res.writeHead(200, { 'content-type': 'application/json' });
292
- res.end(JSON.stringify({
293
- id: session.id,
294
- title: session.title,
295
- ...(Array.isArray(session.permission) ? { permission: session.permission } : {}),
296
- }));
297
- return;
398
+ req.on('end', async () => { await handleRequestBody(req, res, url, body); });
399
+ }
400
+
401
+ async function handleSessionPrompt(req, res, url, body, received) {
402
+ const sessionPrompt = /^\/session\/([^/]+)\/(prompt_async|message)$/.exec(url);
403
+ SESSION_ID = sessionPrompt[1];
404
+ recordSessionPrompt(body);
405
+ if (await preparePromptTurn(res, url, body, received)) return;
406
+ await streamPrompt(url, body);
407
+ }
408
+
409
+ async function handleRequestBody(req, res, url, body) {
410
+ const prepared = prepareRequestBody(req, url, body);
411
+ body = prepared.body;
412
+ const route = bodyRoutes.find(([matches]) => matches(req, url));
413
+ if (route) { await route[1](req, res, url, body, prepared.received); return; }
414
+ res.writeHead(200, { 'content-type': 'application/json' });
415
+ res.end('{}');
416
+ }
417
+
418
+ function prepareRequestBody(req, url, body) {
419
+ if (process.env.CEZ_MOCK_ARGS_FILE) appendFileSync(process.env.CEZ_MOCK_ARGS_FILE, `${JSON.stringify({ method: req.method, url, body: body ? JSON.parse(body) : undefined })}\n`);
420
+ // The delivery control also reads the prompt Cezar sent, before a nudge's scripted answer replaces it.
421
+ if (req.method === 'POST' && /\/(prompt_async|message)$/.test(url)) body = withoutSessionContextBody(body);
422
+ const received = body;
423
+ if (req.method === 'POST' && /\/(prompt_async|message)$/.test(url)) body = answerNudgeBody(body);
424
+
425
+ return { body, received };
426
+ }
427
+
428
+ function replyWithNewSession(req, res, url, body) {
429
+ const parsed = body ? JSON.parse(body) : {};
430
+ SESSION_ID = createSession(parsed);
431
+ res.writeHead(200, { 'content-type': 'application/json' });
432
+ res.end(JSON.stringify({
433
+ id: SESSION_ID,
434
+ title: parsed.title ?? 'cezar task',
435
+ ...(Array.isArray(parsed.permission) ? { permission: parsed.permission } : {}),
436
+ }));
437
+ return;
438
+ }
439
+
440
+ function replyWithPatchedSession(req, res, url, body) {
441
+ const sessionPatch = /^\/session\/([^/]+)$/.exec(url);
442
+ const session = patchSession(sessionPatch[1], body ? JSON.parse(body) : {});
443
+ if (!session) {
444
+ res.writeHead(404, { 'content-type': 'application/json' });
445
+ res.end(JSON.stringify({ error: 'NotFoundError' }));
446
+ return;
447
+ }
448
+ res.writeHead(200, { 'content-type': 'application/json' });
449
+ res.end(JSON.stringify({
450
+ id: session.id,
451
+ title: session.title,
452
+ ...(Array.isArray(session.permission) ? { permission: session.permission } : {}),
453
+ }));
454
+ return;
455
+ }
456
+
457
+ function replyWithSession(req, res, url, body) {
458
+ const sessionGet = /^\/session\/([^/]+)$/.exec(url);
459
+ const session = getSession(sessionGet[1]);
460
+ if (!session) {
461
+ res.writeHead(404, { 'content-type': 'application/json' });
462
+ res.end(JSON.stringify({ error: 'NotFoundError' }));
463
+ return;
464
+ }
465
+ res.writeHead(200, { 'content-type': 'application/json' });
466
+ res.end(JSON.stringify({
467
+ id: session.id,
468
+ title: session.title,
469
+ ...(Array.isArray(session.permission) ? { permission: session.permission } : {}),
470
+ }));
471
+ return;
472
+ }
473
+
474
+ function replyToPermission(req, res, url, body) {
475
+ const permissionReply = /^\/permission\/([^/]+)\/reply$/.exec(url);
476
+ // The reply resumes the held turn, then its own session.idle ends it, as a question reply does.
477
+ const id = decodeURIComponent(permissionReply[1]);
478
+ const reply = body ? JSON.parse(body).reply : undefined;
479
+ // P5: refuse the first answer; the ask stays pending, as a failed reply leaves it.
480
+ if (failPermissionReplies > 0 && pendingPermissionAsks.has(id)) {
481
+ failPermissionReplies -= 1;
482
+ res.writeHead(500, { 'content-type': 'application/json' });
483
+ res.end(JSON.stringify({ error: 'mock: reply refused' }));
484
+ return;
485
+ }
486
+ res.writeHead(200, { 'content-type': 'application/json' });
487
+ res.end('true');
488
+ if (pendingPermissionAsks.delete(id)) setTimeout(() => {
489
+ send({ type: 'permission.replied', properties: { sessionID: SESSION_ID, requestID: id, reply } });
490
+ send({ type: 'message.part.updated', properties: { part: {
491
+ id: `prt_permission_${id}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
492
+ text: `Approval response: ${JSON.stringify({ reply })}\n\nCEZ:DONE`, time: { start: 1, end: 2 },
493
+ } } });
494
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
495
+ }, 30);
496
+ return;
497
+ }
498
+
499
+ function replyToQuestion(req, res, url, body) {
500
+ // Native question reply resumes the held turn, then its own session.idle
501
+ // ends it (same SSE lifecycle shape as the baseline; never the HTTP ack).
502
+ const answered = pendingQuestions.some(question => question.id === 'q_mock_1');
503
+ pendingQuestions.length = 0;
504
+ if (answered && url.endsWith('/reply')) setTimeout(() => {
505
+ send({ type: 'message.part.updated', properties: { part: {
506
+ id: 'prt_answer', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: `human answer: ${body}${answerDone ? '\n\nCEZ:DONE' : ''}`,
507
+ } } });
508
+ // #505 review: CEZ_MOCK_OPENCODE_REPLY_HOLD_MS keeps the answered turn running.
509
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), Number(process.env.CEZ_MOCK_OPENCODE_REPLY_HOLD_MS ?? 0));
510
+ }, 30);
511
+ const acknowledge = () => { res.writeHead(200, { 'content-type': 'application/json' }); res.end('{}'); };
512
+ if (lateQuestionReply) setTimeout(acknowledge, 250);
513
+ else acknowledge();
514
+ return;
515
+ }
516
+
517
+ async function replyWithSteerTool(body) {
518
+ send({ type: 'message.updated', properties: { info: info({}) } });
519
+ send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_tool', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_steer', tool: 'bash', state: { status: 'running', input: { command: 'wait' } } } } });
520
+ await sleep(Number(process.env.CEZ_MOCK_STEER_MS ?? 600));
521
+ send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_tool', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_steer', tool: 'bash', state: { status: 'completed', input: { command: 'wait' }, output: 'waited' } } } });
522
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
523
+ return;
524
+ }
525
+
526
+ async function replyWithLateSteer(body) {
527
+ send({ type: 'message.updated', properties: { info: info({}) } });
528
+ send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_late', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'late window: final message already sent', time: { start: 1760000000000, end: 1760000000001 } } } });
529
+ turnLate = true;
530
+ await sleep(300);
531
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
532
+ return;
533
+ }
534
+
535
+ // The raw body is enough to spot a `mock:` marker — the prompt text is
536
+ // inside it whatever part shape the runner used to wrap it.
537
+ async function replyWithCezarTool(body) {
538
+ const { toolPrompt } = await import('./mock-ci-tool.mjs');
539
+ const text = await toolPrompt('opencode', JSON.parse(process.env.OPENCODE_CONFIG_CONTENT ?? '{}'), body);
540
+ // A completed assistant message, as on the other runners' wires, so its text (an ask line
541
+ // after the calls, say) reaches the turn end.
542
+ send({ type: 'message.updated', properties: { info: info({}) } });
543
+ send({ type: 'message.part.updated', properties: { part: { id: 'tool-result', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
544
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
545
+ return;
546
+ }
547
+
548
+ async function replyWithCiWait(body) {
549
+ const { ciPrompt } = await import('./mock-ci-tool.mjs');
550
+ const text = await ciPrompt('opencode', JSON.parse(process.env.OPENCODE_CONFIG_CONTENT ?? '{}'), body);
551
+ send({ type: 'message.part.updated', properties: { part: { id: 'ci-result', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text } } });
552
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
553
+ return;
554
+ }
555
+
556
+ async function replyWithMultiplePrs(body) {
557
+ const { multiPrText, unrelatedPr } = await import('./mock-multi-pr.mjs');
558
+ const text = multiPrText(JSON.parse(body).parts.map(part => part.text ?? '').join('\n'));
559
+ send({ type: 'message.updated', properties: { info: info({}) } });
560
+ send({ type: 'message.part.updated', properties: { part: { id: 'multi-reasoning', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'reasoning', text: unrelatedPr } } });
561
+ send({ type: 'message.part.updated', properties: { part: { id: 'multi-tool', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', tool: 'read', callID: 'multi-read', state: { status: 'completed', input: { filePath: 'other.txt' }, output: unrelatedPr, time: { start: 1, end: 2 } } } } });
562
+ for (const upto of [5, text.length]) send({ type: 'message.part.updated', properties: { part: { id: 'multi-message', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: text.slice(0, upto), ...(upto === text.length ? { time: { start: 1, end: 2 } } : {}) } } });
563
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
564
+ return;
565
+ }
566
+
567
+ async function replyWithTurnMessages(body) {
568
+ const { turnMessages } = await import('./mock-turn-messages.mjs');
569
+ send({ type: 'message.updated', properties: { info: info({}) } });
570
+ // `mock:channel-missing` → a CEZ marker message arrives as a text part without `time`, then a
571
+ // `time`-only update of the same part with no text (monitoring-channel-order.test.ts).
572
+ const missing = body.includes('mock:channel-missing');
573
+ for (const text of turnMessages(JSON.parse(body).parts.map(part => part.text ?? '').join('\n'))) {
574
+ if (missing) {
575
+ const id = `channel_${++echoSerial}`;
576
+ if (text.includes('CEZ:MONITORING') || text.includes('CEZ:DONE')) {
577
+ send({ type: 'message.part.updated', properties: { part: { id, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text } } });
578
+ send({ type: 'message.part.updated', properties: { part: { id, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', time: { start: 1, end: 2 } } } });
579
+ } else send({ type: 'message.part.updated', properties: { part: { id, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
580
+ continue;
298
581
  }
299
- if (req.method === 'POST' && url === '/session') {
300
- const parsed = body ? JSON.parse(body) : {};
301
- SESSION_ID = createSession(parsed);
302
- res.writeHead(200, { 'content-type': 'application/json' });
303
- res.end(JSON.stringify({
304
- id: SESSION_ID,
305
- title: parsed.title ?? 'cezar task',
306
- ...(Array.isArray(parsed.permission) ? { permission: parsed.permission } : {}),
307
- }));
308
- return;
582
+ send({ type: 'message.part.updated', properties: { part: { id: `prt_multi_${++echoSerial}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
583
+ }
584
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
585
+ return;
586
+ }
587
+
588
+ async function replyWithEcho(body) {
589
+ send({ type: 'message.updated', properties: { info: info({}) } });
590
+ send({ type: 'message.part.updated', properties: { part: {
591
+ id: `prt_echo_${++echoSerial}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
592
+ text: JSON.parse(body).parts.map(part => part.text ?? '').join('\n'),
593
+ } } });
594
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
595
+ return;
596
+ }
597
+
598
+ // SSE child attribution from __fixtures__/opencode/subtask-nested.ndjson.
599
+ async function replyWithParkedSubagent(body) {
600
+ send({ type: 'message.updated', properties: { info: info({}) } });
601
+ send({ type: 'message.part.updated', properties: { part: { id: 'park-task', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask', prompt: 'Review', description: 'Review', agent: 'general' } } });
602
+ send({ type: 'message.updated', properties: { info: { ...info({}), id: 'park-child-msg', sessionID: 'park-child', parentID: SESSION_ID, mode: 'subagent' } } });
603
+ send({ type: 'message.part.updated', properties: { part: { id: 'early-child', messageID: 'park-child-msg', sessionID: 'park-child', type: 'text', text: 'Child review started.', time: { start: 1, end: 2 } } } });
604
+ send({ type: 'message.part.updated', properties: { part: { id: 'park-parent', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Watching the child.\nCEZ:MONITORING', time: { start: 1, end: 2 } } } });
605
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
606
+ await afterParityPark(body);
607
+ send({ type: 'message.part.updated', properties: { part: { id: 'late-read', messageID: 'park-child-msg', sessionID: 'park-child', type: 'tool', tool: 'read', state: { status: 'running', input: { path: 'README.md' } } } } });
608
+ send({ type: 'message.part.updated', properties: { part: { id: 'late-read', messageID: 'park-child-msg', sessionID: 'park-child', type: 'tool', tool: 'read', state: { status: 'completed', input: { path: 'README.md' }, output: 'Read the file.' } } } });
609
+ send({ type: 'message.part.updated', properties: { part: { id: 'late-child-text', messageID: 'park-child-msg', sessionID: 'park-child', type: 'text', text: 'Late child text.', time: { start: 3, end: 4 } } } });
610
+ // Closed child scopes are ignored. This passive usage snapshot proves the
611
+ // preceding SSE frames have reached the runner before the test observes it.
612
+ send({ type: 'message.updated', properties: { info: info({ tokens: { input: 424240, output: 2, reasoning: 0, cache: { read: 0, write: 0 } } }) } });
613
+ return;
614
+ }
615
+
616
+ async function replyWithSubagent(body) {
617
+ // Wire shape from `__fixtures__/opencode/subtask-nested.ndjson`: a
618
+ // `subtask` part on the parent message, then a child message whose info
619
+ // carries `parentID`, its parts, and the CHILD's own `session.idle`.
620
+ // That child idle arrives first on purpose — the parent turn has to
621
+ // survive it and close only on its own (#5, #600; harness parity S9).
622
+ send({ type: 'message.updated', properties: { info: info({}) } });
623
+ send({ type: 'message.part.updated', properties: { part: {
624
+ id: 'prt_mock_subt', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask',
625
+ prompt: 'Trace every caller of resolveSession', description: 'Trace resolveSession callers',
626
+ agent: 'general',
627
+ } } });
628
+ send({ type: 'message.updated', properties: { info: {
629
+ ...info({}), id: 'msg_mock_child', sessionID: 'ses_mock_child',
630
+ parentID: SESSION_ID, mode: 'subagent',
631
+ } } });
632
+ send({ type: 'message.part.updated', properties: { part: {
633
+ id: 'prt_mock_ctxt', messageID: 'msg_mock_child', sessionID: 'ses_mock_child',
634
+ type: 'text', text: 'Two callers: router.ts and session.ts.',
635
+ time: { start: 1760000008800, end: 1760000008900 },
636
+ } } });
637
+ send({ type: 'session.idle', properties: { sessionID: 'ses_mock_child' } });
638
+ // A second child is still running when the parent declares monitoring.
639
+ send({ type: 'message.part.updated', properties: { part: {
640
+ id: 'prt_mock_late_subt', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask',
641
+ prompt: 'Review the callers', description: 'Review callers', agent: 'general',
642
+ } } });
643
+ send({ type: 'message.updated', properties: { info: {
644
+ ...info({}), id: 'msg_mock_late_child', sessionID: 'ses_mock_late_child',
645
+ parentID: SESSION_ID, mode: 'subagent',
646
+ } } });
647
+ setTimeout(() => {
648
+ send({ type: 'message.part.updated', properties: { part: {
649
+ id: 'prt_mock_ptxt', messageID: MESSAGE_ID, sessionID: SESSION_ID,
650
+ type: 'text', text: 'Still working after the sub-agent.\nCEZ:MONITORING',
651
+ time: { start: 1760000009000, end: 1760000009200 },
652
+ } } });
653
+ // #149: foreign-session text after the parent's marker must stay v2-only.
654
+ send({ type: 'message.part.updated', properties: { part: {
655
+ id: 'prt_mock_late_child', messageID: 'msg_mock_late_child', sessionID: 'ses_mock_late_child',
656
+ type: 'text', text: 'Child review finished.',
657
+ time: { start: 1760000009200, end: 1760000009300 },
658
+ } } });
659
+ send({ type: 'message.updated', properties: { info: info({
660
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
661
+ }) } });
662
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
663
+ }, 60);
664
+ return;
665
+ }
666
+
667
+ async function replyWithSplitText(body) {
668
+ // OpenCode streams a text part as successive GROWING snapshots of the
669
+ // same part id; only the final one carries `time.end`. A runner emitting
670
+ // one v1 `text` per snapshot publishes the torn `…CEZ:` prefix, which is
671
+ // #2 on opencode's wire (harness parity S8).
672
+ const full = 'parity split text\n\nCEZ:MONITORING';
673
+ send({ type: 'message.updated', properties: { info: info({}) } });
674
+ for (const upto of [17, 24, full.length]) {
675
+ send({ type: 'message.part.updated', properties: { part: {
676
+ id: 'prt_mock_split', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
677
+ text: full.slice(0, upto),
678
+ ...(upto === full.length ? { time: { start: 1760000000500, end: 1760000000600 } } : {}),
679
+ } } });
680
+ }
681
+ send({ type: 'message.updated', properties: { info: info({
682
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
683
+ }) } });
684
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
685
+ return;
686
+ }
687
+
688
+ async function replyAutonomously(body) {
689
+ const { autonomousReply } = await import('./mock-autonomous.mjs');
690
+ send({ type: 'message.updated', properties: { info: info({}) } });
691
+ send({ type: 'message.part.updated', properties: { part: { id: `autonomous-${++autonomousTurn}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: autonomousReply(JSON.parse(body).parts.map(part => part.text ?? '').join('\n')), time: { start: 1, end: 2 } } } });
692
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
693
+ return;
694
+ }
695
+
696
+ async function replyWithSilentTail(body) {
697
+ const silent = await import('./mock-silent-tail.mjs');
698
+ const prompt = (() => { try { return JSON.parse(body).parts.map(part => part.text ?? '').join('\n'); } catch { return body; } })();
699
+ silent.noteSilentTailPrompt(prompt);
700
+ if (silent.isFinalMessageNudge(prompt)) { await replyWithFinalNudge(silent); return; }
701
+ if (silent.isSilentTailScenario(prompt)) {
702
+ send({ type: 'message.updated', properties: { info: info({}) } });
703
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-open', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.SILENT_TAIL_OPENING, time: { start: 1, end: 2 } } } });
704
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-gh', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_silent_gh', tool: 'bash', state: { status: 'completed', input: { command: 'gh issue create' }, output: 'created', time: { start: 1, end: 2 } } } } });
705
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-rsn', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'reasoning', text: silent.SILENT_TAIL_REASONING, time: { start: 1, end: 2 } } } });
706
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
707
+ return;
708
+ }
709
+ if (silent.isToolTailScenario(prompt)) {
710
+ send({ type: 'message.updated', properties: { info: info({}) } });
711
+ send({ type: 'message.part.updated', properties: { part: { id: 'tool-tail-open', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.TOOL_TAIL_OPENING, time: { start: 1, end: 2 } } } });
712
+ send({ type: 'message.part.updated', properties: { part: { id: 'tool-tail-git', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_tool_tail', tool: 'bash', state: { status: 'completed', input: { command: 'git status --short' }, output: ' M src/example.ts', time: { start: 1, end: 2 } } } } });
713
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
714
+ return;
715
+ }
716
+ return false;
717
+ }
718
+
719
+ async function replyWithFinalNudge(silent) {
720
+ const kind = silent.finalMessageNudgeKind();
721
+ send({ type: 'message.updated', properties: { info: info({}) } });
722
+ if (kind === 'silent') {
723
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-rsn', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'reasoning', text: silent.SILENT_TAIL_REASONING, time: { start: 1, end: 2 } } } });
724
+ } else {
725
+ if (kind === 'slow-done') {
726
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-prefix', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.SLOW_DONE_PREFIX, time: { start: 1, end: 2 } } } });
727
+ await silent.sleep(silent.SLOW_DONE_TAIL_MS);
309
728
  }
310
- const sessionPrompt = req.method === 'POST' && /^\/session\/([^/]+)\/(prompt_async|message)$/.exec(url);
311
- if (sessionPrompt && getSession(sessionPrompt[1])) {
312
- SESSION_ID = sessionPrompt[1];
313
- try {
314
- const text = body ? JSON.parse(body).parts?.map(part => part.text ?? '').join('\n') ?? '' : '';
315
- if (text) addPrompt(SESSION_ID, text);
316
- } catch {
317
- // Keep the scripted turn even when the body is not JSON.
318
- }
319
- if (body.includes('mock:crash-stderr-pre-ack')) {
320
- const { crashWithStderr } = await import('./mock-runner-crash.mjs');
321
- crashWithStderr(body);
322
- return;
323
- }
324
- if (body.includes('mock:reject-agent-post')) {
325
- res.writeHead(503, { 'content-type': 'application/json' });
326
- res.end(JSON.stringify({ error: 'agent prompt rejected' }));
327
- return;
328
- }
329
- if (turnActive && url.endsWith('/prompt_async')) {
330
- const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
331
- const userId = `msg_steer_user_${++steerSerial}`;
332
- // #505 review: acknowledge a steer later than the runner's lost-wake window.
333
- const ackDelay = Number(process.env.CEZ_MOCK_OPENCODE_STEER_ACK_MS ?? 0);
334
- if (ackDelay > 0) setTimeout(() => { res.writeHead(204); res.end(); }, ackDelay);
335
- else { res.writeHead(204); res.end(); }
336
- send({ type: 'message.updated', properties: { info: { id: userId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
337
- send({ type: 'message.part.updated', properties: { part: { id: `prt_${userId}`, messageID: userId, sessionID: SESSION_ID, type: 'text', text } } });
338
- steers.push({ userId, text });
339
- return;
340
- }
341
- turnActive = true;
342
- // `prompt_async` semantics: acknowledge now, stream the turn over SSE.
343
- res.writeHead(200, { 'content-type': 'application/json' });
344
- // #426: a nudged turn can finish on SSE before its HTTP acceptance arrives.
345
- // Force that ordering in the cap scenario; the first opening stays ordinary.
346
- if (autonomousReadinessIdle && body.includes('Continue working autonomously until the task is fully complete.')) {
347
- // Keep the portable answer HTTP request unacknowledged while its SSE turn finishes.
348
- } else if (autonomousCap && body.includes('Continue working autonomously until the task is fully complete.')) {
349
- setTimeout(() => res.end(JSON.stringify({ info: info({}), parts: [] })), 100);
350
- } else if (body.includes('f7-delay-ack')) {
351
- // F7: SSE turn-end before HTTP ACK so parkAfterAck applies the silent-tail nudge.
352
- setTimeout(() => res.end(JSON.stringify({ info: info({}), parts: [] })), 250);
353
- } else {
354
- res.end(JSON.stringify({ info: info({}), parts: [] }));
355
- }
356
- if (body.includes('mock:autonomous-readiness-idle')) autonomousReadinessIdle = true;
357
- if (body.includes('mock:autonomous-cap') || body.includes('mock:autonomous-ask-cap')) autonomousCap = true;
358
- if (body.includes('mock:silent-tail') || body.includes('Your last turn ended without a message to the user.')) {
359
- const silent = await import('./mock-silent-tail.mjs');
360
- const prompt = (() => { try { return JSON.parse(body).parts.map(part => part.text ?? '').join('\n'); } catch { return body; } })();
361
- silent.noteSilentTailPrompt(prompt);
362
- if (silent.isAckOnlyNudge(prompt)) {
363
- const beats = setInterval(() => { if (sse) sse.write(': heartbeat\n\n'); }, 50);
364
- beats.unref?.();
365
- return;
366
- }
367
- if (silent.isLateNudge(prompt)) await silent.sleep(silent.LATE_REPLY_MS);
368
- }
369
- if (url.endsWith('/prompt_async')) {
370
- currentUserId = `msg_user_${++steerSerial}`;
371
- const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
372
- send({ type: 'message.updated', properties: { info: { id: currentUserId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
373
- send({ type: 'message.part.updated', properties: { part: { id: `prt_${currentUserId}`, messageID: currentUserId, sessionID: SESSION_ID, type: 'text', text } } });
374
- }
375
- if (body.includes('mock:crash-stderr')) {
376
- const { crashWithStderr } = await import('./mock-runner-crash.mjs');
377
- if (body.includes('mock:crash-stderr-clean')) crashWithStderr(body);
378
- else { setTimeout(() => crashWithStderr(body), 30); return; }
379
- }
380
- if (body.includes('mock:steer-tool')) {
381
- send({ type: 'message.updated', properties: { info: info({}) } });
382
- send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_tool', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_steer', tool: 'bash', state: { status: 'running', input: { command: 'wait' } } } } });
383
- await sleep(Number(process.env.CEZ_MOCK_STEER_MS ?? 600));
384
- send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_tool', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_steer', tool: 'bash', state: { status: 'completed', input: { command: 'wait' }, output: 'waited' } } } });
385
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
386
- return;
387
- }
388
- if (body.includes('mock:steer-late')) {
389
- send({ type: 'message.updated', properties: { info: info({}) } });
390
- send({ type: 'message.part.updated', properties: { part: { id: 'prt_steer_late', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'late window: final message already sent', time: { start: 1760000000000, end: 1760000000001 } } } });
391
- turnLate = true;
392
- await sleep(300);
393
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
394
- return;
395
- }
396
- // The raw body is enough to spot a `mock:` marker — the prompt text is
397
- // inside it whatever part shape the runner used to wrap it.
398
- if (body.includes('mock:ci-wait')) {
399
- const { ciPrompt } = await import('./mock-ci-tool.mjs');
400
- const text = await ciPrompt('opencode', JSON.parse(process.env.OPENCODE_CONFIG_CONTENT ?? '{}'), body);
401
- send({ type: 'message.part.updated', properties: { part: { id: 'ci-result', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text } } });
402
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
403
- return;
404
- }
405
- if (body.includes('mock:turn-messages:')) {
406
- const { turnMessages } = await import('./mock-turn-messages.mjs');
407
- send({ type: 'message.updated', properties: { info: info({}) } });
408
- for (const text of turnMessages(JSON.parse(body).parts.map(part => part.text ?? '').join('\n'))) {
409
- send({ type: 'message.part.updated', properties: { part: { id: `prt_multi_${++echoSerial}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
410
- }
411
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
412
- return;
413
- }
414
- if (body.includes('mock:agent-echo')) {
415
- send({ type: 'message.updated', properties: { info: info({}) } });
416
- send({ type: 'message.part.updated', properties: { part: {
417
- id: `prt_echo_${++echoSerial}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
418
- text: JSON.parse(body).parts.map(part => part.text ?? '').join('\n'),
419
- } } });
420
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
421
- return;
422
- }
423
- // SSE child attribution from __fixtures__/opencode/subtask-nested.ndjson.
424
- if (body.includes('mock:subagent-after-park')) {
425
- send({ type: 'message.updated', properties: { info: info({}) } });
426
- send({ type: 'message.part.updated', properties: { part: { id: 'park-task', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask', prompt: 'Review', description: 'Review', agent: 'general' } } });
427
- send({ type: 'message.updated', properties: { info: { ...info({}), id: 'park-child-msg', sessionID: 'park-child', parentID: SESSION_ID, mode: 'subagent' } } });
428
- send({ type: 'message.part.updated', properties: { part: { id: 'early-child', messageID: 'park-child-msg', sessionID: 'park-child', type: 'text', text: 'Child review started.', time: { start: 1, end: 2 } } } });
429
- send({ type: 'message.part.updated', properties: { part: { id: 'park-parent', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Watching the child.\nCEZ:MONITORING', time: { start: 1, end: 2 } } } });
430
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
431
- await afterParityPark(body);
432
- send({ type: 'message.part.updated', properties: { part: { id: 'late-read', messageID: 'park-child-msg', sessionID: 'park-child', type: 'tool', tool: 'read', state: { status: 'running', input: { path: 'README.md' } } } } });
433
- send({ type: 'message.part.updated', properties: { part: { id: 'late-read', messageID: 'park-child-msg', sessionID: 'park-child', type: 'tool', tool: 'read', state: { status: 'completed', input: { path: 'README.md' }, output: 'Read the file.' } } } });
434
- send({ type: 'message.part.updated', properties: { part: { id: 'late-child-text', messageID: 'park-child-msg', sessionID: 'park-child', type: 'text', text: 'Late child text.', time: { start: 3, end: 4 } } } });
435
- // Closed child scopes are ignored. This passive usage snapshot proves the
436
- // preceding SSE frames have reached the runner before the test observes it.
437
- send({ type: 'message.updated', properties: { info: info({ tokens: { input: 424240, output: 2, reasoning: 0, cache: { read: 0, write: 0 } } }) } });
438
- return;
439
- }
440
- if (body.includes('mock:subagent')) {
441
- // Wire shape from `__fixtures__/opencode/subtask-nested.ndjson`: a
442
- // `subtask` part on the parent message, then a child message whose info
443
- // carries `parentID`, its parts, and the CHILD's own `session.idle`.
444
- // That child idle arrives first on purpose — the parent turn has to
445
- // survive it and close only on its own (#5, #600; harness parity S9).
446
- send({ type: 'message.updated', properties: { info: info({}) } });
447
- send({ type: 'message.part.updated', properties: { part: {
448
- id: 'prt_mock_subt', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask',
449
- prompt: 'Trace every caller of resolveSession', description: 'Trace resolveSession callers',
450
- agent: 'general',
451
- } } });
452
- send({ type: 'message.updated', properties: { info: {
453
- ...info({}), id: 'msg_mock_child', sessionID: 'ses_mock_child',
454
- parentID: SESSION_ID, mode: 'subagent',
455
- } } });
456
- send({ type: 'message.part.updated', properties: { part: {
457
- id: 'prt_mock_ctxt', messageID: 'msg_mock_child', sessionID: 'ses_mock_child',
458
- type: 'text', text: 'Two callers: router.ts and session.ts.',
459
- time: { start: 1760000008800, end: 1760000008900 },
460
- } } });
461
- send({ type: 'session.idle', properties: { sessionID: 'ses_mock_child' } });
462
- // A second child is still running when the parent declares monitoring.
463
- send({ type: 'message.part.updated', properties: { part: {
464
- id: 'prt_mock_late_subt', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask',
465
- prompt: 'Review the callers', description: 'Review callers', agent: 'general',
466
- } } });
467
- send({ type: 'message.updated', properties: { info: {
468
- ...info({}), id: 'msg_mock_late_child', sessionID: 'ses_mock_late_child',
469
- parentID: SESSION_ID, mode: 'subagent',
470
- } } });
471
- setTimeout(() => {
472
- send({ type: 'message.part.updated', properties: { part: {
473
- id: 'prt_mock_ptxt', messageID: MESSAGE_ID, sessionID: SESSION_ID,
474
- type: 'text', text: 'Still working after the sub-agent.\nCEZ:MONITORING',
475
- time: { start: 1760000009000, end: 1760000009200 },
476
- } } });
477
- // #149: foreign-session text after the parent's marker must stay v2-only.
478
- send({ type: 'message.part.updated', properties: { part: {
479
- id: 'prt_mock_late_child', messageID: 'msg_mock_late_child', sessionID: 'ses_mock_late_child',
480
- type: 'text', text: 'Child review finished.',
481
- time: { start: 1760000009200, end: 1760000009300 },
482
- } } });
483
- send({ type: 'message.updated', properties: { info: info({
484
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
485
- }) } });
486
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
487
- }, 60);
488
- return;
489
- }
490
- // #723: native GET /skill diagnostic captured on 1.18.33; upstream
491
- // packages/opencode/src/skill/index.ts:96-111 skips the unreadable skill.
492
- if (body.includes('mock:skill-warning')) {
493
- for (const root of ['.claude', '.agents']) send({ type: 'session.error', properties: {
494
- error: { name: 'UnknownError', data: { message: `Failed to parse skill /home/agent/${root}/skills/pen-design/SKILL.md` } },
495
- } });
496
- }
497
- if (body.includes('mock:unscoped-provider-failure') || body.includes('mock:scoped-skill-failure')) {
498
- send({ type: 'session.error', properties: {
499
- ...(body.includes('mock:scoped-skill-failure') ? { sessionID: SESSION_ID } : {}),
500
- error: { name: 'UnknownError', data: { message: body.includes('mock:scoped-skill-failure')
501
- ? 'Failed to parse skill /skills/required/SKILL.md' : 'provider unavailable — retry after restoring service' } },
502
- } });
503
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
504
- return;
505
- }
506
- if (body.includes('mock:provider-error-early')) {
507
- // #505 review: the provider rejects before any assistant message exists.
508
- send({ type: 'session.error', properties: { sessionID: SESSION_ID, error: { name: 'ProviderAuthError', data: { message: 'API key expired' } } } });
509
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
510
- return;
511
- }
512
- if (body.includes('mock:provider-error')) {
513
- // The session-level error frame, wire shape copied verbatim from
514
- // `__fixtures__/opencode/session-error.ndjson`. #53: a bare upstream
515
- // rejection used to read like a missing executable, and the run parked
516
- // as "Needs You" rather than failing (harness parity S7).
517
- send({ type: 'message.updated', properties: { info: info({}) } });
518
- send({ type: 'message.part.updated', properties: { part: {
519
- id: 'prt_mock_perr', messageID: MESSAGE_ID, sessionID: SESSION_ID,
520
- type: 'text', text: 'Starting the review',
521
- } } });
522
- send({ type: 'session.error', properties: { sessionID: SESSION_ID, error: {
523
- name: 'ProviderAuthError',
524
- data: { message: 'API key expired — run `opencode auth login`' },
525
- } } });
526
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
527
- return;
528
- }
529
- if (body.includes('mock:split-text')) {
530
- // OpenCode streams a text part as successive GROWING snapshots of the
531
- // same part id; only the final one carries `time.end`. A runner emitting
532
- // one v1 `text` per snapshot publishes the torn `…CEZ:` prefix, which is
533
- // #2 on opencode's wire (harness parity S8).
534
- const full = 'parity split text\n\nCEZ:MONITORING';
535
- send({ type: 'message.updated', properties: { info: info({}) } });
536
- for (const upto of [17, 24, full.length]) {
537
- send({ type: 'message.part.updated', properties: { part: {
538
- id: 'prt_mock_split', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
539
- text: full.slice(0, upto),
540
- ...(upto === full.length ? { time: { start: 1760000000500, end: 1760000000600 } } : {}),
541
- } } });
542
- }
543
- send({ type: 'message.updated', properties: { info: info({
544
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
545
- }) } });
546
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
547
- return;
548
- }
549
- if (body.includes('mock:autonomous') || body.includes('Continue working autonomously until the task is fully complete.')) {
550
- const { autonomousReply } = await import('./mock-autonomous.mjs');
551
- send({ type: 'message.updated', properties: { info: info({}) } });
552
- send({ type: 'message.part.updated', properties: { part: { id: `autonomous-${++autonomousTurn}`, messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: autonomousReply(JSON.parse(body).parts.map(part => part.text ?? '').join('\n')), time: { start: 1, end: 2 } } } });
553
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
554
- return;
555
- }
556
- if (body.includes('mock:silent-tail') || body.includes('mock:tool-tail') || body.includes('Your last turn ended without a message to the user.')) {
557
- const silent = await import('./mock-silent-tail.mjs');
558
- const prompt = (() => { try { return JSON.parse(body).parts.map(part => part.text ?? '').join('\n'); } catch { return body; } })();
559
- silent.noteSilentTailPrompt(prompt);
560
- if (silent.isFinalMessageNudge(prompt)) {
561
- const kind = silent.finalMessageNudgeKind();
562
- send({ type: 'message.updated', properties: { info: info({}) } });
563
- if (kind === 'silent') {
564
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-rsn', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'reasoning', text: silent.SILENT_TAIL_REASONING, time: { start: 1, end: 2 } } } });
565
- } else {
566
- if (kind === 'slow-done') {
567
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-prefix', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.SLOW_DONE_PREFIX, time: { start: 1, end: 2 } } } });
568
- await silent.sleep(silent.SLOW_DONE_TAIL_MS);
569
- }
570
- const text = kind === 'standing' ? silent.FINAL_MESSAGE_STANDING : silent.SILENT_TAIL_DONE;
571
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
572
- }
573
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
574
- return;
575
- }
576
- if (silent.isSilentTailScenario(prompt)) {
577
- send({ type: 'message.updated', properties: { info: info({}) } });
578
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-open', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.SILENT_TAIL_OPENING, time: { start: 1, end: 2 } } } });
579
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-gh', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_silent_gh', tool: 'bash', state: { status: 'completed', input: { command: 'gh issue create' }, output: 'created', time: { start: 1, end: 2 } } } } });
580
- send({ type: 'message.part.updated', properties: { part: { id: 'silent-rsn', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'reasoning', text: silent.SILENT_TAIL_REASONING, time: { start: 1, end: 2 } } } });
581
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
582
- return;
583
- }
584
- if (silent.isToolTailScenario(prompt)) {
585
- send({ type: 'message.updated', properties: { info: info({}) } });
586
- send({ type: 'message.part.updated', properties: { part: { id: 'tool-tail-open', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: silent.TOOL_TAIL_OPENING, time: { start: 1, end: 2 } } } });
587
- send({ type: 'message.part.updated', properties: { part: { id: 'tool-tail-git', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'tool', callID: 'call_tool_tail', tool: 'bash', state: { status: 'completed', input: { command: 'git status --short' }, output: ' M src/example.ts', time: { start: 1, end: 2 } } } } });
588
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
589
- return;
590
- }
591
- }
592
- // #401: one completed text snapshot, without an earlier streaming part.
593
- if (body.includes('mock:ask-snapshot')) {
594
- send({ type: 'message.updated', properties: { info: info({}) } });
595
- send({ type: 'message.part.updated', properties: { part: { id: 'ask-snapshot', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: body.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"}]}]}', time: { start: 1, end: 2 } } } });
596
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
597
- return;
598
- }
599
- if (body.includes('mock:ask-prose')) {
600
- send({ type: 'message.updated', properties: { info: info({}) } });
601
- send({ type: 'message.part.updated', properties: { part: { id: 'ask-prose', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Use `CEZ:ASK {"questions":[]}` in your reply.\n> CEZ:ASK {not valid json', time: { start: 1, end: 2 } } } });
602
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
603
- return;
604
- }
605
- if (body.includes('mock:ask-bad')) {
606
- // Malformed: `questions` is not an array, so the runner must REJECT the
607
- // native question and finish the turn rather than wait for an answer to
608
- // a card that can never render (#6's rejection path, harness parity R4).
609
- send({ type: 'message.updated', properties: { info: info({}) } });
610
- pendingQuestions.push({ id: 'q_mock_bad', sessionID: SESSION_ID });
611
- sendQuestionPart({ questions: 'not-an-array' });
612
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 60);
613
- return;
614
- }
615
- if (body.includes('mock:ask')) {
616
- lateQuestionReply = body.includes('mock:ask-reply-late');
617
- answerDone = body.includes('mock:ask-reply-late-done') || body.includes('mock:resume-done');
618
- // The native question tool (#6). No `session.idle` follows: a real ask
619
- // holds the turn open until the answer is routed back.
620
- send({ type: 'message.updated', properties: { info: info({}) } });
621
- pendingQuestions.push({ id: 'q_mock_1', sessionID: SESSION_ID });
622
- sendQuestionPart({ questions: [{
623
- header: 'Library',
624
- question: 'Which test library?',
625
- options: [
626
- { label: 'Vitest', description: 'Use the existing test runner' },
627
- { label: 'Node test', description: 'Use node:test' },
628
- ],
629
- }] });
630
- return;
631
- }
632
- if (body.includes('mock:hold-done')) {
633
- // Pause, then CEZ:DONE, so a mid-turn sendMessage can queue behind the
634
- // running turn and race the close (#486).
635
- await waitForMockRelease(400);
636
- send({ type: 'message.updated', properties: { info: info({}) } });
637
- send({ type: 'message.part.updated', properties: { part: {
638
- id: 'prt_mock_hold_done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
639
- text: 'parity hold-done: content after the pause\n\nCEZ:DONE',
640
- time: { start: 1760000000500, end: 1760000000600 },
641
- } } });
642
- send({ type: 'message.updated', properties: { info: info({
643
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
644
- }) } });
645
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
646
- return;
647
- }
648
- if (body.includes('mock:done')) {
649
- // Declares the task complete so the run reaches cezar's review gate; a
650
- // markerless turn-end correctly parks as `waiting` instead (harness
651
- // parity R1).
652
- send({ type: 'message.updated', properties: { info: info({}) } });
653
- send({ type: 'message.part.updated', properties: { part: {
654
- id: 'prt_mock_done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
655
- text: 'parity done: the task is complete\n\nCEZ:DONE',
656
- time: { start: 1760000000500, end: 1760000000600 },
657
- } } });
658
- send({ type: 'message.updated', properties: { info: info({
659
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
660
- }) } });
661
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
662
- return;
663
- }
664
- if (body.includes('mock:hold-ask')) {
665
- // Portable CEZ:ASK after a pause, so a mid-turn sendMessage can queue
666
- // and then race the park (run.ts must discard that waiter).
667
- await waitForMockRelease(250);
668
- const ask = 'Pick one.\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which date library should I standardize on?","options":[{"label":"date-fns","description":"Tree-shakeable, functional"},{"label":"Luxon","description":"Immutable, tz-aware"}]}]}';
669
- send({ type: 'message.updated', properties: { info: info({}) } });
670
- send({ type: 'message.part.updated', properties: { part: {
671
- id: 'prt_mock_hold_ask', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
672
- text: ask,
673
- time: { start: 1760000000500, end: 1760000000600 },
674
- } } });
675
- send({ type: 'message.updated', properties: { info: info({
676
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
677
- }) } });
678
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
679
- return;
680
- }
681
- if (watchdogStall(body)) return;
682
- if (body.includes('mock:busy-progress')) {
683
- let ticks = 0;
684
- const timer = setInterval(() => {
685
- send({ type: 'server.heartbeat', properties: {} });
686
- if (++ticks === 24) { clearInterval(timer); send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }); }
687
- }, 100);
688
- return;
689
- }
690
- if (body.includes('mock:hold-gated')) {
691
- await waitForMockRelease(250);
692
- const gated = 'parity hold-gated: content after the pause';
693
- send({ type: 'message.updated', properties: { info: info({}) } });
694
- send({ type: 'message.part.updated', properties: { part: {
695
- id: 'prt_mock_hold_gated', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: gated,
696
- } } });
697
- send({ type: 'message.updated', properties: { info: info({
698
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
699
- }) } });
700
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
701
- return;
702
- }
703
- if (body.includes('mock:hold')) {
704
- // The response above is the ack (this is the #4 wire: `prompt_async`
705
- // resolves immediately, the turn arrives over SSE afterwards). Holding
706
- // the content behind it is what makes harness parity S2 meaningful.
707
- setTimeout(() => {
708
- const held = 'parity hold: content after the pause';
709
- send({ type: 'message.updated', properties: { info: info({}) } });
710
- send({ type: 'message.part.updated', properties: { part: {
711
- id: 'prt_mock_hold', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: held,
712
- } } });
713
- send({ type: 'message.updated', properties: { info: info({
714
- cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
715
- }) } });
716
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
717
- }, 250);
718
- return;
719
- }
729
+ const text = kind === 'standing' ? silent.FINAL_MESSAGE_STANDING : silent.SILENT_TAIL_DONE;
730
+ send({ type: 'message.part.updated', properties: { part: { id: 'silent-nudge-done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text, time: { start: 1, end: 2 } } } });
731
+ }
732
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
733
+ return;
734
+ }
735
+
736
+ async function replyWithSettledApproval(body) {
737
+ // P3/P4: OpenCode settles the ask itself, or the turn ends around it, once the test releases it.
738
+ const resolved = body.includes('mock:approval-resolved');
739
+ send({ type: 'message.updated', properties: { info: info({}) } });
740
+ sendPermissionAsk('per_mock_1', 'bash', ['git status --short'], ['git status *']);
741
+ await waitForMockRelease();
742
+ pendingPermissionAsks.delete('per_mock_1');
743
+ if (resolved) send({ type: 'permission.replied', properties: { sessionID: SESSION_ID, requestID: 'per_mock_1', reply: 'once' } });
744
+ textPart('prt_approval_settled', resolved ? 'Native approval settled without a cockpit answer.\n\nCEZ:DONE'
745
+ : 'Turn ended with the approval still pending.\n\nCEZ:DONE');
746
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
747
+ return;
748
+ }
749
+
750
+ async function replyWithFailedApprovalReply(body) {
751
+ failPermissionReplies = 1;
752
+ send({ type: 'message.updated', properties: { info: info({}) } });
753
+ sendPermissionAsk('per_mock_1', 'bash', ['git status --short'], ['git status *']);
754
+ return;
755
+ }
756
+
757
+ async function replyWithMalformedPermission(body) {
758
+ // P6: `patterns` must be an array; cezar rejects the ask by its id.
759
+ send({ type: 'message.updated', properties: { info: info({}) } });
760
+ pendingPermissionAsks.add('per_mock_bad');
761
+ send({ type: 'permission.asked', properties: {
762
+ id: 'per_mock_bad', sessionID: SESSION_ID, permission: 'bash', patterns: 'git status --short', metadata: {},
763
+ always: ['git status *'], tool: { messageID: MESSAGE_ID, callID: 'call_mock_bash' },
764
+ } });
765
+ return;
766
+ }
767
+
768
+ async function replyWithClassifyingPermission(body) {
769
+ classifyPending = true;
770
+ send({ type: 'message.updated', properties: { info: info({}) } });
771
+ sendPermissionAsk('per_mock_classify', 'bash', ['git status --short'], ['git status *']);
772
+ return;
773
+ }
774
+
775
+ async function replyWithLatePermission(body) {
776
+ // P8: the ask lands right after the idle, inside the runner's auto-end delay. As in real
777
+ // OpenCode, it comes from a run the server started, which the answer resumes and its idle ends.
778
+ send({ type: 'message.updated', properties: { info: info({}) } });
779
+ textPart('prt_late_done', 'The turn finished before the late request.\n\nCEZ:DONE');
780
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
781
+ sendPermissionAsk('per_mock_late', 'bash', ['git push'], ['git push *']);
782
+ return;
783
+ }
784
+
785
+ async function replyWithDefaultPermission(body) {
786
+ send({ type: 'message.updated', properties: { info: info({}) } });
787
+ sendPermissionAsk('per_mock_default', 'external_directory', ['/outside/*'], ['/outside/*']);
788
+ return;
789
+ }
790
+
791
+ async function replyWithApproval(body) {
792
+ send({ type: 'message.updated', properties: { info: info({}) } });
793
+ sendPermissionAsk('per_mock_1', 'bash', ['git status --short'], ['git status *']);
794
+ return;
795
+ }
796
+
797
+ // #401: one completed text snapshot, without an earlier streaming part.
798
+ async function replyWithAskSnapshot(body) {
799
+ send({ type: 'message.updated', properties: { info: info({}) } });
800
+ send({ type: 'message.part.updated', properties: { part: { id: 'ask-snapshot', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: body.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"}]}]}', time: { start: 1, end: 2 } } } });
801
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
802
+ return;
803
+ }
804
+
805
+ async function replyWithAskProse(body) {
806
+ send({ type: 'message.updated', properties: { info: info({}) } });
807
+ send({ type: 'message.part.updated', properties: { part: { id: 'ask-prose', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Use `CEZ:ASK {"questions":[]}` in your reply.\n> CEZ:ASK {not valid json', time: { start: 1, end: 2 } } } });
808
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
809
+ return;
810
+ }
811
+
812
+ async function replyWithMalformedAsk(body) {
813
+ // Malformed: `questions` is not an array, so the runner must REJECT the
814
+ // native question and finish the turn rather than wait for an answer to
815
+ // a card that can never render (#6's rejection path, harness parity R4).
816
+ send({ type: 'message.updated', properties: { info: info({}) } });
817
+ pendingQuestions.push({ id: 'q_mock_bad', sessionID: SESSION_ID });
818
+ sendQuestionPart({ questions: 'not-an-array' });
819
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 60);
820
+ return;
821
+ }
822
+
823
+ async function replyWithAsk(body) {
824
+ lateQuestionReply = body.includes('mock:ask-reply-late');
825
+ answerDone = body.includes('mock:ask-reply-late-done') || body.includes('mock:resume-done');
826
+ // The native question tool (#6). No `session.idle` follows: a real ask
827
+ // holds the turn open until the answer is routed back.
828
+ send({ type: 'message.updated', properties: { info: info({}) } });
829
+ pendingQuestions.push({ id: 'q_mock_1', sessionID: SESSION_ID });
830
+ sendQuestionPart({ questions: [{
831
+ header: 'Library',
832
+ question: 'Which test library?',
833
+ options: [
834
+ { label: 'Vitest', description: 'Use the existing test runner' },
835
+ { label: 'Node test', description: 'Use node:test' },
836
+ ],
837
+ }] });
838
+ return;
839
+ }
840
+
841
+ async function replyWithHeldDone(body) {
842
+ // Pause, then CEZ:DONE, so a mid-turn sendMessage can queue behind the
843
+ // running turn and race the close (#486).
844
+ await waitForMockRelease(400);
845
+ send({ type: 'message.updated', properties: { info: info({}) } });
846
+ send({ type: 'message.part.updated', properties: { part: {
847
+ id: 'prt_mock_hold_done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
848
+ text: 'parity hold-done: content after the pause\n\nCEZ:DONE',
849
+ time: { start: 1760000000500, end: 1760000000600 },
850
+ } } });
851
+ send({ type: 'message.updated', properties: { info: info({
852
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
853
+ }) } });
854
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
855
+ return;
856
+ }
857
+
858
+ async function replyWithDone(body) {
859
+ // Declares the task complete so the run reaches cezar's review gate; a
860
+ // markerless turn-end correctly parks as `waiting` instead (harness
861
+ // parity R1).
862
+ send({ type: 'message.updated', properties: { info: info({}) } });
863
+ send({ type: 'message.part.updated', properties: { part: {
864
+ id: 'prt_mock_done', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
865
+ text: 'parity done: the task is complete\n\nCEZ:DONE',
866
+ time: { start: 1760000000500, end: 1760000000600 },
867
+ } } });
868
+ send({ type: 'message.updated', properties: { info: info({
869
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
870
+ }) } });
871
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
872
+ return;
873
+ }
874
+
875
+ async function replyWithHeldAsk(body) {
876
+ // Portable CEZ:ASK after a pause, so a mid-turn sendMessage can queue
877
+ // and then race the park (run.ts must discard that waiter).
878
+ await waitForMockRelease(250);
879
+ const ask = 'Pick one.\n\nCEZ:ASK {"questions":[{"header":"Library","question":"Which date library should I standardize on?","options":[{"label":"date-fns","description":"Tree-shakeable, functional"},{"label":"Luxon","description":"Immutable, tz-aware"}]}]}';
880
+ send({ type: 'message.updated', properties: { info: info({}) } });
881
+ send({ type: 'message.part.updated', properties: { part: {
882
+ id: 'prt_mock_hold_ask', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text',
883
+ text: ask,
884
+ time: { start: 1760000000500, end: 1760000000600 },
885
+ } } });
886
+ send({ type: 'message.updated', properties: { info: info({
887
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
888
+ }) } });
889
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
890
+ return;
891
+ }
892
+
893
+ async function replyWithBusyProgress(body) {
894
+ let ticks = 0;
895
+ const timer = setInterval(() => {
896
+ send({ type: 'server.heartbeat', properties: {} });
897
+ if (++ticks === 24) {
898
+ clearInterval(timer);
720
899
  send({ type: 'message.updated', properties: { info: info({}) } });
721
- send({
722
- type: 'message.part.updated',
723
- properties: {
724
- part: { id: 'prt_mock_t1', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Checking the working tree.' },
725
- },
726
- });
727
- send({
728
- type: 'message.part.updated',
729
- properties: {
730
- part: {
731
- id: 'prt_mock_c1',
732
- messageID: MESSAGE_ID,
733
- sessionID: SESSION_ID,
734
- type: 'tool',
735
- callID: 'call_mock_1',
736
- tool: 'bash',
737
- state: { status: 'pending', input: { command: 'git status --short' }, raw: '{}' },
738
- },
739
- },
740
- });
741
- send({
742
- type: 'message.part.updated',
743
- properties: {
744
- part: {
745
- id: 'prt_mock_c1',
746
- messageID: MESSAGE_ID,
747
- sessionID: SESSION_ID,
748
- type: 'tool',
749
- callID: 'call_mock_1',
750
- tool: 'bash',
751
- state: { status: 'running', input: { command: 'git status --short' }, title: 'git status --short', time: { start: 1760000000100 } },
752
- },
753
- },
754
- });
755
- send({
756
- type: 'message.part.updated',
757
- properties: {
758
- part: {
759
- id: 'prt_mock_c1',
760
- messageID: MESSAGE_ID,
761
- sessionID: SESSION_ID,
762
- type: 'tool',
763
- callID: 'call_mock_1',
764
- tool: 'bash',
765
- state: {
766
- status: 'completed',
767
- input: { command: 'git status --short' },
768
- output: ' M src/example.ts\n',
769
- title: 'git status --short',
770
- metadata: { exit: 0 },
771
- time: { start: 1760000000100, end: 1760000000400 },
772
- },
773
- },
774
- },
775
- });
776
- send({
777
- type: 'message.updated',
778
- properties: {
779
- info: info({ cost: 0.0021, tokens: { input: 1200, output: 300, reasoning: 0, cache: { read: 0, write: 0 } } }),
780
- },
781
- });
782
- setTimeout(() => {
783
- send({
784
- type: 'message.part.updated',
785
- properties: {
786
- part: {
787
- id: 'prt_mock_t2',
788
- messageID: MESSAGE_ID,
789
- sessionID: SESSION_ID,
790
- type: 'text',
791
- text: 'Done.',
792
- time: { start: 1760000000500, end: 1760000000600 },
793
- },
794
- },
795
- });
796
- }, 30);
797
- setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 90);
798
- return;
900
+ textPart('prt_busy_done', 'busy complete\n\nCEZ:DONE');
901
+ send({ type: 'message.updated', properties: { info: info({ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } } }) } });
902
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
799
903
  }
800
- res.writeHead(200, { 'content-type': 'application/json' });
801
- res.end('{}');
904
+ }, 100);
905
+ return;
906
+ }
907
+
908
+ async function replyWithGatedHold(body) {
909
+ await waitForMockRelease(250);
910
+ const gated = 'parity hold-gated: content after the pause';
911
+ send({ type: 'message.updated', properties: { info: info({}) } });
912
+ send({ type: 'message.part.updated', properties: { part: {
913
+ id: 'prt_mock_hold_gated', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: gated,
914
+ } } });
915
+ send({ type: 'message.updated', properties: { info: info({
916
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
917
+ }) } });
918
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
919
+ return;
920
+ }
921
+
922
+ async function replyWithHold(body) {
923
+ // The response above is the ack (this is the #4 wire: `prompt_async`
924
+ // resolves immediately, the turn arrives over SSE afterwards). Holding
925
+ // the content behind it is what makes harness parity S2 meaningful.
926
+ setTimeout(() => {
927
+ const held = 'parity hold: content after the pause';
928
+ send({ type: 'message.updated', properties: { info: info({}) } });
929
+ send({ type: 'message.part.updated', properties: { part: {
930
+ id: 'prt_mock_hold', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: held,
931
+ } } });
932
+ send({ type: 'message.updated', properties: { info: info({
933
+ cost: 0.0001, tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
934
+ }) } });
935
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
936
+ }, 250);
937
+ return;
938
+ }
939
+
940
+ function acknowledgeWatchdog() {}
941
+
942
+ // #723: native GET /skill diagnostic captured on 1.18.33; upstream
943
+ // packages/opencode/src/skill/index.ts:96-111 skips the unreadable skill.
944
+ async function replyWithSkillWarning(body) {
945
+ for (const root of ['.claude', '.agents']) send({ type: 'session.error', properties: {
946
+ error: { name: 'UnknownError', data: { message: `Failed to parse skill /home/agent/${root}/skills/pen-design/SKILL.md` } },
947
+ } });
948
+ // hearsay-tools/cezarion#1265: unscoped FrontmatterError frame captured
949
+ // from `opencode serve` 1.18.35 on 2026-10-18; upstream
950
+ // skill/index.ts:105-116 skips the skill.
951
+ send({ type: 'session.error', properties: { error: { name: 'UnknownError', data: { message: FRONTMATTER_ERROR } } } });
952
+ }
953
+
954
+ function replyWithFatalControl(body) {
955
+ const fatalControl = Object.entries({
956
+ 'mock:scoped-skill-failure': { sessionID: SESSION_ID, message: 'Failed to parse skill /skills/required/SKILL.md' },
957
+ 'mock:scoped-frontmatter-failure': { sessionID: SESSION_ID, message: FRONTMATTER_ERROR },
958
+ 'mock:unscoped-provider-failure': { message: 'provider unavailable — retry after restoring service' },
959
+ }).find(([marker]) => body.includes(marker))?.[1];
960
+ if (fatalControl) {
961
+ send({ type: 'session.error', properties: {
962
+ ...(fatalControl.sessionID ? { sessionID: fatalControl.sessionID } : {}),
963
+ error: { name: 'UnknownError', data: { message: fatalControl.message } },
964
+ } });
965
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
966
+ return true;
967
+ }
968
+ return false;
969
+ }
970
+
971
+ async function replyWithEarlyProviderError(body) {
972
+ // #505 review: the provider rejects before any assistant message exists.
973
+ send({ type: 'session.error', properties: { sessionID: SESSION_ID, error: { name: 'ProviderAuthError', data: { message: 'API key expired' } } } });
974
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
975
+ return;
976
+ }
977
+
978
+ async function replyWithNativeFailure(body) {
979
+ const selectedFailure = failureControl?.failureFor(body);
980
+ const nativeFailure = selectedFailure instanceof Promise ? await selectedFailure : selectedFailure;
981
+ if (failureControl ? nativeFailure === 'provider' : body.includes('mock:provider-error')) {
982
+ // The session-level error frame, wire shape copied verbatim from
983
+ // `__fixtures__/opencode/session-error.ndjson`. #53: a bare upstream
984
+ // rejection used to read like a missing executable, and the run parked
985
+ // as "Needs You" rather than failing (harness parity S7).
986
+ send({ type: 'message.updated', properties: { info: info({}) } });
987
+ send({ type: 'message.part.updated', properties: { part: {
988
+ id: 'prt_mock_perr', messageID: MESSAGE_ID, sessionID: SESSION_ID,
989
+ type: 'text', text: 'Starting the review',
990
+ } } });
991
+ send({ type: 'session.error', properties: { sessionID: SESSION_ID, error: {
992
+ name: 'ProviderAuthError',
993
+ data: { message: 'API key expired — run `opencode auth login`' },
994
+ } } });
995
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
996
+ return true;
997
+ }
998
+ return false;
999
+ }
1000
+
1001
+ function replyWithOrdinaryTurn() {
1002
+ send({ type: 'message.updated', properties: { info: info({}) } });
1003
+ send({
1004
+ type: 'message.part.updated',
1005
+ properties: {
1006
+ part: { id: 'prt_mock_t1', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Checking the working tree.' },
1007
+ },
802
1008
  });
803
- });
1009
+ send({
1010
+ type: 'message.part.updated',
1011
+ properties: {
1012
+ part: {
1013
+ id: 'prt_mock_c1',
1014
+ messageID: MESSAGE_ID,
1015
+ sessionID: SESSION_ID,
1016
+ type: 'tool',
1017
+ callID: 'call_mock_1',
1018
+ tool: 'bash',
1019
+ state: { status: 'pending', input: { command: 'git status --short' }, raw: '{}' },
1020
+ },
1021
+ },
1022
+ });
1023
+ send({
1024
+ type: 'message.part.updated',
1025
+ properties: {
1026
+ part: {
1027
+ id: 'prt_mock_c1',
1028
+ messageID: MESSAGE_ID,
1029
+ sessionID: SESSION_ID,
1030
+ type: 'tool',
1031
+ callID: 'call_mock_1',
1032
+ tool: 'bash',
1033
+ state: { status: 'running', input: { command: 'git status --short' }, title: 'git status --short', time: { start: 1760000000100 } },
1034
+ },
1035
+ },
1036
+ });
1037
+ send({
1038
+ type: 'message.part.updated',
1039
+ properties: {
1040
+ part: {
1041
+ id: 'prt_mock_c1',
1042
+ messageID: MESSAGE_ID,
1043
+ sessionID: SESSION_ID,
1044
+ type: 'tool',
1045
+ callID: 'call_mock_1',
1046
+ tool: 'bash',
1047
+ state: {
1048
+ status: 'completed',
1049
+ input: { command: 'git status --short' },
1050
+ output: ' M src/example.ts\n',
1051
+ title: 'git status --short',
1052
+ metadata: { exit: 0 },
1053
+ time: { start: 1760000000100, end: 1760000000400 },
1054
+ },
1055
+ },
1056
+ },
1057
+ });
1058
+ send({
1059
+ type: 'message.updated',
1060
+ properties: {
1061
+ info: info({ cost: 0.0021, tokens: { input: 1200, output: 300, reasoning: 0, cache: { read: 0, write: 0 } } }),
1062
+ },
1063
+ });
1064
+ setTimeout(() => {
1065
+ send({
1066
+ type: 'message.part.updated',
1067
+ properties: {
1068
+ part: {
1069
+ id: 'prt_mock_t2',
1070
+ messageID: MESSAGE_ID,
1071
+ sessionID: SESSION_ID,
1072
+ type: 'text',
1073
+ text: 'Done.',
1074
+ time: { start: 1760000000500, end: 1760000000600 },
1075
+ },
1076
+ },
1077
+ });
1078
+ }, 30);
1079
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 90);
1080
+ return;
1081
+ }
1082
+
1083
+ async function crashAfterAcknowledgement(body) {
1084
+ if (!body.includes('mock:crash-stderr')) return false;
1085
+ const { crashWithStderr } = await import('./mock-runner-crash.mjs');
1086
+ if (body.includes('mock:crash-stderr-clean')) crashWithStderr(body);
1087
+ else { setTimeout(() => crashWithStderr(body), 30); return true; }
1088
+ return false;
1089
+ }
1090
+
1091
+ function recordSessionPrompt(body) {
1092
+ try {
1093
+ const text = body ? JSON.parse(body).parts?.map(part => part.text ?? '').join('\n') ?? '' : '';
1094
+ if (text) addPrompt(SESSION_ID, text);
1095
+ } catch {
1096
+ // Keep the scripted turn even when the body is not JSON.
1097
+ }
1098
+ }
1099
+
1100
+ function deliveryPromptText(body, received) {
1101
+ // A configured control matches the prompt's text parts, never their JSON encoding.
1102
+ let deliveryText = body;
1103
+ // Both the prompt Cezar sent and a nudge's scripted answer match (hearsay-tools/cezarion#1525).
1104
+ const textOf = (raw) => JSON.parse(raw).parts.map(part => part.text ?? '').join('\n');
1105
+ if (delivery) try { deliveryText = received === body ? textOf(body) : `${textOf(received)}\n${textOf(body)}`; } catch { /* not a prompt body */ }
1106
+ return deliveryText;
1107
+ }
1108
+
1109
+ async function acknowledgeSteer(body, res) {
1110
+ const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
1111
+ const userId = `msg_steer_user_${++steerSerial}`;
1112
+ // #505 review: acknowledge a steer later than the runner's lost-wake window.
1113
+ const ackDelay = Number(process.env.CEZ_MOCK_OPENCODE_STEER_ACK_MS ?? 0);
1114
+ if (ackDelay > 0) setTimeout(() => { res.writeHead(204); res.end(); }, ackDelay);
1115
+ else { res.writeHead(204); res.end(); }
1116
+ send({ type: 'message.updated', properties: { info: { id: userId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
1117
+ send({ type: 'message.part.updated', properties: { part: { id: `prt_${userId}`, messageID: userId, sessionID: SESSION_ID, type: 'text', text } } });
1118
+ steers.push({ userId, text });
1119
+ return;
1120
+ }
1121
+
1122
+ function acknowledgePrompt(res, body, deliveryText) {
1123
+ // `prompt_async` semantics: acknowledge now, stream the turn over SSE.
1124
+ res.writeHead(200, { 'content-type': 'application/json' });
1125
+ // #426: a nudged turn can finish on SSE before its HTTP acceptance arrives.
1126
+ // Force that ordering in the cap scenario; the first opening stays ordinary.
1127
+ if (autonomousReadinessIdle && body.includes('Continue working autonomously until the task is fully complete.')) {
1128
+ // Keep the portable answer HTTP request unacknowledged while its SSE turn finishes.
1129
+ } else if (autonomousCap && body.includes('Continue working autonomously until the task is fully complete.')) {
1130
+ setTimeout(() => res.end(JSON.stringify({ info: info({}), parts: [] })), 100);
1131
+ } else if (body.includes('f7-delay-ack')) {
1132
+ // F7: SSE turn-end before HTTP ACK so parkAfterAck applies the silent-tail nudge.
1133
+ setTimeout(() => res.end(JSON.stringify({ info: info({}), parts: [] })), 250);
1134
+ } else {
1135
+ const ack = () => res.end(JSON.stringify({ info: info({}), parts: [] }));
1136
+ if (delivery) delivery.ack(deliveryText, ack); else ack();
1137
+ }
1138
+ }
1139
+
1140
+ function needsSilentPreparation(body) {
1141
+ return body.includes('mock:silent-tail') || silentNudge(body);
1142
+ }
1143
+
1144
+ async function prepareSilentTail(body) {
1145
+ if (!needsSilentPreparation(body)) return false;
1146
+ const silent = await import('./mock-silent-tail.mjs');
1147
+ const prompt = (() => { try { return JSON.parse(body).parts.map(part => part.text ?? '').join('\n'); } catch { return body; } })();
1148
+ silent.noteSilentTailPrompt(prompt);
1149
+ if (silent.isAckOnlyNudge(prompt)) {
1150
+ const beats = setInterval(() => { if (sse) sse.write(': heartbeat\n\n'); }, 50);
1151
+ beats.unref?.();
1152
+ return true;
1153
+ }
1154
+ if (silent.isLateNudge(prompt)) await waitForMockRelease(silent.LATE_REPLY_MS);
1155
+ return false;
1156
+ }
1157
+
1158
+ function rejectsPrompt(body, deliveryText) {
1159
+ return delivery?.kind === 'reject' ? delivery.rejects(deliveryText) : body.includes('mock:reject-agent-post');
1160
+ }
1161
+
1162
+ async function preparePromptTurn(res, url, body, received) {
1163
+ if (body.includes('mock:crash-stderr-pre-ack')) {
1164
+ const { crashWithStderr } = await import('./mock-runner-crash.mjs');
1165
+ crashWithStderr(body);
1166
+ return true;
1167
+ }
1168
+ const deliveryText = deliveryPromptText(body, received);
1169
+ // A configured rejection decides alone: it adds "once" and "until lifted" to the native token.
1170
+ if (rejectsPrompt(body, deliveryText)) {
1171
+ res.writeHead(503, { 'content-type': 'application/json' });
1172
+ res.end(JSON.stringify({ error: 'agent prompt rejected' }));
1173
+ return true;
1174
+ }
1175
+ if (heldBeats && url.endsWith('/prompt_async')) { clearInterval(heldBeats); heldBeats = undefined; turnActive = false; }
1176
+ if (turnActive && url.endsWith('/prompt_async')) { await acknowledgeSteer(body, res); return true; }
1177
+ // A prompt posted while a follow-on run is due runs after it.
1178
+ if (followOn) await followOn;
1179
+ turnActive = true;
1180
+ acknowledgePrompt(res, body, deliveryText);
1181
+ if (delivery) await delivery.holdResponse(deliveryText);
1182
+ // `mock:ack-only` → prompt_async is acknowledged and the session stays busy on heartbeats
1183
+ // with no turn until the next prompt, which it then runs (hearsay-tools/cezarion#1301).
1184
+ if (body.includes('mock:ack-only')) {
1185
+ heldBeats = setInterval(() => { if (sse) sse.write(': heartbeat\n\n'); }, 50);
1186
+ heldBeats.unref?.();
1187
+ return true;
1188
+ }
1189
+ if (body.includes('mock:autonomous-readiness-idle')) autonomousReadinessIdle = true;
1190
+ if (body.includes('mock:autonomous-cap') || body.includes('mock:autonomous-ask-cap')) autonomousCap = true;
1191
+ if (await prepareSilentTail(body)) return true;
1192
+ return false;
1193
+ }
1194
+
1195
+ async function streamPrompt(url, body) {
1196
+ // hearsay-tools/cezarion#1112: the acknowledged prompt streams its turn late.
1197
+ if (body.includes('mock:deferred-turn-start')) await (await import('./mock-deferred-turn-start.mjs')).deferredTurnStart();
1198
+ if (url.endsWith('/prompt_async')) {
1199
+ currentUserId = `msg_user_${++steerSerial}`;
1200
+ const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
1201
+ send({ type: 'message.updated', properties: { info: { id: currentUserId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
1202
+ send({ type: 'message.part.updated', properties: { part: { id: `prt_${currentUserId}`, messageID: currentUserId, sessionID: SESSION_ID, type: 'text', text } } });
1203
+ }
1204
+ if (await crashAfterAcknowledgement(body)) return;
1205
+ if (await dispatchReply(earlyReplies, body)) return;
1206
+ if (body.includes('mock:skill-warning')) await replyWithSkillWarning(body);
1207
+ if (replyWithFatalControl(body)) return;
1208
+ if (body.includes('mock:provider-error-early')) { await replyWithEarlyProviderError(body); return; }
1209
+ if (await replyWithNativeFailure(body)) return;
1210
+ if (await dispatchReply(lateReplies, body)) return;
1211
+ replyWithOrdinaryTurn();
1212
+ }
804
1213
 
805
1214
  // Test sessions share a host: let the OS allocate a port instead of racing
806
1215
  // the runner’s random requested port against other fixtures.
@@ -808,6 +1217,12 @@ server.listen(0, hostname, () => {
808
1217
  // The runner reads the bound URL back from stdout, like the real server.
809
1218
  console.log(`opencode server listening on http://${hostname}:${server.address().port}`);
810
1219
  });
1220
+ // hearsay-tools/cezarion#1449: the server leads its own session, so a test process that dies
1221
+ // before its cleanup leaves it running. The runner never writes to or closes this stdin pipe,
1222
+ // so end of input means the parent died (SIGKILL included). Exit directly, past any SIGTERM
1223
+ // handler a shutdown scenario replaced.
1224
+ process.stdin.on('end', () => process.exit(0));
1225
+ process.stdin.resume();
811
1226
  process.on('SIGTERM', () => {
812
1227
  // Expose the end-request/process-exit interval to the root idle regression.
813
1228
  if (autonomousReadinessIdle) setTimeout(() => process.exit(0), 250);