@wjarka/cezarion 0.14.8 → 0.14.9-dev.1004

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 (274) hide show
  1. package/README.md +129 -2
  2. package/dist/application-update/bin-recovery.d.ts +5 -0
  3. package/dist/application-update/bin-recovery.js +96 -0
  4. package/dist/application-update/bin-recovery.js.map +1 -0
  5. package/dist/application-update/discovery.d.ts +22 -0
  6. package/dist/application-update/discovery.js +91 -0
  7. package/dist/application-update/discovery.js.map +1 -0
  8. package/dist/application-update/helper.d.ts +57 -0
  9. package/dist/application-update/helper.js +374 -0
  10. package/dist/application-update/helper.js.map +1 -0
  11. package/dist/application-update/launcher.d.ts +8 -0
  12. package/dist/application-update/launcher.js +53 -0
  13. package/dist/application-update/launcher.js.map +1 -0
  14. package/dist/application-update/lock.d.ts +4 -0
  15. package/dist/application-update/lock.js +70 -0
  16. package/dist/application-update/lock.js.map +1 -0
  17. package/dist/application-update/npm-process.d.ts +17 -0
  18. package/dist/application-update/npm-process.js +133 -0
  19. package/dist/application-update/npm-process.js.map +1 -0
  20. package/dist/application-update/service.d.ts +69 -0
  21. package/dist/application-update/service.js +456 -0
  22. package/dist/application-update/service.js.map +1 -0
  23. package/dist/artifacts/cli.d.ts +1 -0
  24. package/dist/artifacts/cli.js +32 -0
  25. package/dist/artifacts/cli.js.map +1 -0
  26. package/dist/artifacts/lifecycle.d.ts +4 -0
  27. package/dist/artifacts/lifecycle.js +42 -0
  28. package/dist/artifacts/lifecycle.js.map +1 -0
  29. package/dist/artifacts/resolve.d.ts +18 -0
  30. package/dist/artifacts/resolve.js +102 -0
  31. package/dist/artifacts/resolve.js.map +1 -0
  32. package/dist/artifacts/store.d.ts +8 -0
  33. package/dist/artifacts/store.js +323 -0
  34. package/dist/artifacts/store.js.map +1 -0
  35. package/dist/automations/scheduler.d.ts +6 -0
  36. package/dist/automations/scheduler.js +45 -17
  37. package/dist/automations/scheduler.js.map +1 -1
  38. package/dist/automations/store.d.ts +12 -4
  39. package/dist/automations/store.js +199 -22
  40. package/dist/automations/store.js.map +1 -1
  41. package/dist/automations/types.d.ts +2 -0
  42. package/dist/automations/types.js +1 -0
  43. package/dist/automations/types.js.map +1 -1
  44. package/dist/ci-wait/mcp.d.ts +3 -0
  45. package/dist/ci-wait/mcp.js +14 -3
  46. package/dist/ci-wait/mcp.js.map +1 -1
  47. package/dist/contract/application-update.d.ts +32 -0
  48. package/dist/contract/artifacts.d.ts +83 -0
  49. package/dist/contract/ask-schema.d.ts +32 -0
  50. package/dist/contract/ask.d.ts +2 -32
  51. package/dist/contract/automations.d.ts +6 -0
  52. package/dist/contract/conversations.d.ts +165 -0
  53. package/dist/contract/delegation.d.ts +68 -0
  54. package/dist/contract/discovery.d.ts +134 -0
  55. package/dist/contract/events.d.ts +49 -0
  56. package/dist/contract/health.d.ts +12 -0
  57. package/dist/contract/index.d.ts +3 -0
  58. package/dist/contract/index.js +1654 -1454
  59. package/dist/contract/projects.d.ts +40 -0
  60. package/dist/contract/runs.d.ts +186 -0
  61. package/dist/contract/skills.d.ts +33 -0
  62. package/dist/core/agent-runner.d.ts +33 -4
  63. package/dist/core/agent-runner.js +5 -0
  64. package/dist/core/agent-runner.js.map +1 -1
  65. package/dist/core/claude-cli-runner.d.ts +6 -12
  66. package/dist/core/claude-cli-runner.js +90 -20
  67. package/dist/core/claude-cli-runner.js.map +1 -1
  68. package/dist/core/codex-app-server-runner.d.ts +2 -1
  69. package/dist/core/codex-app-server-runner.js +327 -92
  70. package/dist/core/codex-app-server-runner.js.map +1 -1
  71. package/dist/core/codex-app-server-transport.d.ts +13 -1
  72. package/dist/core/codex-app-server-transport.js +58 -17
  73. package/dist/core/codex-app-server-transport.js.map +1 -1
  74. package/dist/core/cursor-acp-runner.d.ts +6 -1
  75. package/dist/core/cursor-acp-runner.js +162 -26
  76. package/dist/core/cursor-acp-runner.js.map +1 -1
  77. package/dist/core/cursor-provider-error.d.ts +3 -0
  78. package/dist/core/cursor-provider-error.js +16 -1
  79. package/dist/core/cursor-provider-error.js.map +1 -1
  80. package/dist/core/input-submissions.d.ts +14 -0
  81. package/dist/core/input-submissions.js +26 -0
  82. package/dist/core/input-submissions.js.map +1 -0
  83. package/dist/core/opencode-server-runner.d.ts +4 -1
  84. package/dist/core/opencode-server-runner.js +156 -26
  85. package/dist/core/opencode-server-runner.js.map +1 -1
  86. package/dist/core/pi-runner.d.ts +2 -1
  87. package/dist/core/pi-runner.js +88 -10
  88. package/dist/core/pi-runner.js.map +1 -1
  89. package/dist/core/provider-auth.d.ts +99 -1
  90. package/dist/core/provider-auth.js +203 -2
  91. package/dist/core/provider-auth.js.map +1 -1
  92. package/dist/delegation/cli.d.ts +1 -0
  93. package/dist/delegation/cli.js +103 -7
  94. package/dist/delegation/cli.js.map +1 -1
  95. package/dist/delegation/conversations.js +13 -5
  96. package/dist/delegation/conversations.js.map +1 -1
  97. package/dist/delegation/input.d.ts +2 -1
  98. package/dist/delegation/input.js +6 -1
  99. package/dist/delegation/input.js.map +1 -1
  100. package/dist/delegation/policy.d.ts +3 -2
  101. package/dist/delegation/policy.js +3 -1
  102. package/dist/delegation/policy.js.map +1 -1
  103. package/dist/delegation/process-liveness.d.ts +66 -0
  104. package/dist/delegation/process-liveness.js +215 -0
  105. package/dist/delegation/process-liveness.js.map +1 -0
  106. package/dist/delegation/provision.d.ts +1 -1
  107. package/dist/delegation/provision.js +10 -5
  108. package/dist/delegation/provision.js.map +1 -1
  109. package/dist/delegation/questions.d.ts +19 -0
  110. package/dist/delegation/questions.js +45 -0
  111. package/dist/delegation/questions.js.map +1 -0
  112. package/dist/delegation/results.d.ts +1 -1
  113. package/dist/delegation/results.js +33 -19
  114. package/dist/delegation/results.js.map +1 -1
  115. package/dist/delegation/routes.d.ts +185 -11
  116. package/dist/delegation/routes.js +6 -2
  117. package/dist/delegation/routes.js.map +1 -1
  118. package/dist/delegation/service.d.ts +16 -5
  119. package/dist/delegation/service.js +161 -13
  120. package/dist/delegation/service.js.map +1 -1
  121. package/dist/delegation/transport.d.ts +228 -65
  122. package/dist/discovery/catalog.d.ts +14 -0
  123. package/dist/discovery/catalog.js +31 -0
  124. package/dist/discovery/catalog.js.map +1 -0
  125. package/dist/discovery/cli.d.ts +8 -0
  126. package/dist/discovery/cli.js +104 -0
  127. package/dist/discovery/cli.js.map +1 -0
  128. package/dist/git-worktree.d.ts +3 -1
  129. package/dist/git-worktree.js +26 -4
  130. package/dist/git-worktree.js.map +1 -1
  131. package/dist/index.js +92 -26
  132. package/dist/index.js.map +1 -1
  133. package/dist/open-url.d.ts +2 -0
  134. package/dist/open-url.js +20 -0
  135. package/dist/open-url.js.map +1 -0
  136. package/dist/release/snapshot.d.ts +3 -3
  137. package/dist/release/snapshot.js +6 -6
  138. package/dist/release/snapshot.js.map +1 -1
  139. package/dist/runs/agent-tmpdir.d.ts +2 -0
  140. package/dist/runs/agent-tmpdir.js +5 -2
  141. package/dist/runs/agent-tmpdir.js.map +1 -1
  142. package/dist/runs/client-request.d.ts +11 -0
  143. package/dist/runs/client-request.js +26 -0
  144. package/dist/runs/client-request.js.map +1 -0
  145. package/dist/runs/delegation-state.d.ts +17 -0
  146. package/dist/runs/event-history.js +6 -0
  147. package/dist/runs/event-history.js.map +1 -1
  148. package/dist/runs/retention.d.ts +7 -2
  149. package/dist/runs/retention.js +39 -7
  150. package/dist/runs/retention.js.map +1 -1
  151. package/dist/runs/store.d.ts +100 -6
  152. package/dist/runs/store.js +245 -2
  153. package/dist/runs/store.js.map +1 -1
  154. package/dist/runs/webhook.d.ts +125 -0
  155. package/dist/runs/webhook.js +256 -0
  156. package/dist/runs/webhook.js.map +1 -0
  157. package/dist/server/provider-auth-runtime.d.ts +30 -4
  158. package/dist/server/provider-auth-runtime.js +71 -6
  159. package/dist/server/provider-auth-runtime.js.map +1 -1
  160. package/dist/server/server.d.ts +8226 -4860
  161. package/dist/server/server.js +309 -15
  162. package/dist/server/server.js.map +1 -1
  163. package/dist/server-install/platforms/ubuntu-vps.d.ts +5 -1
  164. package/dist/server-install/platforms/ubuntu-vps.js +160 -18
  165. package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
  166. package/dist/server-install/steps.d.ts +4 -2
  167. package/dist/server-install/steps.js +5 -4
  168. package/dist/server-install/steps.js.map +1 -1
  169. package/dist/skills-remote.d.ts +30 -5
  170. package/dist/skills-remote.js +122 -32
  171. package/dist/skills-remote.js.map +1 -1
  172. package/dist/skills.js +15 -6
  173. package/dist/skills.js.map +1 -1
  174. package/dist/task-cli/cli.d.ts +38 -0
  175. package/dist/task-cli/cli.js +580 -0
  176. package/dist/task-cli/cli.js.map +1 -0
  177. package/dist/task-cli/discovery.d.ts +22 -0
  178. package/dist/task-cli/discovery.js +96 -0
  179. package/dist/task-cli/discovery.js.map +1 -0
  180. package/dist/task-cli/http.d.ts +46 -0
  181. package/dist/task-cli/http.js +101 -0
  182. package/dist/task-cli/http.js.map +1 -0
  183. package/dist/task-cli/projections.d.ts +40 -0
  184. package/dist/task-cli/projections.js +45 -0
  185. package/dist/task-cli/projections.js.map +1 -0
  186. package/dist/task-cli/watch.d.ts +78 -0
  187. package/dist/task-cli/watch.js +336 -0
  188. package/dist/task-cli/watch.js.map +1 -0
  189. package/dist/workflows/run.d.ts +160 -2
  190. package/dist/workflows/run.js +1141 -108
  191. package/dist/workflows/run.js.map +1 -1
  192. package/dist/workspace/agent-profiles.d.ts +22 -0
  193. package/dist/workspace/agent-profiles.js +15 -0
  194. package/dist/workspace/agent-profiles.js.map +1 -1
  195. package/dist/workspace/config.d.ts +9 -0
  196. package/dist/workspace/config.js +13 -1
  197. package/dist/workspace/config.js.map +1 -1
  198. package/dist/workspace/projects-cli.js +1 -1
  199. package/dist/workspace/projects.d.ts +24 -1
  200. package/dist/workspace/projects.js +14 -1
  201. package/dist/workspace/projects.js.map +1 -1
  202. package/package.json +5 -5
  203. package/scripts/mock-claude.mjs +64 -2
  204. package/scripts/mock-codex-app-server.mjs +124 -1
  205. package/scripts/mock-cursor-acp.mjs +54 -3
  206. package/scripts/mock-cursor-hang-stdin-late.mjs +35 -0
  207. package/scripts/mock-cursor-hang-stdin.mjs +30 -0
  208. package/scripts/mock-opencode-serve.mjs +102 -2
  209. package/scripts/mock-pi-rpc.mjs +82 -3
  210. package/web/dist/assets/agent-accounts-CerclGiO.js +1 -0
  211. package/web/dist/assets/centered-state-DH8qtCz-.js +43 -0
  212. package/web/dist/assets/collapsible-De2G0xqQ.js +1 -0
  213. package/web/dist/assets/commit-list-CQubP1iS.js +1 -0
  214. package/web/dist/assets/compare-variants-C_NijJvg.js +1 -0
  215. package/web/dist/assets/{diff-tRE59UNb2.js → diff-8M5HlDxq2.js} +2 -2
  216. package/web/dist/assets/diff-controls-B6ok0TtQ.js +1 -0
  217. package/web/dist/assets/{diff-stat-Cgnl_hYF.js → diff-stat-D1AODN2a.js} +1 -1
  218. package/web/dist/assets/{diff-view-DN1ort7Q.js → diff-view-Bs5Gh7UF.js} +2 -2
  219. package/web/dist/assets/dropdown-menu-7KGQMoFj.js +1 -0
  220. package/web/dist/assets/git-toolbar-DqbapMXr.js +1 -0
  221. package/web/dist/assets/github-lMHiuYSx.js +1 -0
  222. package/web/dist/assets/{image-preview-C2JGcBlC.js → image-preview-jzHoEyLM.js} +1 -1
  223. package/web/dist/assets/index-D4OJTArJ.js +7 -0
  224. package/web/dist/assets/index-D5NkwqY7.css +1 -0
  225. package/web/dist/assets/markdown-DvbymqXz.js +2 -0
  226. package/web/dist/assets/pill-BL6ugwXo.js +1 -0
  227. package/web/dist/assets/project-router-ChC5Rb-Y.js +1 -0
  228. package/web/dist/assets/prompt-templates-j8qlK7c-.js +15 -0
  229. package/web/dist/assets/repo-git-BbPAgkqb.js +1 -0
  230. package/web/dist/assets/{run-diff-CyG0Nciv.js → run-diff-BuATgZpx.js} +2 -2
  231. package/web/dist/assets/run-header-DuAWswYM.js +1 -0
  232. package/web/dist/assets/skills-DiC2XrU1.js +1 -0
  233. package/web/dist/assets/{tab-link-BWNp3mLY.js → tab-link-BmGKj9Rt.js} +1 -1
  234. package/web/dist/assets/task-changes-CP9kPnsy.js +1 -0
  235. package/web/dist/assets/task-commits-D6NfTw5r.js +1 -0
  236. package/web/dist/assets/task-files-W-b_gtcb.js +2 -0
  237. package/web/dist/assets/task-thread-Bw_nQuTx.css +1 -0
  238. package/web/dist/assets/task-thread-Q7Nj33DH.js +9 -0
  239. package/web/dist/assets/textarea-ByZ-txS7.js +1 -0
  240. package/web/dist/assets/thread-loading-DHT9p5o5.js +1 -0
  241. package/web/dist/assets/token-metrics-k4XoovDS.js +1 -0
  242. package/web/dist/assets/utils-BTFBhZ55.js +64 -0
  243. package/web/dist/assets/workflows-DqQszPHJ.js +11 -0
  244. package/web/dist/assets/{zoomable-image-Z3JDnm0b.js → zoomable-image-Ba6y6Wyl.js} +1 -1
  245. package/web/dist/index.html +14 -14
  246. package/web/dist/assets/agent-accounts-BZm_2WAv.js +0 -1
  247. package/web/dist/assets/alert-dialog-B422RI-k.js +0 -43
  248. package/web/dist/assets/collapsible-Cy_Fmkrd.js +0 -1
  249. package/web/dist/assets/commit-list-Dcnk7TnI.js +0 -1
  250. package/web/dist/assets/compare-variants-Ciqwvd_f.js +0 -1
  251. package/web/dist/assets/diff-controls-BGrXGi7O.js +0 -1
  252. package/web/dist/assets/dropdown-menu-dwDGT8MM.js +0 -1
  253. package/web/dist/assets/git-toolbar-UiHnGvE0.js +0 -1
  254. package/web/dist/assets/github-D-MrkbJ7.js +0 -1
  255. package/web/dist/assets/ibm-plex-mono-regular-BF3vfe7_.ttf +0 -0
  256. package/web/dist/assets/index-BszfYvBq.js +0 -7
  257. package/web/dist/assets/index-CWh4pKP3.css +0 -1
  258. package/web/dist/assets/markdown-C_IcKVQd.js +0 -2
  259. package/web/dist/assets/pill-BsbO6G7y.js +0 -1
  260. package/web/dist/assets/project-router-DbLkll25.js +0 -1
  261. package/web/dist/assets/prompt-templates-BlTGJB5W.js +0 -15
  262. package/web/dist/assets/repo-git-Bm3s6Why.js +0 -1
  263. package/web/dist/assets/run-header-IFyIGCey.js +0 -1
  264. package/web/dist/assets/skills-C-C4DVFY.js +0 -1
  265. package/web/dist/assets/task-changes-D2PHtWUW.js +0 -1
  266. package/web/dist/assets/task-commits-N5u9U0_R.js +0 -1
  267. package/web/dist/assets/task-files-BS7c7CFm.js +0 -2
  268. package/web/dist/assets/task-thread-BQFdz1dJ.css +0 -1
  269. package/web/dist/assets/task-thread-DGM2dZV-.js +0 -9
  270. package/web/dist/assets/textarea-FjaU370i.js +0 -1
  271. package/web/dist/assets/thread-loading-77sJ8Yu_.js +0 -1
  272. package/web/dist/assets/token-metrics-Clxh03bE.js +0 -1
  273. package/web/dist/assets/utils-PYYcpCFZ.js +0 -64
  274. package/web/dist/assets/workflows-Czl0ZfXH.js +0 -11
@@ -1,9 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  // Offline Cursor ACP wire. Shapes: cursor.com/docs/cli/acp; ACP v1 schema.
3
3
  import { createInterface } from 'node:readline';
4
- import { appendFileSync } from 'node:fs';
4
+ import { appendFileSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import * as parityGateFs from 'node:fs';
6
+
7
+ // #401: let the test observe the actual monitoring park before releasing late wire frames.
8
+ async function afterParityPark(prompt) {
9
+ const gate = /parity-release=([^\s"\\]+)/.exec(prompt)?.[1];
10
+ if (!gate) throw new Error('post-park scenario requires a release path');
11
+ while (!parityGateFs.existsSync(gate)) await new Promise(resolve => setTimeout(resolve, 10));
12
+ }
13
+
5
14
  const record = (file, value) => { if (file) appendFileSync(file, `${JSON.stringify(value, (key, value) => key === 'env' && Array.isArray(value) ? value.map(item => item.name?.startsWith('CEZ_TOOL_') ? { ...item, value: '[redacted]' } : item) : value)}\n`); };
6
15
  record(process.env.CEZ_MOCK_ARGS_FILE, process.argv.slice(2));
16
+ /** #529: remaining-count file. Each session/new or session/load decrements; at >0 the process writes stderr and exits 1 before the reply. */
17
+ function consumeBootstrapCrash() {
18
+ const file = process.env.CEZ_MOCK_CURSOR_CRASH_ON_LOAD;
19
+ if (!file) return;
20
+ let remaining = 0;
21
+ try { remaining = Number(readFileSync(file, 'utf8')); } catch { return; }
22
+ if (!Number.isFinite(remaining) || remaining <= 0) return;
23
+ writeFileSync(file, String(remaining - 1));
24
+ process.stderr.write(`${process.env.CEZ_MOCK_CURSOR_CRASH_STDERR ?? 'Cursor ACP mock bootstrap crash'}\n`);
25
+ process.exit(1);
26
+ }
7
27
  const emit = value => process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', ...value })}\n`);
8
28
  const reply = (id, result) => emit({ id, result });
9
29
  let sessionId = 'cursor-offline-session';
@@ -34,17 +54,48 @@ async function prompt(id, content) {
34
54
  if (input.includes('mock:rpc-error')) { emit({ id, error: { code: -32603, message: 'Provider rejected request' } }); return; }
35
55
  // #443 provider-error envelopes: the specific scenarios must be matched BEFORE the
36
56
  // bare 'mock:provider-error' prefix, which every one of them contains.
57
+ // #508: observed Cursor 2026.09.18-9a7762b error envelope, followed by end_turn.
58
+ if (input.includes('mock:provider-error-protocol') && (input.includes('-exhaust') || prompts === 1)) { text('\n\nError: RetriableError: [invalid_argument] protocol error: missing EndStreamResponse'); complete(id); return; }
59
+ // #528: observed Cursor SSL record-layer envelope (run 4bcd5420 seq 2667). Matched
60
+ // before the bare mock:provider-error prefix, same pattern as protocol.
61
+ if (input.includes('mock:provider-error-ssl') && (input.includes('-exhaust') || prompts === 1)) { text('\n\nError: RetriableError: [internal] C0AC9346CC7B0000:error:0A000119:SSL routines:tls_get_more_records:decryption failed or bad record mac:../deps/openssl/openssl/ssl/record/methods/tls_common.c:869:'); complete(id); return; }
62
+ // #531: observed run 2f891027 seq 2005 capacity envelope. Match before the bare prefix.
63
+ // Do not reuse the protocol mock's `includes('-exhaust')` check: that token is a
64
+ // substring of `resource-exhausted` and would never recover.
65
+ if (input.includes('mock:provider-error-resource-exhausted-exhaust')) { text('\n\nError: RetriableError: [resource_exhausted] Error'); complete(id); return; }
66
+ if (input.includes('mock:provider-error-resource-exhausted') && prompts === 1) { text('\n\nError: RetriableError: [resource_exhausted] Error'); complete(id); return; }
67
+ if (input.includes('mock:provider-error-unknown-protocol')) { text('\n\nError: [invalid_argument] protocol error: unknown frame'); complete(id); return; }
37
68
  if (input.includes('mock:provider-error-transient') && prompts === 1) { text('\n\nError: 502 bad gateway.'); complete(id); return; }
38
69
  if (input.includes('mock:provider-error-bare')) { text('\n\nError: 502 bad gateway.'); complete(id); return; }
39
70
  if (input.includes('mock:provider-error-instant-near') && prompts === 1) { text(`\n\nError: 429 rate limited, try again at ${new Date(Date.now() + 300).toISOString()}.`); complete(id); return; }
40
71
  if (input.includes('mock:provider-error-instant-far')) { text(`\n\nError: usage limit reached, try again at ${new Date(Date.now() + 6 * 3600000).toISOString()}.`); complete(id); return; }
41
72
  if (input.includes('mock:provider-error-verbose')) { text(`\n\nError: usage limit reached. The request ${'x'.repeat(600)} could not be completed, try again at ${new Date(Date.now() + 6 * 3600000).toISOString()}.`); complete(id); return; }
42
73
  if (input.includes('mock:provider-error') && !input.includes('mock:provider-error-')) { text('\n\nError: [unauthenticated] Backend rejected authentication.'); complete(id); return; }
74
+ // #401: ACP has chunks only, so completion must flush the same text to v1/v2.
75
+ if (input.includes('mock:ask-snapshot')) {
76
+ text('Choose a test library.\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}'); complete(id); return;
77
+ }
43
78
  if (input.includes('mock:ask-bad')) { pendingAsk = { id }; emit({ id: 'bad-question', method: 'cursor/ask_question', params: { questions: [] } }); return; }
44
79
  if (input.includes('mock:plan')) { pendingAsk = { id, input }; emit({ id: 'plan-1', method: 'cursor/create_plan', params: { name: 'Test plan', overview: 'Approve the changes?', plan: 'Implement the change and run tests.' } }); return; }
45
80
  if (input.includes('mock:multi-ask')) { pendingAsk = { id }; emit({ id: 'multi-question', method: 'cursor/ask_question', params: { title: 'Choices', questions: [question, { ...question, id: 'build', prompt: 'Which build tool?', options: [{ id: 'vite', label: 'Vite' }, { id: 'webpack', label: 'Webpack' }] }] } }); return; }
46
81
  if (input.includes('mock:ask')) { pendingAsk = { id, input }; emit({ id: 'question-1', method: 'cursor/ask_question', params: { toolCallId: 'ask-tool', title: 'Tests', questions: [question] } }); return; }
47
82
  if (input.includes('mock:permission')) { pendingAsk = { id }; emit({ id: 'permission-1', method: 'session/request_permission', params: { sessionId, toolCall: { toolCallId: 'shell-1', title: 'echo hello', kind: 'execute' }, options: [{ optionId: 'allow-once', name: 'Allow once', kind: 'allow_once' }, { optionId: 'reject-once', name: 'Reject', kind: 'reject_once' }] } }); return; }
83
+ // ACP subagent attribution from __fixtures__/cursor/acp-subagents.ndjson.
84
+ if (input.includes('mock:subagent-after-park')) {
85
+ update({ sessionUpdate: 'subagent_spawned', subagentSessionId: 'park-child', name: 'Reviewer', task: 'Review', capabilities: {}, _meta: { cursor: { toolCallId: 'park-task', agentId: 'park-agent' } } });
86
+ update({ sessionUpdate: 'tool_call', toolCallId: 'late-read', kind: 'other', title: 'Inspect README', status: 'in_progress' }, 'park-child');
87
+ update({ sessionUpdate: 'tool_call_update', toolCallId: 'late-read', status: 'completed' }, 'park-child');
88
+ text('Watching the child.\nCEZ:MONITORING');
89
+ complete(id);
90
+ await afterParityPark(input);
91
+ update({ sessionUpdate: 'tool_call_update', toolCallId: 'late-read', status: 'completed', rawOutput: 'Read the file.' }, 'park-child');
92
+ update({ sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'Late child text.' } }, 'park-child');
93
+ // Cursor task metadata can follow the regular completion (acp-subagents.ndjson).
94
+ // Sessionless extensions are attributed by their known child toolCallId even
95
+ // after prompt completion; this is the ordered barrier and a nested item update.
96
+ emit({ method: 'cursor/task', params: { toolCallId: 'late-read', description: 'Post-park child update processed.' } });
97
+ return;
98
+ }
48
99
  if (input.includes('mock:subagent')) {
49
100
  update({ sessionUpdate: 'tool_call', toolCallId: 'task-1', title: 'Review', kind: 'other', rawInput: { description: 'Review' }, status: 'in_progress' });
50
101
  update({ sessionUpdate: 'subagent_spawned', subagentSessionId: 'child', name: 'Reviewer', task: 'Review', capabilities: {}, _meta: { cursor: { toolCallId: 'task-1', agentId: 'child-agent' } } });
@@ -89,8 +140,8 @@ createInterface({ input: process.stdin }).on('line', async line => {
89
140
  switch (msg.method) {
90
141
  case 'initialize': reply(msg.id, { protocolVersion: 1, agentCapabilities: { loadSession: true, promptCapabilities: { image: true } } }); break;
91
142
  case 'cursor/list_available_models': reply(msg.id, { models: [{ value: model, name: model, configOptions: configOptions().filter(option => option.id !== 'model') }] }); break;
92
- case 'session/new': reply(msg.id, { sessionId, ...configuration() }); break;
93
- case 'session/load': sessionId = msg.params.sessionId; reply(msg.id, configuration()); break;
143
+ case 'session/new': consumeBootstrapCrash(); reply(msg.id, { sessionId, ...configuration() }); break;
144
+ case 'session/load': consumeBootstrapCrash(); sessionId = msg.params.sessionId; reply(msg.id, configuration()); break;
94
145
  case 'session/set_model': model = msg.params.modelId; reply(msg.id, {}); break;
95
146
  case 'session/set_config_option':
96
147
  if (msg.params.configId === 'model') model = msg.params.value;
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ // #587: the bootstrap write succeeds first, then stdin dies.
3
+ //
4
+ // The parent writes session/new while the pipe's read end is still open, so
5
+ // that write cannot fail. The child then drops the read end and never answers
6
+ // session/new. The runner's next write — the bootstrap stdin probe — is what
7
+ // must surface the death, not the 15s request timeout.
8
+ //
9
+ // The close is three steps, and the order matters. Closing fd 0 while
10
+ // readline's libuv handle still owns it aborts the child on some machines
11
+ // (SIGABRT — the fixture would become a process-death test); on other
12
+ // machines closeSync alone throws EBADF and never closes the read end, so the
13
+ // parent's write silently succeeds and only the 15s request timeout can catch
14
+ // the hang (#588). destroy() disarms the handle, closeSync(0) actually closes
15
+ // the pipe's read end (destroy alone leaves the fd open). The stdin 'error'
16
+ // handler swallows the EBADF from the next read attempt.
17
+ import { createInterface } from 'node:readline';
18
+ import { closeSync } from 'node:fs';
19
+ const emit = value => process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', ...value })}\n`);
20
+ const rl = createInterface({ input: process.stdin });
21
+ process.stdin.on('error', () => { /* the EBADF below is intentional */ });
22
+ rl.on('line', line => {
23
+ let msg; try { msg = JSON.parse(line); } catch { return; }
24
+ if (msg.method === 'initialize') {
25
+ emit({ id: msg.id, result: { protocolVersion: 1, agentCapabilities: { loadSession: true } } });
26
+ } else if (msg.method === 'session/new') {
27
+ // The parent's write already succeeded into the kernel buffer; only now
28
+ // does the read end die, with the child staying up.
29
+ rl.close();
30
+ process.stdin.destroy();
31
+ try { closeSync(0); } catch { /* already closed */ }
32
+ }
33
+ });
34
+ process.on('SIGTERM', () => { /* ignore — the child must outlive its stdin */ });
35
+ setInterval(() => {}, 1e6);
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ // #587: answers initialize, then dies on stdin while the child stays up.
3
+ // The parent's next write (session/new) must hit EPIPE and surface as a fast
4
+ // v1 error.
5
+ //
6
+ // Close before replying: a fast parent sends session/new as soon as it reads
7
+ // initialize, and that write must encounter the dead input pipe. The close is
8
+ // three steps, and the order matters. Closing fd 0 while readline's libuv
9
+ // handle still owns it aborts the child on some machines (SIGABRT — the
10
+ // fixture would become a process-death test); on other machines closeSync
11
+ // alone throws EBADF and never closes the read end, so the parent's write
12
+ // silently succeeds and only the 15s request timeout can catch the hang
13
+ // (#588). destroy() disarms the handle, closeSync(0) actually closes the
14
+ // pipe's read end (destroy alone leaves the fd open).
15
+ import { createInterface } from 'node:readline';
16
+ import { closeSync } from 'node:fs';
17
+ const emit = value => process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', ...value })}\n`);
18
+ const rl = createInterface({ input: process.stdin });
19
+ process.stdin.on('error', () => { /* the EBADF below is intentional */ });
20
+ rl.on('line', line => {
21
+ let msg; try { msg = JSON.parse(line); } catch { return; }
22
+ if (msg.method === 'initialize') {
23
+ rl.close();
24
+ process.stdin.destroy();
25
+ try { closeSync(0); } catch { /* already closed */ }
26
+ emit({ id: msg.id, result: { protocolVersion: 1, agentCapabilities: { loadSession: true } } });
27
+ }
28
+ });
29
+ process.on('SIGTERM', () => { /* ignore — the child must outlive its stdin */ });
30
+ setInterval(() => {}, 1e6);
@@ -11,6 +11,15 @@ import { createServer } from 'node:http';
11
11
 
12
12
  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');
13
13
  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 ?? '{}')); }
14
+ import * as parityGateFs from 'node:fs';
15
+
16
+ // #401: let the test observe the actual monitoring park before releasing late wire frames.
17
+ async function afterParityPark(prompt) {
18
+ const gate = /parity-release=([^\s"\\]+)/.exec(prompt)?.[1];
19
+ if (!gate) throw new Error('post-park scenario requires a release path');
20
+ while (!parityGateFs.existsSync(gate)) await new Promise(resolve => setTimeout(resolve, 10));
21
+ }
22
+
14
23
  const args = process.argv.slice(2);
15
24
  const arg = (flag, fallback) => {
16
25
  const i = args.indexOf(flag);
@@ -22,9 +31,30 @@ const SESSION_ID = 'ses_mock_1';
22
31
  const MESSAGE_ID = 'msg_mock_1';
23
32
 
24
33
  let sse = null;
25
- const send = (event) => {
34
+ const write = (event) => {
26
35
  if (sse) sse.write(`data: ${JSON.stringify(event)}\n\n`);
27
36
  };
37
+ // #505: like opencode 1.18.32, a prompt_async POSTed while the session is busy is
38
+ // steered into the running loop: its user message exists at once, and the next
39
+ // assistant message names it as parentID. Steers are read just before the turn's
40
+ // idle, unless the turn is `late` (the upstream lost wake: never read).
41
+ let turnActive = false;
42
+ let turnLate = false;
43
+ let steers = [];
44
+ let steerSerial = 0;
45
+ const send = (event) => {
46
+ const ownIdle = event?.type === 'session.idle' && (!event.properties?.sessionID || event.properties.sessionID === SESSION_ID);
47
+ if (ownIdle) {
48
+ if (!turnLate) for (const steer of steers) {
49
+ const id = `msg_steer_reply_${++steerSerial}`;
50
+ write({ type: 'message.updated', properties: { info: { ...info({}), id, parentID: steer.userId } } });
51
+ write({ type: 'message.part.updated', properties: { part: { id: `prt_${id}`, messageID: id, sessionID: SESSION_ID, type: 'text', text: steer.text } } });
52
+ }
53
+ steers = []; turnActive = false; turnLate = false;
54
+ }
55
+ write(event);
56
+ };
57
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
28
58
 
29
59
  /** Questions the runner can look up an id for. `replyQuestion`/`rejectQuestion`
30
60
  * resolve the id through `GET /question` before answering (see
@@ -51,10 +81,14 @@ const sendQuestionPart = (input) =>
51
81
  },
52
82
  },
53
83
  });
84
+ // The user message of the prompt being answered: opencode 1.18.32 creates it at the
85
+ // POST and names it as the assistant's parentID (#505 probe).
86
+ let currentUserId;
54
87
  const info = (extra) => ({
55
88
  id: MESSAGE_ID,
56
89
  sessionID: SESSION_ID,
57
90
  role: 'assistant',
91
+ ...(currentUserId ? { parentID: currentUserId } : {}),
58
92
  time: { created: 1760000000000 },
59
93
  modelID: 'mock-model',
60
94
  providerID: 'mock',
@@ -91,7 +125,8 @@ const server = createServer((req, res) => {
91
125
  send({ type: 'message.part.updated', properties: { part: {
92
126
  id: 'prt_answer', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: `human answer: ${body}${answerDone ? '\n\nCEZ:DONE' : ''}`,
93
127
  } } });
94
- send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
128
+ // #505 review: CEZ_MOCK_OPENCODE_REPLY_HOLD_MS keeps the answered turn running.
129
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), Number(process.env.CEZ_MOCK_OPENCODE_REPLY_HOLD_MS ?? 0));
95
130
  }, 30);
96
131
  const acknowledge = () => { res.writeHead(200, { 'content-type': 'application/json' }); res.end('{}'); };
97
132
  if (lateQuestionReply) setTimeout(acknowledge, 250);
@@ -112,9 +147,44 @@ const server = createServer((req, res) => {
112
147
  res.end(JSON.stringify({ error: 'agent prompt rejected' }));
113
148
  return;
114
149
  }
150
+ if (turnActive && url.endsWith('/prompt_async')) {
151
+ const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
152
+ const userId = `msg_steer_user_${++steerSerial}`;
153
+ // #505 review: acknowledge a steer later than the runner's lost-wake window.
154
+ const ackDelay = Number(process.env.CEZ_MOCK_OPENCODE_STEER_ACK_MS ?? 0);
155
+ if (ackDelay > 0) setTimeout(() => { res.writeHead(204); res.end(); }, ackDelay);
156
+ else { res.writeHead(204); res.end(); }
157
+ send({ type: 'message.updated', properties: { info: { id: userId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
158
+ send({ type: 'message.part.updated', properties: { part: { id: `prt_${userId}`, messageID: userId, sessionID: SESSION_ID, type: 'text', text } } });
159
+ steers.push({ userId, text });
160
+ return;
161
+ }
162
+ turnActive = true;
115
163
  // `prompt_async` semantics: acknowledge now, stream the turn over SSE.
116
164
  res.writeHead(200, { 'content-type': 'application/json' });
117
165
  res.end(JSON.stringify({ info: info({}), parts: [] }));
166
+ if (url.endsWith('/prompt_async')) {
167
+ currentUserId = `msg_user_${++steerSerial}`;
168
+ const text = JSON.parse(body).parts.map(part => part.text ?? '').join('\n');
169
+ send({ type: 'message.updated', properties: { info: { id: currentUserId, sessionID: SESSION_ID, role: 'user', time: { created: Date.now() } } } });
170
+ send({ type: 'message.part.updated', properties: { part: { id: `prt_${currentUserId}`, messageID: currentUserId, sessionID: SESSION_ID, type: 'text', text } } });
171
+ }
172
+ if (body.includes('mock:steer-tool')) {
173
+ send({ type: 'message.updated', properties: { info: info({}) } });
174
+ 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' } } } } });
175
+ await sleep(Number(process.env.CEZ_MOCK_STEER_MS ?? 600));
176
+ 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' } } } });
177
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
178
+ return;
179
+ }
180
+ if (body.includes('mock:steer-late')) {
181
+ send({ type: 'message.updated', properties: { info: info({}) } });
182
+ 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 } } } });
183
+ turnLate = true;
184
+ await sleep(300);
185
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
186
+ return;
187
+ }
118
188
  // The raw body is enough to spot a `mock:` marker — the prompt text is
119
189
  // inside it whatever part shape the runner used to wrap it.
120
190
  if (body.includes('mock:ci-wait')) {
@@ -133,6 +203,23 @@ const server = createServer((req, res) => {
133
203
  send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
134
204
  return;
135
205
  }
206
+ // SSE child attribution from __fixtures__/opencode/subtask-nested.ndjson.
207
+ if (body.includes('mock:subagent-after-park')) {
208
+ send({ type: 'message.updated', properties: { info: info({}) } });
209
+ send({ type: 'message.part.updated', properties: { part: { id: 'park-task', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'subtask', prompt: 'Review', description: 'Review', agent: 'general' } } });
210
+ send({ type: 'message.updated', properties: { info: { ...info({}), id: 'park-child-msg', sessionID: 'park-child', parentID: SESSION_ID, mode: 'subagent' } } });
211
+ 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 } } } });
212
+ 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 } } } });
213
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
214
+ await afterParityPark(body);
215
+ 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' } } } } });
216
+ 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.' } } } });
217
+ 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 } } } });
218
+ // Closed child scopes are ignored. This passive usage snapshot proves the
219
+ // preceding SSE frames have reached the runner before the test observes it.
220
+ send({ type: 'message.updated', properties: { info: info({ tokens: { input: 424240, output: 2, reasoning: 0, cache: { read: 0, write: 0 } } }) } });
221
+ return;
222
+ }
136
223
  if (body.includes('mock:subagent')) {
137
224
  // Wire shape from `__fixtures__/opencode/subtask-nested.ndjson`: a
138
225
  // `subtask` part on the parent message, then a child message whose info
@@ -183,6 +270,12 @@ const server = createServer((req, res) => {
183
270
  }, 60);
184
271
  return;
185
272
  }
273
+ if (body.includes('mock:provider-error-early')) {
274
+ // #505 review: the provider rejects before any assistant message exists.
275
+ send({ type: 'session.error', properties: { sessionID: SESSION_ID, error: { name: 'ProviderAuthError', data: { message: 'API key expired' } } } });
276
+ setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
277
+ return;
278
+ }
186
279
  if (body.includes('mock:provider-error')) {
187
280
  // The session-level error frame, wire shape copied verbatim from
188
281
  // `__fixtures__/opencode/session-error.ndjson`. #53: a bare upstream
@@ -220,6 +313,13 @@ const server = createServer((req, res) => {
220
313
  setTimeout(() => send({ type: 'session.idle', properties: { sessionID: SESSION_ID } }), 30);
221
314
  return;
222
315
  }
316
+ // #401: one completed text snapshot, without an earlier streaming part.
317
+ if (body.includes('mock:ask-snapshot')) {
318
+ send({ type: 'message.updated', properties: { info: info({}) } });
319
+ send({ type: 'message.part.updated', properties: { part: { id: 'ask-snapshot', messageID: MESSAGE_ID, sessionID: SESSION_ID, type: 'text', text: 'Choose a test library.\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}', time: { start: 1, end: 2 } } } });
320
+ send({ type: 'session.idle', properties: { sessionID: SESSION_ID } });
321
+ return;
322
+ }
223
323
  if (body.includes('mock:ask-bad')) {
224
324
  // Malformed: `questions` is not an array, so the runner must REJECT the
225
325
  // native question and finish the turn rather than wait for an answer to
@@ -8,7 +8,45 @@ process.on('SIGTERM', () => process.exit(143));
8
8
 
9
9
  if (process.env.CEZ_MOCK_CI_PR) { const { probeCiTool } = await import('./mock-ci-tool.mjs'); await probeCiTool('pi', process.argv.slice(2)); }
10
10
  const sessionId = '00000000-0000-4000-8000-0000000000pi';
11
- const send = (value) => process.stdout.write(`${JSON.stringify(value)}\n`);
11
+ const write = (value) => process.stdout.write(`${JSON.stringify(value)}\n`);
12
+ // #505: like pi 0.87, a prompt with streamingBehavior 'steer' joins the running
13
+ // turn. Steers accepted by a scripted turn are read just before it settles: a user
14
+ // message_start/end, then an echo, unless the turn is `late` (never read).
15
+ let activeTurn = null;
16
+ // Pi 0.87 defaults steeringMode to one-at-a-time (#551). Cezar's runner must
17
+ // send set_steering_mode all for a burst to reach the same next step.
18
+ let steeringMode = 'one-at-a-time';
19
+ // The prompt a queued handler is running; pi records it as the turn's user message.
20
+ let currentPrompt;
21
+ const send = (value) => {
22
+ if (value?.type === 'turn_start' && !activeTurn) {
23
+ activeTurn = { steers: [], late: false };
24
+ write(value);
25
+ if (currentPrompt !== undefined && process.env.CEZ_MOCK_PI_NO_USER_START !== '1') {
26
+ const message = { role: 'user', content: [{ type: 'text', text: currentPrompt }] };
27
+ currentPrompt = undefined;
28
+ write({ type: 'message_start', message });
29
+ write({ type: 'message_end', message });
30
+ }
31
+ return;
32
+ }
33
+ if (value?.type === 'agent_settled' && activeTurn) {
34
+ const turn = activeTurn; activeTurn = null;
35
+ const deliverNow = turn.late ? [] : (steeringMode === 'all' ? turn.steers : turn.steers.slice(0, 1));
36
+ const defer = turn.late ? turn.steers : (steeringMode === 'all' ? [] : turn.steers.slice(1));
37
+ for (const text of deliverNow) {
38
+ write({ type: 'message_start', message: { role: 'user', content: [{ type: 'text', text }] } });
39
+ write({ type: 'message_end', message: { role: 'user', content: [{ type: 'text', text }] } });
40
+ sendText([text]);
41
+ write({ type: 'message_end', message: { role: 'assistant', usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0, cost: { total: 0 } } } });
42
+ }
43
+ write(value);
44
+ // one-at-a-time leftover, or a late steer, runs as the next prompt.
45
+ for (const text of defer) queue = queue.then(() => { currentPrompt = text; return handle({ type: 'prompt', message: text }); });
46
+ return;
47
+ }
48
+ write(value);
49
+ };
12
50
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13
51
 
14
52
  /** One valid CEZ:ASK payload (spec #473), used by the `mock:ask` scenario. */
@@ -32,8 +70,21 @@ function sendTurnEnd(usage = { input: 10, output: 5, cacheRead: 0, cacheWrite: 0
32
70
  send({ type: 'agent_settled' });
33
71
  }
34
72
 
35
- for await (const line of readline.createInterface({ input: process.stdin })) {
73
+ const rl = readline.createInterface({ input: process.stdin });
74
+ let queue = Promise.resolve();
75
+ rl.on('line', (line) => {
36
76
  const command = JSON.parse(line);
77
+ if (command.type === 'prompt' && command.streamingBehavior === 'steer' && activeTurn) {
78
+ if (process.env.CEZ_MOCK_STDIN_FILE) appendFileSync(process.env.CEZ_MOCK_STDIN_FILE, `${JSON.stringify({ userText: command.message, imageCount: 0, streamingBehavior: 'steer' })}\n`);
79
+ activeTurn.steers.push(command.message);
80
+ write({ id: command.id, type: 'response', command: 'prompt', success: true });
81
+ return;
82
+ }
83
+ queue = queue.then(() => { currentPrompt = command.type === 'prompt' ? command.message : undefined; return handle(command); });
84
+ });
85
+ rl.on('close', () => { queue.then(() => process.exit(0)); });
86
+
87
+ async function handle(command) {
37
88
  // Testability hook, mirroring mock-claude: CEZ_MOCK_STDIN_FILE=<path> appends
38
89
  // each inbound prompt's text and image count, so tests can assert what the
39
90
  // runner actually wrote onto the RPC (harness parity's AgentRunSpec probes).
@@ -55,7 +106,7 @@ for await (const line of readline.createInterface({ input: process.stdin })) {
55
106
  thinkingLevel: 'medium',
56
107
  isStreaming: false,
57
108
  isCompacting: false,
58
- steeringMode: 'all',
109
+ steeringMode,
59
110
  followUpMode: 'one-at-a-time',
60
111
  autoCompactionEnabled: true,
61
112
  messageCount: 0,
@@ -68,6 +119,21 @@ for await (const line of readline.createInterface({ input: process.stdin })) {
68
119
  const { ciPrompt } = await import('./mock-ci-tool.mjs');
69
120
  sendText([await ciPrompt('pi', process.argv.slice(2), command.message)]);
70
121
  sendTurnEnd();
122
+ } else if (command.type === 'prompt' && command.message.includes('mock:steer-tool')) {
123
+ send({ id: command.id, type: 'response', command: 'prompt', success: true });
124
+ send({ type: 'agent_start' }); send({ type: 'turn_start' });
125
+ send({ type: 'tool_execution_start', toolCallId: 'tool-steer', toolName: 'bash', args: { command: 'wait' } });
126
+ await sleep(Number(process.env.CEZ_MOCK_STEER_MS ?? 600));
127
+ send({ type: 'tool_execution_end', toolCallId: 'tool-steer', toolName: 'bash', result: { content: [{ type: 'text', text: 'waited' }] }, isError: false });
128
+ sendText(['steer tool done']);
129
+ sendTurnEnd();
130
+ } else if (command.type === 'prompt' && command.message.includes('mock:steer-late')) {
131
+ send({ id: command.id, type: 'response', command: 'prompt', success: true });
132
+ send({ type: 'agent_start' }); send({ type: 'turn_start' });
133
+ sendText(['late window: final message already sent']);
134
+ activeTurn.late = true;
135
+ await sleep(300);
136
+ sendTurnEnd();
71
137
  } else if (command.type === 'prompt' && command.message.includes('mock:agent-echo')) {
72
138
  // rpc-lifecycle.ndjson's normal prompt/assistant/settled sequence.
73
139
  send({ id: command.id, type: 'response', command: 'prompt', success: true });
@@ -115,6 +181,13 @@ for await (const line of readline.createInterface({ input: process.stdin })) {
115
181
  send({ type: 'turn_start' });
116
182
  sendText(['parity done: the task is complete\n\nCEZ:DONE']);
117
183
  sendTurnEnd();
184
+ } else if (command.type === 'prompt' && command.message.includes('mock:ask-snapshot')) {
185
+ // #401: authoritative text_end without deltas, using rpc-lifecycle frames.
186
+ send({ id: command.id, type: 'response', command: 'prompt', success: true });
187
+ send({ type: 'agent_start' });
188
+ send({ type: 'turn_start' });
189
+ send({ type: 'message_update', message: {}, assistantMessageEvent: { type: 'text_end', contentIndex: 0, content: 'Choose a test library.\nCEZ:ASK {"questions":[{"header":"Library","question":"Which test library?","options":[{"label":"Vitest"},{"label":"Node test"}]}]}', partial: {} } });
190
+ sendTurnEnd();
118
191
  } else if (command.type === 'prompt' && command.message.includes('mock:ask-bad')) {
119
192
  // A marker whose JSON body is invalid. `parseAskMarker` must render no card
120
193
  // and the turn must still end (harness parity R4) — pi has no native ask
@@ -245,6 +318,12 @@ for await (const line of readline.createInterface({ input: process.stdin })) {
245
318
  });
246
319
  }, 250);
247
320
  }
321
+ } else if (command.type === 'set_steering_mode') {
322
+ if (command.mode === 'all' || command.mode === 'one-at-a-time') steeringMode = command.mode;
323
+ if (process.env.CEZ_MOCK_STDIN_FILE) {
324
+ try { appendFileSync(process.env.CEZ_MOCK_STDIN_FILE, `${JSON.stringify({ type: 'set_steering_mode', mode: command.mode })}\n`); } catch { /* best effort */ }
325
+ }
326
+ send({ ...(command.id ? { id: command.id } : {}), type: 'response', command: 'set_steering_mode', success: true });
248
327
  } else if (command.type === 'abort') {
249
328
  send({ type: 'response', command: 'abort', success: true });
250
329
  }
@@ -0,0 +1 @@
1
+ import{r as e}from"./rolldown-runtime-QTnfLwEv.js";import{i as t,t as n}from"./react-runtime-CCIEwYL0.js";import{C as r,D as i,I as a,M as o,N as s,O as c,Qn as l,S as u,T as d,b as f,j as p,k as m,un as h,w as g,x as _,z as v}from"./centered-state-DH8qtCz-.js";import{t as y}from"./utils-BTFBhZ55.js";import{_ as b,g as x,h as ee,m as te,p as S}from"./dropdown-menu-7KGQMoFj.js";var C=e(t(),1),w=n(),T=`Popover`,[E,ne]=o(T,[b]),D=b(),[O,k]=E(T),A=e=>{let{__scopePopover:t,children:n,open:r,defaultOpen:a,onOpenChange:o,modal:s=!1}=e,c=D(t),l=C.useRef(null),[u,d]=C.useState(!1),[f,p]=m({prop:r,defaultProp:a??!1,onChange:o,caller:T});return(0,w.jsx)(x,{...c,children:(0,w.jsx)(O,{scope:t,contentId:i(),triggerRef:l,open:f,onOpenChange:p,onOpenToggle:C.useCallback(()=>p(e=>!e),[p]),hasCustomAnchor:u,onCustomAnchorAdd:C.useCallback(()=>d(!0),[]),onCustomAnchorRemove:C.useCallback(()=>d(!1),[]),modal:s,children:n})})};A.displayName=T;var j=`PopoverAnchor`,M=C.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=k(j,n),a=D(n),{onCustomAnchorAdd:o,onCustomAnchorRemove:s}=i;return C.useEffect(()=>(o(),()=>s()),[o,s]),(0,w.jsx)(S,{...a,...r,ref:t})});M.displayName=j;var N=`PopoverTrigger`,P=C.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=k(N,n),a=D(n),o=v(t,i.triggerRef),c=(0,w.jsx)(s.button,{type:`button`,"aria-haspopup":`dialog`,"aria-expanded":i.open,"aria-controls":i.open?i.contentId:void 0,"data-state":J(i.open),...r,ref:o,onClick:p(e.onClick,i.onOpenToggle)});return i.hasCustomAnchor?c:(0,w.jsx)(S,{asChild:!0,...a,children:c})});P.displayName=N;var F=`PopoverPortal`,[re,I]=E(F,{forceMount:void 0}),L=e=>{let{__scopePopover:t,forceMount:n,children:i,container:a}=e,o=k(F,t);return(0,w.jsx)(re,{scope:t,forceMount:n,children:(0,w.jsx)(c,{present:n||o.open,children:(0,w.jsx)(r,{asChild:!0,container:a,children:i})})})};L.displayName=F;var R=`PopoverContent`,z=C.forwardRef((e,t)=>{let n=I(R,e.__scopePopover),{forceMount:r=n.forceMount,...i}=e,a=k(R,e.__scopePopover);return(0,w.jsx)(c,{present:r||a.open,children:a.modal?(0,w.jsx)(V,{...i,ref:t}):(0,w.jsx)(H,{...i,ref:t})})});z.displayName=R;var B=a(`PopoverContent.RemoveScroll`),V=C.forwardRef((e,t)=>{let n=k(R,e.__scopePopover),r=C.useRef(null),i=v(t,r),a=C.useRef(!1);return C.useEffect(()=>{let e=r.current;if(e)return f(e)},[]),(0,w.jsx)(_,{as:B,allowPinchZoom:!0,children:(0,w.jsx)(U,{...e,ref:i,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:p(e.onCloseAutoFocus,e=>{e.preventDefault(),a.current||n.triggerRef.current?.focus()}),onPointerDownOutside:p(e.onPointerDownOutside,e=>{let t=e.detail.originalEvent,n=t.button===0&&t.ctrlKey===!0,r=t.button===2||n;a.current=r},{checkForDefaultPrevented:!1}),onFocusOutside:p(e.onFocusOutside,e=>e.preventDefault(),{checkForDefaultPrevented:!1})})})}),H=C.forwardRef((e,t)=>{let n=k(R,e.__scopePopover),r=C.useRef(!1),i=C.useRef(!1);return(0,w.jsx)(U,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:t=>{e.onCloseAutoFocus?.(t),t.defaultPrevented||(r.current||n.triggerRef.current?.focus(),t.preventDefault()),r.current=!1,i.current=!1},onInteractOutside:t=>{e.onInteractOutside?.(t),t.defaultPrevented||(r.current=!0,t.detail.originalEvent.type===`pointerdown`&&(i.current=!0));let a=t.target;n.triggerRef.current?.contains(a)&&t.preventDefault(),t.detail.originalEvent.type===`focusin`&&i.current&&t.preventDefault()}})}),U=C.forwardRef((e,t)=>{let{__scopePopover:n,trapFocus:r,onOpenAutoFocus:i,onCloseAutoFocus:a,disableOutsidePointerEvents:o,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onInteractOutside:f,...p}=e,m=k(R,n),h=D(n);return u(),(0,w.jsx)(g,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:i,onUnmountAutoFocus:a,children:(0,w.jsx)(d,{asChild:!0,disableOutsidePointerEvents:o,onInteractOutside:f,onEscapeKeyDown:s,onPointerDownOutside:c,onFocusOutside:l,onDismiss:()=>m.onOpenChange(!1),deferPointerDownOutside:!0,children:(0,w.jsx)(ee,{"data-state":J(m.open),role:`dialog`,id:m.contentId,...h,...p,ref:t,style:{...p.style,"--radix-popover-content-transform-origin":`var(--radix-popper-transform-origin)`,"--radix-popover-content-available-width":`var(--radix-popper-available-width)`,"--radix-popover-content-available-height":`var(--radix-popper-available-height)`,"--radix-popover-trigger-width":`var(--radix-popper-anchor-width)`,"--radix-popover-trigger-height":`var(--radix-popper-anchor-height)`}})})})}),W=`PopoverClose`,G=C.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=k(W,n);return(0,w.jsx)(s.button,{type:`button`,...r,ref:t,onClick:p(e.onClick,()=>i.onOpenChange(!1))})});G.displayName=W;var K=`PopoverArrow`,q=C.forwardRef((e,t)=>{let{__scopePopover:n,...r}=e,i=D(n);return(0,w.jsx)(te,{...i,...r,ref:t})});q.displayName=K;function J(e){return e?`open`:`closed`}var Y=A,X=M,Z=P,ie=L,ae=z;function Q(e){let t=e.visualViewport;return t?Math.max(0,Math.round(e.innerHeight-t.height-t.offsetTop)):0}function oe(e,t,n,r=250){let i=e.visualViewport;if(!i)return t(0),()=>{};let a,o=()=>{let i=Q(e);t(i),n&&(clearTimeout(a),a=setTimeout(()=>n(i),r))};return t(Q(e)),i.addEventListener(`resize`,o),i.addEventListener(`scroll`,o),()=>{clearTimeout(a),i.removeEventListener(`resize`,o),i.removeEventListener(`scroll`,o),t(0)}}function se(e){let t=e.visualViewport;return t?{top:Math.max(0,Math.round(t.offsetTop)),bottom:Q(e)}:{top:0,bottom:0}}function ce(e,t=0){let n=typeof t==`number`?{top:t,right:t,bottom:t,left:t}:t;return{top:(n.top??0)+e.top,right:n.right??0,bottom:(n.bottom??0)+e.bottom,left:n.left??0}}function le(e,t){let n=e.visualViewport;if(!n)return t({top:0,bottom:0}),()=>{};let r=()=>t(se(e));return r(),n.addEventListener(`resize`,r),n.addEventListener(`scroll`,r),()=>{n.removeEventListener(`resize`,r),n.removeEventListener(`scroll`,r)}}function ue(){let[e,t]=(0,C.useState)({top:0,bottom:0});return(0,C.useEffect)(()=>le(window,e=>t(t=>t.top===e.top&&t.bottom===e.bottom?t:e)),[]),e}function de(e){let t=(0,C.useRef)(e);t.current=e,(0,C.useEffect)(()=>{let e=document.documentElement;return oe(window,t=>e.style.setProperty(`--kb`,`${t}px`),e=>t.current?.(e))},[])}var $=C.createContext(null);function fe({...e}){let t=C.useRef(!1);return(0,w.jsx)($,{value:t,children:(0,w.jsx)(Y,{"data-slot":`popover`,...e})})}function pe({onPointerDownCapture:e,onKeyDownCapture:t,...n}){let r=C.useContext($);return(0,w.jsx)(Z,{"data-slot":`popover-trigger`,...n,onPointerDownCapture:t=>{r&&(r.current=t.pointerType===`touch`),e?.(t)},onKeyDownCapture:e=>{r&&(r.current=!1),t?.(e)}})}function me({className:e,align:t=`center`,sideOffset:n=4,collisionPadding:r,onOpenAutoFocus:i,...a}){let o=ue(),s=C.useContext($);return(0,w.jsx)(ie,{children:(0,w.jsx)(ae,{"data-slot":`popover-content`,align:t,sideOffset:n,onOpenAutoFocus:e=>{let t=s?.current;if(s&&(s.current=!1),i?.(e),e.defaultPrevented||!t)return;let n=e.target;n instanceof HTMLElement&&n.querySelector(`[cmdk-input]`)&&(e.preventDefault(),n.focus({preventScroll:!0}))},collisionPadding:ce(o,r),className:y(`z-50 w-72 max-w-(--radix-popover-content-available-width) [overflow-wrap:anywhere] origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95`,e),...a})})}function he({...e}){return(0,w.jsx)(X,{"data-slot":`popover-anchor`,...e})}function ge(){let e=h(),t=l(),n=(e.data?.profiles??[]).map(e=>({provider:e.provider,id:e.id,label:e.label,configDir:e.configDir})),r=t.data?.info?.root;return{accounts:n,repoAccount:r?e.data?.selections?.[r]:void 0}}function _e(e,t){return e.filter(e=>e.provider===t).length>1}export{me as a,he as i,ge as n,pe as o,fe as r,de as s,_e as t};