pitroom 0.6.5 → 0.6.7

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pitroom",
3
3
  "description": "A free pit crew for your expensive coding agent: the superpowers development workflow, run by cheap workers. Delegate research, fixes, tests, reviews and whole plans from Claude Code, Codex or any agent to OpenCode, Codex or Claude Code workers, with receipts that prove the savings.",
4
- "version": "0.6.5",
4
+ "version": "0.6.7",
5
5
  "author": {
6
6
  "name": "TahaAtas"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pitroom",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "A free pit crew for your expensive coding agent: delegate research, fixes, tests, reviews and parallel investigations to cheap worker agents running your own models, with verified answers, exact diffs and cost receipts.",
5
5
  "author": {
6
6
  "name": "TahaAtas"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.7
4
+
5
+ ### Dashboard
6
+ - The expanded run card keeps its header in place and scrolls the rest (a shadcn `ScrollArea`), and its close button sits in the card's corner.
7
+ - Scrolling boxes fade out at their edges while there is more to scroll (the expanded card, the result and diff blocks), and so does the window itself.
8
+ - A review's card shows a short count of its findings instead of the raw verdict line, and its result starts at the findings; a Task block that only repeats the title is left out.
9
+ - The Live filter and the Stats period use the same sliding-pill tabs as the page tabs; the history timestamp tooltip is the themed, keyboard-focusable one; scrollbars are thin and in the theme's colours.
10
+ - "Saved" is labelled "Est. saved" with an (i) that says what it is: an estimate against the primary model's list price (`price` in `/api/state` and `/api/stats`), not money back.
11
+ - A dashboard left running serves the files of the build on disk: it re-reads them when they change, so an upgrade or rebuild no longer needs a restart to show up. The footer says it stops itself when left idle and how to stop it now.
12
+
13
+ ### Savings card and README
14
+ - `pitroom savings --card` draws the card in the dashboard's look (dark surface, orange mark, green savings, bordered tiles).
15
+ - The README has a Dashboard section with screenshots of sample data (`scripts/demo-dash.mjs` makes them) and lists the dashboard and the history among the features.
16
+
17
+ ## 0.6.6
18
+
19
+ ### Node.js 22.13 or newer
20
+ - Pitroom now needs Node.js 22.13+ (`engines`, the launcher, the CI matrix of 22 and 24, the build target). It is the first release line with a built-in SQLite (`node:sqlite`) that needs no flag, which the run history uses.
21
+
22
+ ### History in SQLite
23
+ - Every finished run is written to `history.db` (built-in `node:sqlite`, FTS5): the task, worker and model, time, tokens, savings, the worker's steps, its answer and the diff, plus the whole record.
24
+ - `pitroom history [TEXT]` searches and filters (`--model`, `--state`, `--group`, `--since`, `--limit`, `--json`); `pitroom history stats` shows runs, success rate, average time and tokens per worker and model; `pitroom history import` takes in older runs (it also runs by itself on first use).
25
+ - After a run the raw event stream is compressed (about a sixth of its size) and the stderr log is kept only for runs that did not succeed. `pitroom clean` now keeps the history: `show`, `show --patch` and a short run id keep working for cleaned runs.
26
+ - The dashboard serves `/api/history` and `/api/stats`.
27
+
28
+ ### A new dashboard
29
+ - `pitroom dash` is now a React app built from [shadcn/ui](https://ui.shadcn.com) components on [Base UI](https://base-ui.com), with [Shadix UI](https://shadix-ui.vercel.app)'s expandable card and [beUI](https://beui.dev)'s agent activity stream, and tabs: **Live** (running and recent runs as cards that grow, with a shared-layout animation, into a panel with the task, the worker's steps, the result, the diff and the details), **History** (full-text search and filters over the SQLite history, a table, a side panel per run) and **Stats** (success rate, time, tokens and savings per worker and model, runs per day). Light and dark themes.
30
+ - It is built once into `dist/ui` (esbuild and the Tailwind CLI, from `ui/`) and served as two static files; the CLI still has no runtime dependencies. The page's Content-Security-Policy allows only its own files and one hashed inline script.
31
+
32
+ ### Seeing Pitroom in the Claude Code and Codex apps
33
+ - `pitroom dash` serves a live, read-only page of every run on `127.0.0.1` (state, worker and model, time, steps, tokens, savings, result; click a run for its report). `--detach` starts it in the background and prints the address (a running one is reused), `--stop` ends it, `--open` opens a browser, and it closes itself after four idle hours. The host must be `localhost`, `127.0.0.1` or `[::1]`, and only GET is answered.
34
+ - `pitroom watch --brief` prints one card line when a run starts, when it falls back and when it ends, then a total: made for Claude Code's Monitor tool and for a Codex command block.
35
+ - Click a card in the dashboard to see what the worker was asked (the task), what it did step by step (files read, searches, commands, edits, with times), its result, the files and diff it changed, and the details (model, tokens, cost, fallbacks, reference check). Runs can be searched and older ones loaded; `#run-id` in the address opens a card.
36
+ - Worker adapters record a step-by-step timeline from the event stream (`ParsedRun.timeline`).
37
+ - Cards name a range review by 9-character hashes instead of 40.
38
+ - The skills tell the agent to start the dashboard and give you its address when workers run in the background.
39
+
3
40
  ## 0.6.5
4
41
 
5
42
  ### Models, costs and effort
package/README.md CHANGED
@@ -20,6 +20,7 @@ Model-agnostic · Verified answers · Receipts, not vibes · OpenCode / Codex /
20
20
  <br />
21
21
 
22
22
  **[Quick start](#quick-start)** ·
23
+ [Dashboard](#dashboard) ·
23
24
  [Benchmarks](#benchmarks) ·
24
25
  [Workflow](#workflow) ·
25
26
  [Skills](#skills) ·
@@ -106,7 +107,8 @@ In isolate mode nothing reaches your tree until you apply it.
106
107
  - **Real isolation.** `--isolate` runs the worker in a private copy of your **current** state (its own repository, sharing your objects read-only), uncommitted and untracked files included, then hands you a patch: `pitroom apply <id>` (checked, refuses on conflict) or `pitroom discard <id>`.
107
108
  - **Zero repo pollution.** No `.pitroom/` folder, no `.gitignore` edits, no branches. Records live in `~/.local/state/pitroom`.
108
109
  - **Works with any agent.** Fourteen [Agent Skills](https://agentskills.io): the superpowers development workflow run by workers, plus delegation (`using-pitroom`, `pitroom-research`, `-crew`, `-implement`), a CLI, and a Claude Code / Codex plugin whose session-start hook loads the workflow. Claude Code, Codex, Gemini CLI, Cursor, or anything that can run a shell command.
109
- - **Small.** About 4,400 lines of TypeScript, one ~135 KB bundled file, zero runtime dependencies.
110
+ - **A live dashboard and a searchable history.** `pitroom dash` opens a local page of every run (the task, what the worker did step by step, its result and diff) with history and statistics, for the agent apps that show neither hooks nor a status line; `pitroom history` searches everything Pitroom ever ran. See [Dashboard](#dashboard).
111
+ - **Small.** About 6,000 lines of TypeScript, a ~190 KB CLI bundle (plus the ~0.7 MB of dashboard files it serves), zero runtime dependencies.
110
112
 
111
113
  ### How it stays safe
112
114
 
@@ -134,12 +136,34 @@ FILES CHANGED: none
134
136
  · returned ~554 tokens, 294× compression · est. saved $0.152 vs Claude Sonnet
135
137
  ```
136
138
 
137
- Every run prints a receipt, and `pitroom savings` adds them up (a bigger measurement follows in [Benchmarks](#benchmarks)):
139
+ Every run prints a receipt, and `pitroom savings` adds them up (`--card` writes this shareable card; the numbers here are sample data, and a real measurement follows in [Benchmarks](#benchmarks)):
138
140
 
139
141
  <p align="center"><img src="docs/card.svg" width="520" alt="Pitroom savings card"></p>
140
142
 
141
143
  ---
142
144
 
145
+ ## Dashboard
146
+
147
+ `pitroom dash --detach` prints the address of a live page on `127.0.0.1` (read-only, this machine only). It is for the places where hook messages and status lines do not reach, such as the Claude Code and Codex apps: open it in a browser or in the app's own browser pane. The skills tell your agent to start it and give you the address when it runs workers in the background.
148
+
149
+ The screenshots below show sample data (an imaginary `shop-api` project), not a real one.
150
+
151
+ **Live.** What is running now, and the latest runs. Running cards show a timer and the worker's last words; finished ones show the result and, for reviews, the findings.
152
+
153
+ <p align="center"><img src="docs/dash-live.png" width="100%" alt="The Live tab: two running workers with timers and progress bars, then finished runs with approved and needs-fixes badges, step counts, tokens and savings"></p>
154
+
155
+ **Click a card and it grows into a panel** with the task, what the worker did step by step (files read, searches, commands, edits, with times, failed ones marked), its result, the files and diff it changed, and the details (model, tokens, cost, fallbacks, reference check).
156
+
157
+ <p align="center"><img src="docs/dash-card.png" width="100%" alt="An expanded run card: the task, a ten-step activity list, the result, five changed files and the details"></p>
158
+
159
+ **History** searches everything Pitroom ever ran (the full text of tasks, answers and steps), with filters by state, model and period. **Stats** shows runs, success rate, time, tokens and savings, per worker and model and per day.
160
+
161
+ <p align="center"><img src="docs/dash-history.png" width="49%" alt="The History tab: a search box, filters and a table of runs"> <img src="docs/dash-stats.png" width="49%" alt="The Stats tab: totals, runs per day and success rate per worker and model"></p>
162
+
163
+ The dashboard is a React app built once into `dist/ui` and served as two static files, so the CLI still has no runtime dependencies. It follows your system's light or dark theme (a button switches it).
164
+
165
+ ---
166
+
143
167
  ## Benchmarks
144
168
 
145
169
  Measured on a real, public repository: [PI-Desktop](https://github.com/vastsa/PI-Desktop) at commit `c2bfe35`, 2,349 tracked files and about 419,000 lines of TypeScript and Rust. Eight questions of the kind an agent asks before it changes code, run as one read-only crew (`pitroom crew`, at most four workers at a time) on OpenCode's free `muse-spark-1.3-contributor-free` model.
@@ -162,8 +186,43 @@ Workers read 5.5 million tokens of code and docs (96 steps, 179 tool calls) and
162
186
 
163
187
  **A change in an isolated copy:** "add a one-line comment above `resolveUpdateMode`" finished in 16 s and 4 steps (142,491 tokens). The patch was one file and one line, the comment matched the code, and your working tree is untouched until you apply.
164
188
 
189
+ **Free models against `gpt-6-sol`, on three large repositories.** The same kind of work on React (7,252 files), Django (7,091) and Kubernetes (31,353 files), at pinned commits. Nine bounded questions, three per repository: the file and line that define a function, how many files contain a word, and which files contain it. Every answer is checked against `git grep`, not by opinion: an exact `path:line` (half a point for the right file on the wrong line), an exact count, and for the lists an F1 score that punishes both missed and invented files. Each worker ran the nine questions once, read-only, with no fallback, so a model that cannot do it fails instead of being swapped for another.
190
+
191
+ | Worker and model | Score | Median time | Median tokens |
192
+ |---|---|---|---|
193
+ | Codex `gpt-6-sol` (effort medium) | **100%** | 16 s | 50k |
194
+ | Codex `gpt-6-sol` (effort low) | **100%** | 20 s | 50k |
195
+ | OpenCode `muse-spark-1.3-contributor-free` | **100%** | 23 s | 44k |
196
+ | OpenCode `space-bunny-free` | **100%** | 35 s | 63k |
197
+ | OpenCode `mimo-v2.6-flash-free` | **100%** | 37 s | 42k |
198
+ | OpenCode `big-pickle` | **100%** | 42 s | 91k |
199
+ | OpenRouter `nemotron-3-ultra-550b-a55b:free` | 89% | 27 s | 42k |
200
+ | OpenCode `longcat-2.5-preview-free` | 89% | 29 s | 20k |
201
+ | OpenCode `nemotron-3-ultra-free` | 89% | 35 s | 21k |
202
+ | OpenRouter `inkling:free` | 83% | 14 s | 41k |
203
+ | OpenCode `nemotron-3.5-lightning-free` | 72% | 377 s | 69k |
204
+ | NVIDIA `gpt-oss-20b` (7 questions) | 57% | 24 s | 42k |
205
+
206
+ Four free models matched `gpt-6-sol` on these questions. Every model found every definition; the misses are count questions (a wrong number) and, for two models, list questions, one of them by adding `docs/` files that are not under `django/`. `gpt-6-sol` was as fast as the quickest free models, and lowering its effort did not cost accuracy here. `nemotron-3.5-lightning-free` wrote an unrelated text for one list question and ran into the 10-minute limit on another. The scoring harness and every raw answer are in [`benchmarks/multi-repo`](benchmarks/multi-repo).
207
+
208
+ Only workers that ran are in the table. Models that could not answer at all (a provider error, or the shared daily quota of OpenRouter's free tier) are left out, and so are the two `gpt-oss-20b` runs that ended in a provider error; a timeout is kept.
209
+
210
+ **Does a review find a planted bug?** A bug was changed into real code (an inverted check, a swapped `&&`/`||`, a flipped `return`) in React, Django, Kubernetes and Pitroom itself, committed as a bare "tidy", and `pitroom review` was asked to review the commit. 29 packages (14 with the bug alone, 15 with the bug and three comment-only edits around it). A review counts as a find when a Critical or Important finding names the changed file and either cites a line within 5 or names the changed identifier or its function; "exact line" is the strict version (within 3).
211
+
212
+ | Worker and model | Bug found | Exact line | Reviews | Mean time | Slowest |
213
+ |---|---|---|---|---|---|
214
+ | OpenCode `space-bunny-free` | 100% | 100% | 29 of 29 | 2 min 19 s | 5 min 46 s |
215
+ | OpenCode `nemotron-3-ultra-free` | 97% | 79% | 29 of 29 | 1 min 45 s | 3 min 29 s |
216
+ | OpenCode `longcat-2.5-preview-free` | 83% | 83% | 29 of 29 | 2 min 53 s | 8 min (1 timed out) |
217
+ | Codex `gpt-6-sol` | 100% | 100% | 9 of 29 | 38 s | 52 s |
218
+ | OpenCode `muse-spark-1.3-contributor-free` | 100% | 57% | 14 of 29 | 48 s | 2 min 0 s |
219
+ | OpenCode `big-pickle` | 100% | 92% | 13 of 29 | 2 min 28 s | 5 min 27 s |
220
+ | OpenCode `mimo-v2.6-flash-free` | 100% | 100% | 10 of 29 | 4 min 6 s | 7 min 40 s |
221
+
222
+ Almost every model found almost every bug, so this test separates them on speed and precision, not on whether they can review: `muse-spark` described the right bug but often quoted a wrong line number (57% exact), and `gpt-6-sol` and `muse-spark` were the fastest by a wide margin. Only three models finished all 29: the other four are scored on the reviews that ran before a provider limit (the free tier's usage limit, and the Codex plan's, which `gpt-6-sol` reached), and the rest are left out, not counted as misses, so their rows rest on 9 to 14 reviews. Every change is only 2 to 5 lines, which is easy; there is no false-alarm rate (the comment-only "clean" packages turned out to contain comments that were really wrong, so they cannot show one); and React, Django and Kubernetes may be in a model's training data. The harness and every raw review are in [`benchmarks/review-bugs`](benchmarks/review-bugs).
223
+
165
224
  **What this does not show**
166
- - One repository, one free model, one run per question: there is no variance here, and another model or repository will differ.
225
+ - One run per question and model: there is no variance here. The questions are bounded lookups that `grep` can answer, so they do not show how a model handles design questions or large edits, and four free models and `gpt-6-sol` all scoring 100% says the test is easy at the top, not that they are equal.
167
226
  - An open-ended task ("find up to five spelling mistakes in `docs/`") did not finish: it was stopped after 19 minutes and 57 tool calls. Give workers bounded tasks.
168
227
  - "Tokens processed" is what the workers read and wrote. What your agent would have spent doing the same reading itself is an estimate, not a measurement: the savings figure assumes it would process about the same tokens. The $0 worker cost is because the model is free.
169
228
  - The hand check covered key claims, not every citation.
@@ -174,7 +233,7 @@ To repeat it on your own repository: `pitroom crew -d <repo> -g bench "<question
174
233
 
175
234
  ## Quick start
176
235
 
177
- You need Node.js 18+ and at least one worker CLI: [OpenCode](https://opencode.ai) v2+ (the default worker, with free models), [Codex CLI](https://github.com/openai/codex) or [Claude Code](https://claude.com/claude-code).
236
+ You need Node.js 22.13+ and at least one worker CLI: [OpenCode](https://opencode.ai) v2+ (the default worker, with free models), [Codex CLI](https://github.com/openai/codex) or [Claude Code](https://claude.com/claude-code).
178
237
 
179
238
  ### 1. Install: pick your agent
180
239
 
@@ -367,7 +426,10 @@ pitroom crew [-i] [-g NAME] "task 1" "task 2" … (or --task-file with --- sep
367
426
  pitroom run -i --plan PLAN --step N [--tier T] ["notes"]
368
427
  pitroom review [run | --range A..B [--plan PLAN]] [--tier T | -W T] [--bg]
369
428
  pitroom plan status PLAN [--json] · pitroom plan note PLAN "Task N: …"
370
- pitroom status|wait|watch [run… | -g NAME] wait: --any --brief --timeout · watch: --json --interval
429
+ pitroom status|wait|watch [run… | -g NAME] wait: --any --brief --timeout · watch: --json|--brief --interval
430
+ pitroom dash [--detach] [--port N] [--open] [--stop] a live page of the runs on 127.0.0.1
431
+ pitroom history [TEXT] [--model M] [--state S] [--since 30d] [--limit N] [--json] finished runs, searchable
432
+ pitroom history stats [--since 30d] · pitroom history import per worker and model · take in older runs
371
433
  pitroom show [run] --patch --events --full --json
372
434
  pitroom apply [run | -g NAME] · pitroom discard|revert [run] · pitroom stop [run | -g NAME]
373
435
  pitroom ls [--running] [-g NAME] · pitroom clean [--days 14] [--yes]
@@ -381,7 +443,19 @@ Exit codes: `0` ok · `1` worker failed · `2` usage · `3` refused/setup · `4`
381
443
 
382
444
  </details>
383
445
 
384
- ### Seeing Pitroom at work in Claude Code
446
+ ### History
447
+
448
+ Every finished run is also written to a SQLite database (`history.db` in Pitroom's state directory; Node's built-in `node:sqlite`, nothing to install). It keeps the task, worker and model, time, tokens, savings, the steps the worker took, its answer and the diff, and it is searchable:
449
+
450
+ ```bash
451
+ pitroom history "login bug" # full-text search over the task, the answer and the steps
452
+ pitroom history --state problem --since 7d
453
+ pitroom history stats --since 30d # runs, success rate, average time and tokens per worker and model
454
+ ```
455
+
456
+ While a run runs, its raw event stream is a plain file (the simplest thing that survives a crash). When it ends, the stream is compressed, and the stderr log is kept only for runs that did not succeed. `pitroom clean` removes old run directories but the history keeps them: `pitroom show <run>` still prints their report, steps, and patch. Runs from before the history existed are taken in by `pitroom history import` (it also runs on the first `pitroom history` and `pitroom dash`). The files remain the source: the database can be deleted and rebuilt with `history import` for the runs still on disk.
457
+
458
+ ### Seeing Pitroom at work
385
459
 
386
460
  Two settings make every delegation visible, whether or not the agent mentions it. A status line shows running workers and this week's savings (`--then` keeps your own status line first); a card appears after each `pitroom` command the agent runs, once per phase (started, finished with its result, applied). The plugin registers the card hook itself; with `pitroom install`, add both to `~/.claude/settings.json`:
387
461
 
@@ -394,12 +468,15 @@ Two settings make every delegation visible, whether or not the agent mentions it
394
468
  }
395
469
  ```
396
470
 
471
+ The Claude Code and Codex **apps** show neither hook messages nor a status line. For them there are two things that need no setup:
472
+
473
+ - `pitroom dash --detach` prints the address of a live dashboard with three tabs: **Live** (every running and recent run as an animated card; click one for the task, what the worker did step by step, its result, the diff and the details), **History** (search and filter everything Pitroom ever ran) and **Stats** (success rate, time, tokens and savings per worker and model). It is read-only, listens on `127.0.0.1` only and stops itself after four idle hours (`pitroom dash --stop` ends it sooner). Open it in a browser or in the app's own browser pane. The skills tell the agent to start it and give you the address when it runs workers in the background.
474
+ - `pitroom watch -g NAME --brief` prints one card line when a worker starts and one when it ends. In Claude Code, the agent runs it through the Monitor tool and the lines appear in the app; in Codex the command's output block fills as it goes.
475
+
397
476
  ---
398
477
 
399
478
  ## How it works
400
479
 
401
- ![Pitroom workflow: primary agent → Pitroom CLI → OpenCode, Codex or Claude Code workers → verified answer, exact diff and receipt returned to the primary agent.](docs/pitroom-flow.png)
402
-
403
480
  <p align="center"><img src="docs/architecture.svg" width="100%" alt="How a run flows: the primary agent delegates to the Pitroom CLI, which starts a worker CLI in read, isolate or write mode; events, then the answer, an exact diff and a receipt come back."></p>
404
481
 
405
482
  <details>
@@ -14,6 +14,31 @@ The above copyright notice and this permission notice shall be included in all c
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
16
 
17
+ ## The dashboard (`dist/ui`)
18
+
19
+ `pitroom dash` serves a React app that is bundled into `dist/ui/app.js` and `app.css` when Pitroom is built. The bundle contains, or was written from, these projects (the licenses are those of the versions used):
20
+
21
+ | Project | License | Copyright |
22
+ |---|---|---|
23
+ | [React](https://github.com/facebook/react) and react-dom | MIT | Meta Platforms, Inc. and affiliates |
24
+ | [Base UI](https://github.com/mui/base-ui) | MIT | Material-UI SAS (2019) |
25
+ | [shadcn/ui](https://github.com/shadcn-ui/ui) components (`ui/src/components/ui`) | MIT | shadcn |
26
+ | [Shadix UI](https://github.com/apix-js/shadix-ui) expandable card (`ui/src/components/expandable-card.tsx`) | MIT | gihanrangana (2025) |
27
+ | [beUI](https://github.com/starc007/ui-components) agent activity (`ui/src/components/agents`, `motion`, `lib`) | MIT | Saurabh Chauhan (2026) |
28
+ | [Motion](https://github.com/motiondivision/motion) (with framer-motion) | MIT | Motion B.V. (2024), Framer B.V. (2018) |
29
+ | [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) | MIT | Tailwind Labs, Inc. |
30
+ | [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | MIT | Wombosvideo (2025) |
31
+ | [clsx](https://github.com/lukeed/clsx) | MIT | Luke Edwards |
32
+ | [tailwind-merge](https://github.com/dcastil/tailwind-merge) | MIT | Dany Castillo (2021) |
33
+ | [Lucide](https://github.com/lucide-icons/lucide) | ISC | Lucide Icons and Contributors (2026) |
34
+ | [class-variance-authority](https://github.com/joe-bell/cva) | Apache-2.0 | Joe Bell |
35
+
36
+ The MIT License for the projects above is the one reproduced for superpowers, with each project's own copyright holder in place of the name there. The ISC and Apache-2.0 licenses are in the projects' repositories. Pitroom's own code in `ui/src` is MIT like the rest of the package.
37
+
17
38
  ## Not bundled
18
39
 
19
40
  Pitroom starts worker CLIs you install yourself (OpenCode, Codex CLI, Claude Code). They are not part of this package and keep their own licenses and terms.
41
+
42
+ ## Product names and marks
43
+
44
+ `docs/architecture.svg` shows the logos of OpenCode, Codex (OpenAI) and Claude Code (Anthropic) only to say which tools Pitroom can drive. The files come from [svgl](https://svgl.app) (OpenCode) and the MIT-licensed [LobeHub icons](https://github.com/lobehub/lobe-icons) set (Codex, Claude Code). The names and logos belong to their owners. Pitroom is not affiliated with or endorsed by them.
package/dist/lib.mjs CHANGED
@@ -49,6 +49,9 @@ function resolveCommand(bin) {
49
49
  return { command: bin, prefix: [] };
50
50
  }
51
51
 
52
+ // src/backends/types.ts
53
+ var MAX_STEPS = 400;
54
+
52
55
  // src/backends/claude/events.ts
53
56
  var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
54
57
  var DENIED = /permission|not allowed|denied|blocked|disallowed/i;
@@ -67,6 +70,8 @@ function parseEvents(jsonl) {
67
70
  const tools = {};
68
71
  const edits = [];
69
72
  const pendingEdits = /* @__PURE__ */ new Map();
73
+ const timeline = [];
74
+ const stepOf = /* @__PURE__ */ new Map();
70
75
  const steps = /* @__PURE__ */ new Set();
71
76
  let sessionId;
72
77
  let model;
@@ -97,18 +102,26 @@ function parseEvents(jsonl) {
97
102
  if (c.type === "text" && String(c.text ?? "").trim()) {
98
103
  lastText = String(c.text).trim();
99
104
  lastActivity = `says: ${oneLine(lastText)}`;
105
+ if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip(lastText, 600) });
100
106
  } else if (c.type === "tool_use") {
101
107
  usage.toolCalls++;
102
108
  tools[c.name] = (tools[c.name] ?? 0) + 1;
103
109
  const target = c.input?.file_path ?? c.input?.path ?? c.input?.pattern ?? c.input?.command ?? c.input?.url;
104
110
  lastActivity = `${c.name} ${target ? oneLine(String(target), 60) : ""}`.trim();
105
111
  if (EDIT_TOOLS.has(c.name) && c.input?.file_path) pendingEdits.set(c.id, String(c.input.file_path));
112
+ if (timeline.length < MAX_STEPS) {
113
+ const step = { kind: EDIT_TOOLS.has(c.name) ? "edit" : c.name === "Bash" ? "shell" : "tool", name: c.name, text: clip(String(target ?? ""), 240) };
114
+ timeline.push(step);
115
+ stepOf.set(String(c.id), step);
116
+ }
106
117
  }
107
118
  }
108
119
  } else if (e.type === "user") {
109
120
  for (const c of e.message?.content ?? []) {
110
121
  if (c?.type !== "tool_result") continue;
111
122
  const text = typeof c.content === "string" ? c.content : JSON.stringify(c.content ?? "");
123
+ const step = stepOf.get(String(c.tool_use_id));
124
+ if (step) step.ok = !c.is_error;
112
125
  if (c.is_error) {
113
126
  if (DENIED.test(text)) usage.denied++;
114
127
  } else if (pendingEdits.has(c.tool_use_id)) {
@@ -134,8 +147,9 @@ function parseEvents(jsonl) {
134
147
  }
135
148
  }
136
149
  usage.steps = steps.size;
137
- return { sessionId, model, finalText: finalText ?? (error ? "" : lastText), usage, tools, edits, lastActivity, error };
150
+ return { sessionId, model, finalText: finalText ?? (error ? "" : lastText), usage, tools, edits, lastActivity, timeline, error };
138
151
  }
152
+ var clip = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
139
153
  function num(v) {
140
154
  return typeof v === "number" && Number.isFinite(v) ? v : 0;
141
155
  }
@@ -321,6 +335,7 @@ function parseEvents2(jsonl) {
321
335
  };
322
336
  const tools = {};
323
337
  const edits = [];
338
+ const timeline = [];
324
339
  let sessionId;
325
340
  let finalText = "";
326
341
  let lastActivity;
@@ -348,14 +363,17 @@ function parseEvents2(jsonl) {
348
363
  if (it.type === "agent_message" && String(it.text ?? "").trim()) {
349
364
  finalText = String(it.text).trim();
350
365
  lastActivity = `says: ${oneLine2(finalText)}`;
366
+ if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip2(finalText, 600) });
351
367
  } else if (it.type === "command_execution") {
352
368
  if (it.status === "declined" || /operation not permitted|sandbox/i.test(String(it.aggregated_output ?? ""))) usage.denied++;
353
369
  lastActivity = `shell ${oneLine2(String(it.command ?? ""), 60)}`;
370
+ if (timeline.length < MAX_STEPS) timeline.push({ kind: "shell", name: "shell", text: clip2(String(it.command ?? "").replace(/^\/bin\/\w+ -lc /, ""), 240), ok: it.exit_code === 0 });
354
371
  } else if (it.type === "file_change") {
355
372
  const paths = (it.changes ?? []).map((c) => String(c.path));
356
373
  if (it.status !== "failed" && it.status !== "declined") edits.push(...paths);
357
374
  else usage.denied++;
358
375
  lastActivity = `edit ${oneLine2(paths.join(", "), 60)}`;
376
+ if (timeline.length < MAX_STEPS) timeline.push({ kind: "edit", name: "edit", text: clip2(paths.join(", "), 240), ok: it.status !== "failed" && it.status !== "declined" });
359
377
  }
360
378
  break;
361
379
  }
@@ -379,7 +397,7 @@ function parseEvents2(jsonl) {
379
397
  break;
380
398
  }
381
399
  }
382
- return { sessionId, finalText, usage, tools, edits, lastActivity, error };
400
+ return { sessionId, finalText, usage, tools, edits, lastActivity, timeline, error };
383
401
  }
384
402
  function describe(raw) {
385
403
  const s = String(raw ?? "unknown Codex error");
@@ -399,6 +417,7 @@ function oneLine2(s, max = 80) {
399
417
  const t = s.replace(/\s+/g, " ").trim();
400
418
  return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
401
419
  }
420
+ var clip2 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
402
421
 
403
422
  // src/backends/codex/index.ts
404
423
  var binary2 = () => findBinary("codex", "PITROOM_CODEX_BIN");
@@ -543,6 +562,7 @@ function parseEvents3(ndjson) {
543
562
  };
544
563
  const tools = {};
545
564
  const edits = [];
565
+ const timeline = [];
546
566
  const textByMessage = /* @__PURE__ */ new Map();
547
567
  let sessionId;
548
568
  let lastActivity;
@@ -565,6 +585,7 @@ function parseEvents3(ndjson) {
565
585
  if (!textByMessage.has(key)) textByMessage.set(key, []);
566
586
  textByMessage.get(key).push(text);
567
587
  lastActivity = `says: ${oneLine3(text)}`;
588
+ if (timeline.length < MAX_STEPS) timeline.push({ kind: "say", text: clip3(text.trim(), 600), at: stamp(e.timestamp) });
568
589
  break;
569
590
  }
570
591
  case "tool_use": {
@@ -577,6 +598,11 @@ function parseEvents3(ndjson) {
577
598
  edits.push(String(st.input?.filePath ?? st.input?.path ?? name));
578
599
  }
579
600
  lastActivity = `${name} ${describe2(st.input)}`.trim();
601
+ if (timeline.length < MAX_STEPS) {
602
+ const kind = EDIT_TOOLS2.has(name) ? "edit" : /^(bash|shell)$/.test(name) ? "shell" : "tool";
603
+ const text = kind === "shell" ? String(st.input?.command ?? "") : String(st.input?.filePath ?? st.input?.path ?? st.input?.pattern ?? st.input?.url ?? "") || describe2(st.input);
604
+ timeline.push({ kind, name, text: clip3(text, 240), ok: st.status === "completed" ? true : st.status === "error" ? false : void 0, at: stamp(e.timestamp) });
605
+ }
580
606
  break;
581
607
  }
582
608
  case "step_finish": {
@@ -599,7 +625,7 @@ function parseEvents3(ndjson) {
599
625
  }
600
626
  const groups = [...textByMessage.values()];
601
627
  const finalText = (groups[groups.length - 1] ?? []).join("\n").trim();
602
- return { sessionId, finalText, usage, tools, edits, lastActivity, error };
628
+ return { sessionId, finalText, usage, tools, edits, lastActivity, timeline, error };
603
629
  }
604
630
  function describeError(err) {
605
631
  if (!err || typeof err !== "object") return "unknown OpenCode error";
@@ -612,6 +638,8 @@ function describeError(err) {
612
638
  const tags = [err.type, inner, err.status && `HTTP ${err.status}`].filter(Boolean);
613
639
  return tags.length ? `${message} [${tags.join(", ")}]` : message;
614
640
  }
641
+ var clip3 = (s, n) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
642
+ var stamp = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
615
643
  function num3(v) {
616
644
  return typeof v === "number" && Number.isFinite(v) ? v : 0;
617
645
  }