@pat-lewczuk/cezar 0.1.3 → 0.1.5

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 (167) hide show
  1. package/README.md +124 -37
  2. package/dist/config.d.ts +46 -0
  3. package/dist/config.js +31 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/core/agent-runner.d.ts +16 -0
  6. package/dist/core/agent-runner.js +9 -0
  7. package/dist/core/agent-runner.js.map +1 -1
  8. package/dist/core/claude-cli-runner.d.ts +6 -0
  9. package/dist/core/claude-cli-runner.js +27 -43
  10. package/dist/core/claude-cli-runner.js.map +1 -1
  11. package/dist/core/claude-ui-mapper.d.ts +57 -0
  12. package/dist/core/claude-ui-mapper.js +370 -0
  13. package/dist/core/claude-ui-mapper.js.map +1 -0
  14. package/dist/core/codex-app-server-runner.js +42 -5
  15. package/dist/core/codex-app-server-runner.js.map +1 -1
  16. package/dist/core/codex-ui-mapper.d.ts +53 -0
  17. package/dist/core/codex-ui-mapper.js +411 -0
  18. package/dist/core/codex-ui-mapper.js.map +1 -0
  19. package/dist/core/opencode-server-runner.js +42 -5
  20. package/dist/core/opencode-server-runner.js.map +1 -1
  21. package/dist/core/opencode-ui-mapper.d.ts +105 -0
  22. package/dist/core/opencode-ui-mapper.js +578 -0
  23. package/dist/core/opencode-ui-mapper.js.map +1 -0
  24. package/dist/core/process-usage.d.ts +63 -0
  25. package/dist/core/process-usage.js +181 -0
  26. package/dist/core/process-usage.js.map +1 -0
  27. package/dist/core/tool-display.d.ts +27 -0
  28. package/dist/core/tool-display.js +150 -0
  29. package/dist/core/tool-display.js.map +1 -0
  30. package/dist/core/ui-events.d.ts +294 -0
  31. package/dist/core/ui-events.js +25 -0
  32. package/dist/core/ui-events.js.map +1 -0
  33. package/dist/git-worktree.d.ts +21 -0
  34. package/dist/git-worktree.js +48 -4
  35. package/dist/git-worktree.js.map +1 -1
  36. package/dist/handoff.d.ts +1 -1
  37. package/dist/handoff.js +2 -0
  38. package/dist/handoff.js.map +1 -1
  39. package/dist/index.js +34 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/pack-check.d.ts +17 -0
  42. package/dist/pack-check.js +27 -0
  43. package/dist/pack-check.js.map +1 -0
  44. package/dist/runs/store.d.ts +82 -1
  45. package/dist/runs/store.js +57 -5
  46. package/dist/runs/store.js.map +1 -1
  47. package/dist/runs/title-summary.d.ts +15 -0
  48. package/dist/runs/title-summary.js +84 -0
  49. package/dist/runs/title-summary.js.map +1 -0
  50. package/dist/runs/ui-event-sink.d.ts +81 -0
  51. package/dist/runs/ui-event-sink.js +210 -0
  52. package/dist/runs/ui-event-sink.js.map +1 -0
  53. package/dist/server/capabilities.d.ts +18 -0
  54. package/dist/server/capabilities.js +23 -0
  55. package/dist/server/capabilities.js.map +1 -0
  56. package/dist/server/forge/github.d.ts +39 -0
  57. package/dist/server/forge/github.js +369 -0
  58. package/dist/server/forge/github.js.map +1 -0
  59. package/dist/server/forge/index.d.ts +24 -0
  60. package/dist/server/forge/index.js +45 -0
  61. package/dist/server/forge/index.js.map +1 -0
  62. package/dist/server/forge/types.d.ts +80 -0
  63. package/dist/server/forge/types.js +2 -0
  64. package/dist/server/forge/types.js.map +1 -0
  65. package/dist/server/git-changes.d.ts +182 -0
  66. package/dist/server/git-changes.js +424 -0
  67. package/dist/server/git-changes.js.map +1 -0
  68. package/dist/server/git.js +12 -1
  69. package/dist/server/git.js.map +1 -1
  70. package/dist/server/github.d.ts +8 -29
  71. package/dist/server/github.js +7 -139
  72. package/dist/server/github.js.map +1 -1
  73. package/dist/server/open-in-app.d.ts +19 -0
  74. package/dist/server/open-in-app.js +134 -0
  75. package/dist/server/open-in-app.js.map +1 -0
  76. package/dist/server/pr.d.ts +5 -20
  77. package/dist/server/pr.js +4 -95
  78. package/dist/server/pr.js.map +1 -1
  79. package/dist/server/server.d.ts +30 -1
  80. package/dist/server/server.js +448 -19
  81. package/dist/server/server.js.map +1 -1
  82. package/dist/server/static-ui.d.ts +49 -0
  83. package/dist/server/static-ui.js +92 -0
  84. package/dist/server/static-ui.js.map +1 -0
  85. package/dist/update-check.d.ts +12 -0
  86. package/dist/update-check.js +41 -0
  87. package/dist/update-check.js.map +1 -0
  88. package/dist/workflows/run.d.ts +90 -0
  89. package/dist/workflows/run.js +412 -29
  90. package/dist/workflows/run.js.map +1 -1
  91. package/dist/workflows/types.d.ts +16 -16
  92. package/package.json +44 -6
  93. package/scripts/check-pack.mjs +46 -0
  94. package/scripts/dev.mjs +74 -0
  95. package/scripts/mock-claude.mjs +54 -2
  96. package/scripts/test-process-usage.mjs +47 -0
  97. package/web/dist/assets/arrow-left-BE957rJs.js +1 -0
  98. package/web/dist/assets/bundle-mjs-DLPpRFyl.js +1 -0
  99. package/web/dist/assets/centered-state-zgEVNWsj.js +43 -0
  100. package/web/dist/assets/chunk-BO2N2NFS-f5igTtpB.js +131 -0
  101. package/web/dist/assets/compare-variants-ChHCcSQX.js +1 -0
  102. package/web/dist/assets/core-BuMqAF_1.js +12 -0
  103. package/web/dist/assets/css-BsVw1vtW.js +1 -0
  104. package/web/dist/assets/dialog-DDRjj-uK.js +1 -0
  105. package/web/dist/assets/diff-view-CWRO0GMe.js +4 -0
  106. package/web/dist/assets/diff-woXpYk--.js +1 -0
  107. package/web/dist/assets/dist-id2CeyV-.js +1 -0
  108. package/web/dist/assets/ellipsis-vertical-DJPAfccS.js +1 -0
  109. package/web/dist/assets/engine-javascript-FQvzRocB.js +141 -0
  110. package/web/dist/assets/folder-RbRLfIX-.js +1 -0
  111. package/web/dist/assets/git-pull-request-DJkAMT0f.js +1 -0
  112. package/web/dist/assets/git-toolbar-C4jo602L.js +1 -0
  113. package/web/dist/assets/github-BOIzQFwE.js +1 -0
  114. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  115. package/web/dist/assets/highlighted-body-OFNGDK62-DGHHie_q.js +1 -0
  116. package/web/dist/assets/highlighter-BKVEsACy.js +3 -0
  117. package/web/dist/assets/html-BY76lXLH.js +1 -0
  118. package/web/dist/assets/index-Bz_r8Jt3.css +2 -0
  119. package/web/dist/assets/index-Cv1pQs9U.js +11 -0
  120. package/web/dist/assets/input-BRsSo4xH.js +1 -0
  121. package/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  122. package/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  123. package/web/dist/assets/javascript-BgS3c2Ky.js +1 -0
  124. package/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  125. package/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  126. package/web/dist/assets/json-qhed-kSA.js +1 -0
  127. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  128. package/web/dist/assets/jsx-Bz0zcwM4.js +1 -0
  129. package/web/dist/assets/lib-CNGa3Dig.js +1 -0
  130. package/web/dist/assets/loader-circle-BV3DO9mE.js +1 -0
  131. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  132. package/web/dist/assets/markdown-CR_HLGTs.js +1 -0
  133. package/web/dist/assets/mermaid-GHXKKRXX-DM2VsGBG.js +1 -0
  134. package/web/dist/assets/open-mercato-toBr6SOa.svg +11 -0
  135. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  136. package/web/dist/assets/react-dom-Ddtik4qM.js +1 -0
  137. package/web/dist/assets/refresh-cw-DCa-Rlwz.js +1 -0
  138. package/web/dist/assets/repo-git-BuyJiqna.js +1 -0
  139. package/web/dist/assets/run-diff-BocG6LVd.js +3 -0
  140. package/web/dist/assets/run-header-CDKw22ek.js +1 -0
  141. package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  142. package/web/dist/assets/search-x-CoCyl7zT.js +1 -0
  143. package/web/dist/assets/shellscript-CLZ0U2zV.js +1 -0
  144. package/web/dist/assets/skill-detail-CcFFKmGO.js +1 -0
  145. package/web/dist/assets/skills-Bc_4U3yq.js +1 -0
  146. package/web/dist/assets/skills-ynuO5oCr.js +1 -0
  147. package/web/dist/assets/sql-BsFa4tDR.js +1 -0
  148. package/web/dist/assets/square-terminal-B6PZX4Qp.js +1 -0
  149. package/web/dist/assets/tab-link-C77EP3i6.js +1 -0
  150. package/web/dist/assets/task-changes-DsDgN8-x.js +1 -0
  151. package/web/dist/assets/task-commits-Dve6Fozc.js +1 -0
  152. package/web/dist/assets/task-files-BDMc_z0B.js +2 -0
  153. package/web/dist/assets/task-thread-CmRuPj9H.js +5 -0
  154. package/web/dist/assets/trash-2-CDtxmmc8.js +1 -0
  155. package/web/dist/assets/triangle-alert-6su4Js5O.js +1 -0
  156. package/web/dist/assets/tsx-udAQXfEw.js +1 -0
  157. package/web/dist/assets/typescript-bsJCZSQ-.js +1 -0
  158. package/web/dist/assets/upload-Bp7hQxll.js +1 -0
  159. package/web/dist/assets/use-desktop-CyEWsxSU.js +3 -0
  160. package/web/dist/assets/utils-rQGbOrwc.js +1 -0
  161. package/web/dist/assets/workflows-J3z3kYic.js +11 -0
  162. package/web/dist/assets/x-Br_jIDBw.js +1 -0
  163. package/web/dist/assets/yaml-rwi0_p6S.js +1 -0
  164. package/web/dist/index.html +67 -0
  165. package/web/app.js +0 -3428
  166. package/web/index.html +0 -108
  167. package/web/style.css +0 -1767
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  # cezar ⚡
4
4
 
5
- **A local cockpit for running and tracking AI coding-agent tasks in your repo.**
5
+ **Parallel coding agents orchestrator** — a local cockpit for running and
6
+ tracking AI coding-agent tasks in your repo.
6
7
 
7
- Type a task, pick a workflow, watch the agent work live — steps, tool calls,
8
- tokens, diffs in a browser cockpit that runs entirely on your machine.
9
- Your `claude` login, your `gh`, your files. No accounts, no database, no cloud.
8
+ Type a task, pick a workflow and an agent **Claude Code, Codex or OpenCode,
9
+ or a mix of them per step** and watch it work live: steps, tool calls,
10
+ tokens, diffs, in a browser cockpit that runs entirely on your machine.
11
+ Your CLI logins, your `gh`, your files. No accounts, no database, no cloud.
10
12
 
11
- [What it solves](#what-it-solves) · [Who it's for](#who-its-for) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Core concepts](#core-concepts) · [Cockpit tour](#cockpit-tour)
13
+ [A look inside](#a-look-inside) · [What it solves](#what-it-solves) · [Who it's for](#who-its-for) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Core concepts](#core-concepts) · [Cockpit tour](#cockpit-tour) · [Agent backends](#coding-agent-backends)
12
14
 
13
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](#license)
14
16
  ![Node 20+](https://img.shields.io/badge/Node-20%2B-339933)
@@ -29,6 +31,18 @@ That's the whole setup. If your `claude` CLI is logged in (Pro/Max) and `gh` is
29
31
  authenticated, there is nothing else to configure. State lives in `.ai/cezar/`
30
32
  inside your repo — plain JSON, NDJSON and Markdown you can `cat` and fix by hand.
31
33
 
34
+ ## A look inside
35
+
36
+ Click any thumbnail for the full-size screenshot.
37
+
38
+ | Watch a run live | The review gate | Parallel variants |
39
+ |:--:|:--:|:--:|
40
+ | [![A running task streaming agent text, tool calls and screenshots live](docs/screenshots/live-run.png)](docs/screenshots/live-run.png) | [![A finished run parked at the review gate — read the diff, send notes back, open the PR, or accept](docs/screenshots/review-gate.png)](docs/screenshots/review-gate.png) | [![Two competing variants of the same task compared side by side — pick the winner](docs/screenshots/variants-compare.png)](docs/screenshots/variants-compare.png) |
41
+ | *Every step, tool call, token and screenshot — streamed as it happens.* | *Nothing auto-merges: inspect the diff, send notes into the same session, or push a draft PR.* | *Run a task ×2/×3 in isolated worktrees, compare the diffs, keep one.* |
42
+ | **Plan before you run** | **Workflow builder** | **GitHub, one click away** |
43
+ | [![The Plan overlay — an AI-drafted chain of steps you approve before anything runs](docs/screenshots/plan-chain.png)](docs/screenshots/plan-chain.png) | [![The workflow builder — drag skills into an ordered chain of agent steps and shell checks](docs/screenshots/workflow-builder.png)](docs/screenshots/workflow-builder.png) | [![The GitHub tab — hand an open issue to the agent with a workflow and skills](docs/screenshots/github-issues.png)](docs/screenshots/github-issues.png) |
44
+ | *Press **Plan first** and review the proposed chain — trim, reorder, then start.* | *Stitch skills and shell checks into a reusable YAML chain, no code.* | *Open issues and PRs via your `gh` — run the agent straight on an issue.* |
45
+
32
46
  ---
33
47
 
34
48
  ## What it solves
@@ -51,6 +65,10 @@ locally under *your* subscription, and a cockpit shows you exactly what it's doi
51
65
  - **Losing a session when it fails.** Every run records its `claude` session id.
52
66
  Take it over interactively in one click (`claude --resume <id>`), or continue it
53
67
  in-process from the cockpit.
68
+ - **Locked into one agent vendor.** Most tools wed you to a single CLI. cezar
69
+ drives **Claude Code, Codex and OpenCode** through one runner seam — set a
70
+ default, pick a backend per task, or mix them inside one workflow (implement
71
+ with one agent, review with another). See [Agent backends](#coding-agent-backends).
54
72
  - **Setup tax.** No wizard, no env vars, no schema. Skills are Markdown, workflows
55
73
  are short YAML, and everything degrades: no `gh` → works without PRs, no network
56
74
  → local skills still load, no `.ai/skills` → the bare prompt still runs.
@@ -73,8 +91,10 @@ locally under *your* subscription, and a cockpit shows you exactly what it's doi
73
91
 
74
92
  ## Quick start
75
93
 
76
- **Prerequisites:** Node 20+, the [`claude` CLI](https://github.com/anthropics/claude-code)
77
- logged in (Pro/Max subscription), and — optionally — `git` and the `gh` CLI.
94
+ **Prerequisites:** Node 20+, at least one logged-in agent CLI — the
95
+ [`claude` CLI](https://github.com/anthropics/claude-code) (Pro/Max subscription),
96
+ the [`codex` CLI](https://github.com/openai/codex), or
97
+ [OpenCode](https://opencode.ai) — and, optionally, `git` and the `gh` CLI.
78
98
 
79
99
  ```bash
80
100
  cd your-repo
@@ -91,8 +111,8 @@ npx cezar-cli init # scaffold .ai/cez
91
111
  ```
92
112
 
93
113
  Both the `cezar` and `cez` commands are installed, so once it's on your PATH you
94
- can run either. No API key is ever used — cezar shells out to your logged-in
95
- `claude` CLI.
114
+ can run either. No API key is ever used — cezar shells out to whichever agent
115
+ CLIs you are already logged into, `claude` by default.
96
116
 
97
117
  > **Just kicking the tires?** Set `CEZ_DRY_RUN=1` to run against a bundled mock
98
118
  > instead of the real CLI — the whole cockpit works with no `claude` login, so
@@ -103,8 +123,9 @@ can run either. No API key is ever used — cezar shells out to your logged-in
103
123
  ## How it works
104
124
 
105
125
  You describe a task. cezar runs it as a **workflow** — an ordered list of agent
106
- steps and shell checks — shelling out to your `claude` CLI in headless
107
- stream-json mode. Each task gets its own git worktree; the cockpit streams every
126
+ steps and shell checks — shelling out to your locally installed agent CLI
127
+ (Claude Code by default; Codex and OpenCode are drop-in alternatives, per task
128
+ or per step). Each task gets its own git worktree; the cockpit streams every
108
129
  event live and parks the run at a review gate when there's a diff to inspect.
109
130
 
110
131
  ```
@@ -117,15 +138,15 @@ event live and parks the run at a review gate when there's a diff to inspect.
117
138
 
118
139
 
119
140
  ┌──────────────────────────────┐ ┌───────────────────────────────┐
120
- │ git worktree per task │ │ claude CLI (your login)
121
- │ (isolated branch, parallel) │◄───►│ stream-json · default-deny
122
- └──────────────────────────────┘ │ tools · acceptEdits in-repo
141
+ │ git worktree per task │ │ agent CLI (your login)
142
+ │ (isolated branch, parallel) │◄───►│ claude · codex · opencode
143
+ └──────────────────────────────┘ │ default-deny · acceptEdits
123
144
  │ └───────────────────────────────┘
124
145
  │ agent text · tool calls · tool results · tokens · cost
125
146
 
126
147
  ┌─────────────┐ SSE (replay + live) ┌──────────────────────────┐
127
148
  │ .ai/cezar/ │ ──────────────────────► │ cockpit localhost:4321 │
128
- │ JSON·NDJSON │ │ Runs · Repo · GitHub · │
149
+ │ JSON·NDJSON │ │ Tasks · Git · GitHub · │
129
150
  │ ·Markdown │ │ Skills · Workflows │
130
151
  └─────────────┘ └──────────────────────────┘
131
152
 
@@ -155,8 +176,8 @@ Three words, no jargon — **task**, **skill**, **chain**:
155
176
  shape *how* the agent reasons without touching code.
156
177
  - **Chains (workflows)** stitch steps into a pipeline: agent steps plus shell
157
178
  checks, with bounded `onFail` retry loops. Write the YAML yourself, build one by
158
- drag-ordering skills in the **Workflows** tab, or press **Plan** and let the AI
159
- draft a chain for your task that you review, trim and start. The built-in
179
+ drag-ordering skills in the **Workflows** tab, or press **Plan first** and let the
180
+ AI draft a chain for your task that you review, trim and start. The built-in
160
181
  `quick-task` (one agent step) works with zero setup.
161
182
 
162
183
  Two moves that make the cockpit worth the browser tab:
@@ -172,19 +193,22 @@ Two moves that make the cockpit worth the browser tab:
172
193
 
173
194
  ## Cockpit tour
174
195
 
175
- Six tabs, one browser window, all live over Server-Sent Events:
196
+ Seven views, one browser window, all live over Server-Sent Events:
176
197
 
177
- | Tab | What's in it |
198
+ | View | What's in it |
178
199
  |---|---|
179
- | **Runs** | Every task with its status, live event stream (agent text · tool calls · tool results · pasted/generated screenshots), tokens and cost. Continue, cancel, open in terminal (`claude --resume`), review the diff, or push a draft PR. |
200
+ | **Tasks** | Every task with its status, live event stream (agent text · tool calls · tool results · pasted/generated screenshots), tokens and cost. Continue, cancel, open in terminal (`claude --resume`), review the diff, or push a draft PR. |
180
201
  | **Inbox** | Follow-ups an agent left behind (`todos.json`) — one click turns a suggestion into the next task, pre-wired to its suggested skill. |
181
- | **Repo** | Branch, working-tree status, diff vs HEAD, recent commits (click one for its inline patch + GitHub link), and the configurable base branch that worktrees fork from and PRs target. |
182
- | **GitHub** | Open issues and PRs of the repo's origin, read through your logged-in `gh`. Drag an issue onto the task box to prefill the prompt. |
202
+ | **Git** | Branch, working-tree status, diff vs HEAD, recent commits (click one for its inline patch + GitHub link), and the configurable base branch that worktrees fork from and PRs target. |
203
+ | **GitHub** | Open issues and PRs of the repo's origin, read through your logged-in `gh`. Hand an issue straight to the agent pick a workflow and skills, one click runs it. |
183
204
  | **Skills** | Local skills plus the team skills repo, with a rendered body + prompt preview. Refresh pulls the latest from the remote. |
184
205
  | **Workflows** | Build a chain by drag-ordering skills, save it as portable YAML, import/export, or delete. Built-ins always come back. |
206
+ | **Settings** | Appearance (dark/light theme, accent, density), agent backends, notifications, and the skills catalog. |
185
207
 
186
- The whole GUI is dependency-free vanilla JS with no build step, a dark/light
187
- theme toggle, and bookmarklets that launch a task straight from a GitHub page.
208
+ The cockpit is a React app served pre-built from the package — `npx cezar-cli`
209
+ still means no build step and no dev server on your machine with a dark/light
210
+ theme, a ⌘K command palette, and bookmarklets that launch a task straight from
211
+ a GitHub page.
188
212
 
189
213
  ---
190
214
 
@@ -201,6 +225,7 @@ steps:
201
225
  prompt: "{{task}}"
202
226
  skill: project-conventions # optional — from .ai/skills or .ai/cezar/skills
203
227
  # model: opus # optional per-step model override
228
+ # runner: codex # optional per-step backend: claude · codex · opencode
204
229
  # allowedTools: [Read, Edit, Write, Grep, Glob, Bash]
205
230
  - id: verify
206
231
  name: Verify
@@ -226,11 +251,13 @@ skills: [reproduce, root-cause, implement, self-review]
226
251
 
227
252
  ## How it runs agents
228
253
 
229
- cezar shells out to your locally installed, logged-in `claude` CLI in headless
230
- `stream-json` mode — **your subscription, no API key**. Tool access is
231
- default-deny via `--allowedTools`, and edits are auto-accepted
232
- (`--permission-mode acceptEdits`) inside the task's worktree. Nothing runs on a
233
- server you don't own.
254
+ cezar shells out to your locally installed, logged-in agent CLI
255
+ **your subscription, no API key**. With the default Claude Code backend that
256
+ means headless `stream-json` mode, tool access default-deny via
257
+ `--allowedTools`, and edits auto-accepted (`--permission-mode acceptEdits`)
258
+ inside the task's worktree. Codex and OpenCode are driven through their own
259
+ native protocols — see [Coding agent backends](#coding-agent-backends).
260
+ Nothing runs on a server you don't own.
234
261
 
235
262
  Useful environment variables:
236
263
 
@@ -238,10 +265,62 @@ Useful environment variables:
238
265
  |---|---|
239
266
  | `CEZ_DRY_RUN=1` | Use the bundled mock instead of the real `claude` CLI — the entire cockpit works offline, for demos and development. |
240
267
  | `CEZ_CLAUDE_BIN=/path/to/claude` | Override which `claude` binary is used. |
268
+ | `CEZ_CODEX_BIN=/path/to/codex` | Override which `codex` binary is used. |
269
+ | `CEZ_OPENCODE_BIN=/path/to/opencode` | Override which `opencode` binary is used. |
241
270
  | `GITHUB_TOKEN` | Fallback for GitHub reads/PRs when `gh` isn't authenticated. |
242
271
 
243
272
  ---
244
273
 
274
+ ## Coding agent backends
275
+
276
+ cezar is not married to one vendor. Every agent step runs through a single
277
+ `AgentRunner` seam with three built-in backends:
278
+
279
+ | Backend | CLI | How cezar drives it |
280
+ |---|---|---|
281
+ | **Claude Code** (default) | [`claude`](https://github.com/anthropics/claude-code) | Headless `stream-json` mode. |
282
+ | **Codex** | [`codex`](https://github.com/openai/codex) | `codex app-server` — JSON-RPC over stdio, the same transport the Codex IDE extensions use. |
283
+ | **OpenCode** | [`opencode`](https://opencode.ai) | `opencode serve` — a local HTTP server with an SSE event stream. |
284
+
285
+ On startup cezar probes which CLIs are installed and the cockpit only offers
286
+ the backends it found — install any one of the three and you're operational.
287
+
288
+ **Pick a backend at three levels** (most specific wins):
289
+
290
+ 1. **Config default** — `"defaultRunner": "codex"` in `.ai/cezar/config.json`.
291
+ 2. **Per task** — the backend picker next to the task box in the cockpit.
292
+ 3. **Per workflow step** — `runner:` on any step in the YAML.
293
+
294
+ Per-step overrides are what make **mixed-agent strategies** a one-liner:
295
+ implement with one agent, review with another, and let a shell check referee:
296
+
297
+ ```yaml
298
+ name: implement-and-cross-review
299
+ steps:
300
+ - id: implement
301
+ name: Implement
302
+ prompt: "{{task}}"
303
+ runner: codex # one vendor writes the code…
304
+ - id: review
305
+ name: Cross-review
306
+ prompt: "Review the diff produced for: {{task}}. Fix real issues only."
307
+ runner: claude # …another one reviews it
308
+ - id: verify
309
+ name: Verify
310
+ command: "npm test"
311
+ onFail: { retry: implement, max: 2 }
312
+ ```
313
+
314
+ Parallel variants (×2/×3) of one task share that task's backend — mixing
315
+ happens per task and per step, not inside a variant group.
316
+
317
+ The seam is deliberately small: a backend is one class implementing the
318
+ `AgentRunner` interface (`src/core/agent-runner.ts`) that turns a prompt into
319
+ a stream of normalized events. Other CLIs — pi, aider, whatever ships next —
320
+ can slot in the same way.
321
+
322
+ ---
323
+
245
324
  ## Configuration (optional)
246
325
 
247
326
  Zero config is the default — everything below is opt-in via
@@ -252,8 +331,9 @@ never blocks startup):
252
331
  {
253
332
  "skillsRepos": [{ "repo": "open-mercato/skills", "ref": "main" }], // team skills; [] disables
254
333
  "maxParallel": 2, // how many tasks may run at once (non-git dirs always run 1)
255
- "plannerModel": "sonnet", // model the Plan button uses to draft chains
256
- "baseBranch": "develop" // branch worktrees fork from + PRs target (also settable in the Repo tab)
334
+ "defaultRunner": "claude", // agent backend: "claude" (default) · "codex" · "opencode"
335
+ "plannerModel": "sonnet", // model the "Plan first" button uses to draft chains
336
+ "baseBranch": "develop" // branch worktrees fork from + PRs target (also settable in the Git tab)
257
337
  }
258
338
  ```
259
339
 
@@ -266,15 +346,22 @@ git-ignored automatically; your workflows and skills stay committable.
266
346
 
267
347
  ```bash
268
348
  npm install
269
- npm run dev # tsx src/index.ts the cockpit, live-reloaded
270
- npm run build # tsc dist/
271
- npm run typecheck # tsc --noEmit
349
+ npm run dev # server (API :4321) + Vite dev server, opens the cockpit in the browser
350
+ npm run dev:server # tsx src/index.ts — the API server alone
351
+ npm run dev:web # Vite dev server alone (proxies /api to :4321)
352
+ npm run build # tsc → dist/, vite build → web/dist/, then the pack gate
353
+ npm run typecheck # server + web (tsc --noEmit)
354
+ npm test # vitest — server + cockpit unit suites
355
+ npm run test:unit # node:test — fast core-module tests
356
+ npm run test:package # pack/install and exercise the built CLI
357
+ npm run test:e2e # real-browser cockpit suite (agent-browser)
272
358
  ```
273
359
 
274
360
  The stack is deliberately small: **TypeScript** (strict, ESM), **Hono** + SSE for
275
- the server, **Zod** at every boundary, **YAML** for workflows, and **vanilla JS**
276
- for the GUI (no framework, no bundler). Every module is meant to be read in one
277
- sitting.
361
+ the server, **Zod** at every boundary, **YAML** for workflows, and a **React 19 +
362
+ Vite + Tailwind v4 + shadcn/ui** cockpit shipped pre-built in `web/dist/` the
363
+ published package carries the built app, so `npx` users never run a bundler.
364
+ Every module is meant to be read in one sitting.
278
365
 
279
366
  ---
280
367
 
package/dist/config.d.ts CHANGED
@@ -32,6 +32,13 @@ declare const configSchema: z.ZodObject<{
32
32
  }>, "many">>;
33
33
  /** How many tasks may run at once (spec 006). Non-git dirs always run 1. */
34
34
  maxParallel: z.ZodDefault<z.ZodNumber>;
35
+ /**
36
+ * Per-task memory ceiling in MiB (whole process tree). When a running task's
37
+ * RSS crosses this the engine pauses it with a warning and lets the queue
38
+ * advance (#memory-guard). 0 / unset = no limit. `.catch(undefined)` keeps
39
+ * the key additive-safe: a bad value degrades to "no limit".
40
+ */
41
+ memoryLimitMb: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
35
42
  /**
36
43
  * Which agent backend a task uses unless overridden per task (GUI) or per
37
44
  * step (workflow). The GUI only offers runners actually installed; this is
@@ -45,6 +52,35 @@ declare const configSchema: z.ZodObject<{
45
52
  * Unset = the branch currently checked out. Settable from the Repo tab.
46
53
  */
47
54
  baseBranch: z.ZodOptional<z.ZodString>;
55
+ /**
56
+ * Default extra system prompt applied to every run's agent steps (claude:
57
+ * `--append-system-prompt`; codex/opencode: prepended to the opening user
58
+ * message — the AgentRunSpec seam handles the per-backend delivery).
59
+ * Settings is the single edit place; `POST /api/runs` can override it per
60
+ * run (`systemPrompt`). `.catch(undefined)` keeps the key additive-safe: a
61
+ * bad value degrades to unset without discarding the rest of the config.
62
+ */
63
+ systemPrompt: z.ZodCatch<z.ZodOptional<z.ZodString>>;
64
+ /**
65
+ * Per-runner default model preset (Settings → Agents, redesign R6 1.5): the
66
+ * model id the composer preselects for that runner. Missing = auto (the
67
+ * runner decides). A capability (`model`), never a vendor config format.
68
+ * `.catch(undefined)` keeps the key additive-safe like `systemPrompt`: a
69
+ * bad value degrades to unset without discarding the rest of the config.
70
+ */
71
+ defaultModels: z.ZodCatch<z.ZodOptional<z.ZodObject<{
72
+ claude: z.ZodOptional<z.ZodString>;
73
+ codex: z.ZodOptional<z.ZodString>;
74
+ opencode: z.ZodOptional<z.ZodString>;
75
+ }, "strip", z.ZodTypeAny, {
76
+ claude?: string | undefined;
77
+ codex?: string | undefined;
78
+ opencode?: string | undefined;
79
+ }, {
80
+ claude?: string | undefined;
81
+ codex?: string | undefined;
82
+ opencode?: string | undefined;
83
+ }>>>;
48
84
  }, "strip", z.ZodTypeAny, {
49
85
  skillsRepos: {
50
86
  repo: string;
@@ -53,16 +89,26 @@ declare const configSchema: z.ZodObject<{
53
89
  maxParallel: number;
54
90
  defaultRunner: "claude" | "codex" | "opencode";
55
91
  plannerModel: string;
92
+ memoryLimitMb?: number | undefined;
56
93
  baseBranch?: string | undefined;
94
+ systemPrompt?: string | undefined;
95
+ defaultModels?: {
96
+ claude?: string | undefined;
97
+ codex?: string | undefined;
98
+ opencode?: string | undefined;
99
+ } | undefined;
57
100
  }, {
58
101
  skillsRepos?: {
59
102
  repo: string;
60
103
  ref?: string | undefined;
61
104
  }[] | undefined;
62
105
  maxParallel?: number | undefined;
106
+ memoryLimitMb?: unknown;
63
107
  defaultRunner?: "claude" | "codex" | "opencode" | undefined;
64
108
  plannerModel?: string | undefined;
65
109
  baseBranch?: string | undefined;
110
+ systemPrompt?: unknown;
111
+ defaultModels?: unknown;
66
112
  }>;
67
113
  export type CezConfig = z.infer<typeof configSchema>;
68
114
  /** Read `.ai/cezar/config.json` on demand — never cached, never throws. */
package/dist/config.js CHANGED
@@ -19,6 +19,13 @@ const configSchema = z.object({
19
19
  skillsRepos: z.array(skillsRepoSchema).default(DEFAULT_SKILLS_REPOS),
20
20
  /** How many tasks may run at once (spec 006). Non-git dirs always run 1. */
21
21
  maxParallel: z.number().int().min(1).max(16).default(2),
22
+ /**
23
+ * Per-task memory ceiling in MiB (whole process tree). When a running task's
24
+ * RSS crosses this the engine pauses it with a warning and lets the queue
25
+ * advance (#memory-guard). 0 / unset = no limit. `.catch(undefined)` keeps
26
+ * the key additive-safe: a bad value degrades to "no limit".
27
+ */
28
+ memoryLimitMb: z.number().int().min(0).max(1_048_576).optional().catch(undefined),
22
29
  /**
23
30
  * Which agent backend a task uses unless overridden per task (GUI) or per
24
31
  * step (workflow). The GUI only offers runners actually installed; this is
@@ -32,6 +39,30 @@ const configSchema = z.object({
32
39
  * Unset = the branch currently checked out. Settable from the Repo tab.
33
40
  */
34
41
  baseBranch: z.string().trim().min(1).optional(),
42
+ /**
43
+ * Default extra system prompt applied to every run's agent steps (claude:
44
+ * `--append-system-prompt`; codex/opencode: prepended to the opening user
45
+ * message — the AgentRunSpec seam handles the per-backend delivery).
46
+ * Settings is the single edit place; `POST /api/runs` can override it per
47
+ * run (`systemPrompt`). `.catch(undefined)` keeps the key additive-safe: a
48
+ * bad value degrades to unset without discarding the rest of the config.
49
+ */
50
+ systemPrompt: z.string().trim().min(1).max(20_000).optional().catch(undefined),
51
+ /**
52
+ * Per-runner default model preset (Settings → Agents, redesign R6 1.5): the
53
+ * model id the composer preselects for that runner. Missing = auto (the
54
+ * runner decides). A capability (`model`), never a vendor config format.
55
+ * `.catch(undefined)` keeps the key additive-safe like `systemPrompt`: a
56
+ * bad value degrades to unset without discarding the rest of the config.
57
+ */
58
+ defaultModels: z
59
+ .object({
60
+ claude: z.string().trim().min(1).max(200).optional(),
61
+ codex: z.string().trim().min(1).max(200).optional(),
62
+ opencode: z.string().trim().min(1).max(200).optional(),
63
+ })
64
+ .optional()
65
+ .catch(undefined),
35
66
  });
36
67
  /** Read `.ai/cezar/config.json` on demand — never cached, never throws. */
37
68
  export async function loadConfig(repoRoot) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;CACvC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,oBAAoB,GAAuB;IACtD,EAAE,IAAI,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,EAAE;CAC7C,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;IACpE,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACvD;;;;OAIG;IACH,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACxE,2EAA2E;IAC3E,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACjD;;;OAGG;IACH,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChD,CAAC,CAAC;AAIH,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACvD,IAAI,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;IACD,OAAO,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -10,6 +10,7 @@
10
10
  * - `codex` — `codex app-server`, JSON-RPC 2.0 (JSONL) over stdin/stdout;
11
11
  * - `opencode` — `opencode serve`, HTTP + SSE.
12
12
  */
13
+ import type { UiEvent } from './ui-events.js';
13
14
  /** `claude-cli` is the legacy id kept so old run records still parse. */
14
15
  export type AgentBackend = 'claude' | 'codex' | 'opencode' | 'claude-cli';
15
16
  /** The user-selectable runners (what config/GUI expose). */
@@ -47,6 +48,13 @@ export interface AgentRunSpec {
47
48
  */
48
49
  resume?: boolean;
49
50
  }
51
+ /**
52
+ * Backends without a dedicated system-prompt channel (codex app-server,
53
+ * opencode serve) deliver `spec.systemPrompt` as a leading block of the
54
+ * opening user message — the documented per-backend mapping (spec §protocol
55
+ * v2: claude = `--append-system-prompt`, codex/opencode = prepended here).
56
+ */
57
+ export declare function prependSystemPrompt(systemPrompt: string | undefined, userPrompt: string): string;
50
58
  /** One content block of a user message — mirrors the Anthropic wire format
51
59
  * so it can be written to the claude CLI's stdin verbatim. */
52
60
  export type ContentBlock = {
@@ -123,6 +131,10 @@ export interface SessionOptions {
123
131
  * behavior, used for non-interactive workflow steps). Interactive
124
132
  * sessions omit this and control `end()` themselves. */
125
133
  autoEndAfterFirstTurn?: boolean;
134
+ /** Protocol-v2 channel: receives the normalized `UiEvent` stream emitted
135
+ * ALONGSIDE the v1 `AgentEvent`s (additive — v1 keeps flowing unchanged).
136
+ * RunManager consumption lands in R2 step 2.1. */
137
+ onUiEvent?: (event: UiEvent) => void;
126
138
  }
127
139
  /**
128
140
  * A live agent session over one spawned backend process. The process stays
@@ -133,6 +145,10 @@ export interface SessionOptions {
133
145
  export interface AgentSession {
134
146
  /** Resolves when the backend process exits — the session is fully over. */
135
147
  result: Promise<AgentRunResult>;
148
+ /** OS pid of the spawned backend process — the root of the run's process
149
+ * tree (agents spawn Bash children under it). Absent when the spawn
150
+ * failed before a pid existed. Feeds live resource telemetry (#348). */
151
+ readonly pid?: number;
136
152
  /** Write a user message into the live session. False when it is closed. */
137
153
  sendMessage(content: ContentBlock[]): boolean;
138
154
  /** Graceful close: end input, then a SIGTERM→SIGKILL watchdog. */
@@ -11,4 +11,13 @@
11
11
  * - `opencode` — `opencode serve`, HTTP + SSE.
12
12
  */
13
13
  export const RUNNER_IDS = ['claude', 'codex', 'opencode'];
14
+ /**
15
+ * Backends without a dedicated system-prompt channel (codex app-server,
16
+ * opencode serve) deliver `spec.systemPrompt` as a leading block of the
17
+ * opening user message — the documented per-backend mapping (spec §protocol
18
+ * v2: claude = `--append-system-prompt`, codex/opencode = prepended here).
19
+ */
20
+ export function prependSystemPrompt(systemPrompt, userPrompt) {
21
+ return systemPrompt ? `${systemPrompt}\n\n---\n\n${userPrompt}` : userPrompt;
22
+ }
14
23
  //# sourceMappingURL=agent-runner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-runner.js","sourceRoot":"","sources":["../../src/core/agent-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC"}
1
+ {"version":3,"file":"agent-runner.js","sourceRoot":"","sources":["../../src/core/agent-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,CAAC,MAAM,UAAU,GAAwB,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAmC/E;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,YAAgC,EAAE,UAAkB;IACtF,OAAO,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,cAAc,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;AAC/E,CAAC"}
@@ -38,6 +38,12 @@ export declare class ClaudeCliRunner implements AgentRunner {
38
38
  interrupt(): Promise<void>;
39
39
  startSession(spec: AgentRunSpec, onEvent?: (event: AgentEvent) => void, opts?: SessionOptions): AgentSession;
40
40
  }
41
+ /**
42
+ * Build the headless argv. `--input-format stream-json` reads user messages
43
+ * from stdin; `--output-format stream-json --verbose` gives per-event NDJSON;
44
+ * `--permission-mode acceptEdits` lets edits through without a TTY prompt.
45
+ */
46
+ export declare function buildClaudeArgs(spec: AgentRunSpec): string[];
41
47
  /**
42
48
  * Map `allowedTools` onto claude's `--allowedTools` syntax. `Bash` with a
43
49
  * `bashAllowlist` becomes one `Bash(<prefix>:*)` entry per allowed prefix;
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
3
3
  import { dirname, resolve as resolvePath } from 'node:path';
4
4
  import { costWeightedTokens } from './usage.js';
5
5
  import { readNdjson } from './ndjson.js';
6
+ import { claudeTurnStarted, createClaudeUiState, mapClaudeMessage, stringifyToolResultContent, toolResultImageBlocks, } from './claude-ui-mapper.js';
6
7
  /** Default wall-clock cap for a single run before SIGTERM → SIGKILL.
7
8
  * Interactive sessions pass `timeoutMs: 0` to disable it entirely. */
8
9
  export const DEFAULT_RUN_TIMEOUT_MS = 30 * 60_000;
@@ -57,6 +58,24 @@ export class ClaudeCliRunner {
57
58
  let autoEndTimer;
58
59
  let eofTermTimer;
59
60
  let eofKillTimer;
61
+ // Protocol v2 emission — additive alongside v1 (`onEvent` keeps flowing
62
+ // byte-identical); the channel is `opts.onUiEvent` (RunManager wiring
63
+ // lands in R2 step 2.1). The mapper never throws, but a defect in it
64
+ // must still never disturb the v1 stream — hence the belt-and-braces try.
65
+ let uiState = createClaudeUiState({ fallbackSessionId: spec.sessionId });
66
+ const emitUi = (map) => {
67
+ try {
68
+ const mapped = map(uiState);
69
+ uiState = mapped.state;
70
+ if (opts.onUiEvent) {
71
+ for (const event of mapped.events)
72
+ opts.onUiEvent(event);
73
+ }
74
+ }
75
+ catch {
76
+ // v2 mapping is best-effort; v1 consumers stay unaffected.
77
+ }
78
+ };
60
79
  const sendMessage = (content) => {
61
80
  if (!stdinOpen)
62
81
  return false;
@@ -72,6 +91,8 @@ export class ClaudeCliRunner {
72
91
  });
73
92
  try {
74
93
  child.stdin.write(`${line}\n`);
94
+ // Each user message written to stdin begins a turn (§7.1).
95
+ emitUi(claudeTurnStarted);
75
96
  return true;
76
97
  }
77
98
  catch (err) {
@@ -151,6 +172,7 @@ export class ClaudeCliRunner {
151
172
  onEvent?.({ type: 'note', message: `claude: skipped unparseable stream line: ${truncate(line)}` });
152
173
  continue;
153
174
  }
175
+ emitUi((state) => mapClaudeMessage(msg, state));
154
176
  let delta = 0;
155
177
  try {
156
178
  delta = handleClaudeMessage(msg, { toolCalls, textChunks, onEvent });
@@ -224,6 +246,7 @@ export class ClaudeCliRunner {
224
246
  sendMessage,
225
247
  end,
226
248
  interrupt,
249
+ pid: child.pid,
227
250
  get open() {
228
251
  return stdinOpen;
229
252
  },
@@ -237,7 +260,7 @@ export class ClaudeCliRunner {
237
260
  * from stdin; `--output-format stream-json --verbose` gives per-event NDJSON;
238
261
  * `--permission-mode acceptEdits` lets edits through without a TTY prompt.
239
262
  */
240
- function buildClaudeArgs(spec) {
263
+ export function buildClaudeArgs(spec) {
241
264
  const args = [
242
265
  '--input-format',
243
266
  'stream-json',
@@ -325,12 +348,12 @@ function handleClaudeMessage(msg, ctx) {
325
348
  ctx.onEvent?.({
326
349
  type: 'tool-result',
327
350
  toolCallId: b.tool_use_id,
328
- result: stringifyContent(b.content),
351
+ result: stringifyToolResultContent(b.content),
329
352
  isError: b.is_error === true,
330
353
  });
331
354
  // Screenshots and other images inside the result get their own
332
355
  // events — the text path above renders them as a placeholder.
333
- for (const img of imageBlocks(b.content)) {
356
+ for (const img of toolResultImageBlocks(b.content)) {
334
357
  ctx.onEvent?.({ type: 'image', mediaType: img.media_type, data: img.data });
335
358
  }
336
359
  }
@@ -355,46 +378,7 @@ function handleClaudeMessage(msg, ctx) {
355
378
  // system/init and anything else: nothing actionable.
356
379
  return 0;
357
380
  }
358
- function stringifyContent(content) {
359
- if (typeof content === 'string')
360
- return content;
361
- if (Array.isArray(content)) {
362
- return content
363
- .map((c) => {
364
- const b = c;
365
- if (b.type === 'text' && typeof b.text === 'string')
366
- return b.text;
367
- if (b.type === 'image')
368
- return '[screenshot]'; // emitted as its own image event
369
- try {
370
- return JSON.stringify(b);
371
- }
372
- catch {
373
- return String(b);
374
- }
375
- })
376
- .join('\n');
377
- }
378
- try {
379
- return JSON.stringify(content);
380
- }
381
- catch {
382
- return String(content);
383
- }
384
- }
385
- /** base64 image blocks inside a tool_result's content, if any. */
386
- function imageBlocks(content) {
387
- if (!Array.isArray(content))
388
- return [];
389
- const out = [];
390
- for (const c of content) {
391
- const b = c;
392
- if (b.type === 'image' && b.source?.type === 'base64' && b.source.media_type && b.source.data) {
393
- out.push({ media_type: b.source.media_type, data: b.source.data });
394
- }
395
- }
396
- return out;
397
- }
381
+ // stringify/image helpers moved to claude-ui-mapper.ts (shared by v1 and v2).
398
382
  // ---- subprocess plumbing --------------------------------------------------
399
383
  function waitForExit(child) {
400
384
  if (child.exitCode != null)