viewable-one 0.0.0-stage → 0.12.2

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 (89) hide show
  1. package/CHANGELOG.md +1483 -0
  2. package/LICENSE +21 -0
  3. package/README.md +738 -2
  4. package/SPAWN.md +2108 -0
  5. package/glytch.mjs +517 -0
  6. package/lib/acp.mjs +1018 -0
  7. package/lib/admission.mjs +105 -0
  8. package/lib/agent-selection.mjs +124 -0
  9. package/lib/agents.mjs +914 -0
  10. package/lib/atomic.mjs +96 -0
  11. package/lib/auth.mjs +1491 -0
  12. package/lib/authenticatedHttp.mjs +75 -0
  13. package/lib/boardside.mjs +225 -0
  14. package/lib/child.mjs +1292 -0
  15. package/lib/cli-output.mjs +176 -0
  16. package/lib/cloudflared-release.json +25 -0
  17. package/lib/cloudflared.mjs +81 -0
  18. package/lib/codex-rpc.mjs +92 -0
  19. package/lib/codex.mjs +767 -0
  20. package/lib/codexApps.mjs +344 -0
  21. package/lib/codexPlanWindow.mjs +84 -0
  22. package/lib/codexToolInputs.mjs +139 -0
  23. package/lib/codingChecks.mjs +55 -0
  24. package/lib/compose.mjs +76 -0
  25. package/lib/computerApps.mjs +110 -0
  26. package/lib/conversationRules.mjs +45 -0
  27. package/lib/detect.mjs +629 -0
  28. package/lib/devserver.mjs +670 -0
  29. package/lib/digest.mjs +290 -0
  30. package/lib/doctor.mjs +464 -0
  31. package/lib/exitClass.mjs +64 -0
  32. package/lib/hosted.mjs +112 -0
  33. package/lib/imageAttachments.mjs +31 -0
  34. package/lib/init-connect.mjs +418 -0
  35. package/lib/kill.mjs +104 -0
  36. package/lib/logindaemon.mjs +976 -0
  37. package/lib/logs.mjs +120 -0
  38. package/lib/mcpjson.mjs +242 -0
  39. package/lib/memberPreview.mjs +173 -0
  40. package/lib/multiwatch.mjs +428 -0
  41. package/lib/pagePictures.mjs +396 -0
  42. package/lib/pageRoutes.mjs +70 -0
  43. package/lib/paths.mjs +10 -0
  44. package/lib/permissionAsk.mjs +418 -0
  45. package/lib/picker.mjs +300 -0
  46. package/lib/platform.mjs +60 -0
  47. package/lib/previewGateway.mjs +130 -0
  48. package/lib/previewTunnel.mjs +85 -0
  49. package/lib/procs.mjs +428 -0
  50. package/lib/project-mcp.mjs +288 -0
  51. package/lib/projectPaths.mjs +31 -0
  52. package/lib/refusal.mjs +70 -0
  53. package/lib/registry.mjs +204 -0
  54. package/lib/repomap.mjs +323 -0
  55. package/lib/restartPass.mjs +151 -0
  56. package/lib/retired-address.mjs +78 -0
  57. package/lib/rowsAnswers.mjs +388 -0
  58. package/lib/rowsEvents.mjs +86 -0
  59. package/lib/run-engine.mjs +16 -0
  60. package/lib/runner-health.mjs +57 -0
  61. package/lib/runner.mjs +1640 -0
  62. package/lib/runnerAdmission.mjs +35 -0
  63. package/lib/runnerApi.mjs +312 -0
  64. package/lib/runnerCompletion.mjs +406 -0
  65. package/lib/runnerLease.mjs +37 -0
  66. package/lib/runnerMaintenance.mjs +27 -0
  67. package/lib/runnerPace.mjs +283 -0
  68. package/lib/runnerPerformance.mjs +26 -0
  69. package/lib/runnerRealtime.mjs +340 -0
  70. package/lib/runnerRegister.mjs +96 -0
  71. package/lib/runnerSettings.mjs +40 -0
  72. package/lib/runnerSettlement.mjs +145 -0
  73. package/lib/runnerWorkspace.mjs +177 -0
  74. package/lib/serverSweep.mjs +90 -0
  75. package/lib/sharedPreview.mjs +292 -0
  76. package/lib/signIn.mjs +47 -0
  77. package/lib/spawn.mjs +330 -0
  78. package/lib/sweep.mjs +161 -0
  79. package/lib/sync.mjs +484 -0
  80. package/lib/uninstall-project.mjs +589 -0
  81. package/lib/uninstall.mjs +464 -0
  82. package/lib/update.mjs +302 -0
  83. package/lib/usageLimit.mjs +108 -0
  84. package/lib/viewableFolder.mjs +105 -0
  85. package/lib/windows-command.mjs +85 -0
  86. package/lib/worktree.mjs +1142 -0
  87. package/lib/writePolicy.mjs +45 -0
  88. package/package.json +58 -4
  89. package/spawn-runner.mjs +167 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,1483 @@
1
+ # Changelog
2
+
3
+ `viewable-one`. Dates are the publish date, newest first.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and the versions follow [semver](https://semver.org/spec/v2.0.0.html). For
7
+ this package the public surface is **the commands, their flags, and what the
8
+ runner does with a run**, not the internals: renaming a command, dropping a
9
+ flag, or changing what the runner promises a session is a breaking change;
10
+ rewriting how one of them does its job is not. The board tools themselves are
11
+ served from the hosted endpoint and ship on their own schedule, not with this
12
+ package.
13
+
14
+ ## [Unreleased]
15
+
16
+ ## [0.12.2] - 2026-10-30
17
+
18
+ ### Changed
19
+
20
+ - Use Viewable for internal and outbound names, retaining old project settings, preview access, credentials and working folders (#2329).
21
+ - A late answer to a finished run's question continues that run: Claude Code
22
+ and Codex resume the same session in the same separate copy instead of
23
+ starting the idea again. When the session or its copy is gone, the run
24
+ fails with "Run again to start a new session" instead of silently starting
25
+ fresh (#2446, #2447).
26
+
27
+ ### Fixed
28
+
29
+ - Uninstall confirms that a personal board key no longer works before saying
30
+ "revoked". Refused or unconfirmed revocation keeps the key file for retry
31
+ (#2945). Requires the matching hosted revocation update.
32
+ - Preview finds the newest server record whether it was written as
33
+ `.viewable-server.json` or `.glytch-server.json`, so a resumed session's
34
+ newer record wins (#2329).
35
+ ## [0.12.1] - 2026-10-25
36
+
37
+ ### Changed
38
+
39
+ - Install Viewable with `npm install -g viewable-one` and use `viewable`. The old `glytch` command, settings and sign-in keep working.
40
+ - Interrupted Claude Code and Codex runs carry on in the same conversation and separate copy when the computer returns.
41
+
42
+ ### Fixed
43
+
44
+ - `glytch update` finds Homebrew's Volta even when Homebrew is absent from
45
+ PATH, and accepts a symlinked Volta bin directory (#727).
46
+
47
+ ## [0.12.0] - 2026-10-17
48
+
49
+ ### Changed
50
+
51
+ - The runner now hears about new work the moment it happens. A run you
52
+ approve starts within a few seconds, and Stop reaches a running session
53
+ within a few seconds, without the runner asking the board every 10 seconds.
54
+ While it is connected it checks in every 5 minutes; if the connection drops
55
+ it goes back to checking every 10 seconds until it is back.
56
+ - A runner watching several folders now shows as one runner, with one id for
57
+ all of its folders.
58
+ - The runner talks to the board's server directly instead of through the
59
+ app's website.
60
+ - `glytch doctor` counts the runner as beating for 6 minutes after its last
61
+ check-in, to match the 5-minute check-in.
62
+ ## [0.11.4] - 2026-10-16
63
+
64
+ ### Fixed
65
+
66
+ - `glytch update` follows Volta's shared launcher with `volta which glytch`
67
+ in a fresh shell and restarts its package image even when `npm root -g`
68
+ points at the separate Node image. Another install is still refused (#707).
69
+ ## [0.11.3] - 2026-10-16
70
+
71
+ ### Changed
72
+
73
+ - The board server given to Codex now allows a tool call up to 120 seconds,
74
+ so a question can wait for the person's answer instead of being cut off
75
+ after a minute. A board entry already in your Codex config keeps its own
76
+ settings.
77
+
78
+ ### Fixed
79
+
80
+ - Codex computer-app approvals now ask on the board and return the selected
81
+ answer instead of rejecting supported requests without a question. Stopped,
82
+ timed-out and resolved requests cannot receive a later approval. Unsupported
83
+ forms and sensitive input remain blocked.
84
+ - The runner calls the board far less, so a machine watching several folders
85
+ no longer trips the board's rate limit. One heartbeat covers every folder,
86
+ an idle folder asks for work only when the board says a run is waiting,
87
+ and folder registration repeats every 5 minutes instead of every beat. With
88
+ five idle folders that is about 6 calls a minute instead of about 45. When
89
+ the board is busy and says how long to wait, the runner waits that long,
90
+ and the log names the request so it can be looked up. Older boards keep
91
+ working: there each folder polls on its own, slowing to every 30 seconds
92
+ when nothing is waiting.
93
+ - Codex tool approvals in every form mode Codex sends are now asked, and a
94
+ tool whose name mentions keys or tokens is asked too; a message or field
95
+ that carries a password, token or code is still refused. Every refused request
96
+ writes its reason and field names (never values) to the runner log. Each
97
+ question offers one Cancel.
98
+ - The runner no longer exits by itself when it is idle or when no folders are
99
+ registered.
100
+ ## [0.11.2] - 2026-10-15
101
+
102
+ ### Fixed
103
+
104
+ - Codex runs preserve configured, plugin and built-in app tools with scoped
105
+ approval policies. Tool exposure is checked before a run starts; apps OFF
106
+ and plan drafts keep their restrictions. MCP discovery cleanup is bounded.
107
+ ## [0.11.1] - 2026-10-15
108
+
109
+ ### Added
110
+
111
+ - A coding run asks in the idea when it needs something outside its list
112
+ (#478): Allow, Not this, or Always allow in <folder>. Always allow is
113
+ remembered per folder under `~/.glytch/permissions/` and not asked again
114
+ there. Claude Code and Codex both ask; a plan draft never does.
115
+ - `webSearch` on a request lets the run search and read the web without
116
+ asking first (#478).
117
+ ### Changed
118
+
119
+ - Keep changes (was the merge op) is one gesture (#483): it commits what
120
+ the agent left in the copy, sets the person's own uncommitted changes in
121
+ the folder aside and puts them back, merges into the default branch, and
122
+ deletes the copy and its branch. Nothing is pushed. A clash changes nothing
123
+ in the folder and names the files, so the app can send Coding back to fit
124
+ its changes around the person's. Throw away (was delete-branch) deletes
125
+ the copy and its branch. A run in a folder that is not git says Throw away
126
+ cannot undo it.
127
+ ## [0.11.0] - 2026-10-14
128
+
129
+ ### Removed
130
+
131
+ - Every run and agent limit (#364): no 2 dollar wall on a run, no 400,000
132
+ token context wall, no 4,000,000 token running total, and no
133
+ `--max-budget-usd` passed to Claude Code. A run is bounded by the wall
134
+ clock alone. `PM_SPAWN_MAX_PER_DAY` is gone; a value still set is ignored.
135
+ `PM_SPAWN_MAX_CONCURRENT` and the wall clock stay. Cost and tokens are
136
+ still recorded with the run.
137
+ - A Codex run no longer stands down when its plan window passes a
138
+ percentage (80 by default). It always starts. The window is still read for
139
+ the run's info line. The runner's status no longer carries the last window
140
+ it read.
141
+
142
+ ### Changed
143
+
144
+ - No receipt or closing reply shows a cost any more: no dollar figure, no
145
+ estimate, no "cost unavailable" or "on your Codex plan". They show turns and
146
+ time. The cost is still recorded with the run.
147
+ - The board digest's flags section is read under its new name, `BOARD FLAGS`,
148
+ and the old `SUBCONSCIOUS FLAGS` still works.
149
+ - `GOAL` is no longer read as a fact on a card's line in the digest; the board
150
+ stopped writing it.
151
+ - `glytch init` exits 1, and does not say a runner is watching, when your
152
+ computer could not sign in or skipped sign-in for want of a terminal.
153
+ `--no-login` still exits 0, but no longer says a runner is watching.
154
+
155
+ ### Security
156
+
157
+ - Relaunching a dev server runs the recorded command as one program and its
158
+ arguments, with no shell and only a plain environment (no model keys, cloud
159
+ credentials or runner settings). A recorded command with pipes, `&&`,
160
+ quotes or `$` is refused, and so is a recorded directory outside the linked
161
+ folder.
162
+ - A run whose request names a directory outside the linked folder is refused
163
+ before anything starts, the same way a chat turn already was.
164
+ - A message from another session can no longer be read as an answered
165
+ question, whatever its sender label says.
166
+ ## [0.10.0] - 2026-10-04
167
+
168
+ ### Changed
169
+
170
+ - The board has one kind of card now, the **idea**, and the runner speaks of
171
+ ideas everywhere. A run belongs to an idea, its outcome is written as a note
172
+ on that idea, and a session that asks for a decision with `ask_user` lands a
173
+ question on the idea it was started from, which you answer in the idea's
174
+ conversation. Nothing about how you start, stop or approve a run changed.
175
+ - The text a coding session is told before it starts says "idea" where it spoke of
176
+ circles, and `get_board` reads the new board
177
+ digest: one `[idea]` line per card and one row per question, naming the idea
178
+ it sits on.
179
+ - The board's write requests are `addIdea` (title, content, pictures) and
180
+ `addQuestion` (an idea, the question, why, options). A session that still
181
+ sends `addThought` or `addPrompt` gets one plain line telling it to use
182
+ `addIdea`; those two names go in the release after this one.
183
+
184
+ ### Added
185
+
186
+ - A note you write to the Coding agent can start a session, and the session
187
+ answers on the same idea: when it finishes it leaves one reply with its
188
+ closing words, a line of figures and links to the branch, the files, a
189
+ preview and a report. Pictures in your notes reach a running session, and a
190
+ session can attach pictures to its replies and questions. A note written
191
+ while the session works reaches it, and counts as delivered only once the
192
+ session has taken it; one it never took reads "did not reach the session"
193
+ with Try again. After your computer restarts, a session that was working
194
+ carries on by itself once, in its own worktree; one that cannot says so in
195
+ a reply and keeps its branch. A follow-up note to a Codex or opencode
196
+ session runs in the branch the earlier session kept.
197
+
198
+ ### Fixed
199
+
200
+ - Messages queued for a running session were never collected: the board read
201
+ the queue before it knew the session's row. Steering from the idea or the
202
+ session screen works now.
203
+
204
+ ## [0.9.1] - 2026-10-03
205
+
206
+ ### Changed
207
+
208
+ - The runner calls the board less. While nothing is queued it checks for new
209
+ runs less often, slowing from every 5 seconds to every 20, and goes back to
210
+ every 5 as soon as it picks one up. When the board is down or unreachable it
211
+ waits longer between tries, up to a minute, and goes back to normal on the
212
+ first answer. With several folders it registers them once per beat instead
213
+ of once per folder.
214
+ ## [0.9.0] - 2026-10-03
215
+
216
+ ### Changed
217
+
218
+ - The board moved to `https://app.glytch.ai`; `prompt.glytch.ai` no longer
219
+ answers. Your saved sign-in carries over without signing in again, and
220
+ `glytch init` moves the `glytch` entries it wrote for your agents to the
221
+ new address. An agent you connected by hand (claude.ai, for example)
222
+ needs reconnecting at `https://app.glytch.ai/mcp`.
223
+
224
+ ### Added
225
+
226
+ - A `Glytch` folder in your home folder. The runner makes it (a git
227
+ repository with one commit) and adds it to your folders when it starts
228
+ watching, so a board has somewhere to work before you link a folder. A chat
229
+ on a board with no folder picked works in its own subfolder there, named
230
+ after the board. `GLYTCH_FOLDER=off` turns it off.
231
+ - The runner now times its own steps for each run (time spent queued,
232
+ setting up, launching, reaching first output, running, and settling) and
233
+ records them with the run and in its own log, so a slow run can be
234
+ diagnosed from data instead of a guess. It never looks at prompt text,
235
+ file paths, or identity, only timestamps.
236
+ - A run's final result is now kept as a local receipt and retried until the
237
+ board acknowledges it, instead of being sent once and dropped if that one
238
+ attempt failed. A network blip right as a run finishes no longer loses
239
+ the run's outcome; it is delivered as soon as the connection comes back.
240
+
241
+ ### Changed
242
+
243
+ - `glytch init` no longer ends by telling you to link the project folder in
244
+ the app. A coding run needs only the project `init` registers; linking the
245
+ folder only shows its files on the board, and `init` now says so. The
246
+ README's quick start says the same, and its engines table has a Codex row.
247
+ - CLI and API messages that said "Prompting Machine" now say "Glytch" (the
248
+ sign-in prompt, the not-signed-in hint, and the package description).
249
+
250
+ ### Fixed
251
+
252
+ - `bin` paths written without `./`: npm 11.8 warned on the 0.8.2 publish
253
+ that it had "removed" both bins, which read as a broken package until the
254
+ registry record showed it had only stripped the prefix. The plain form is
255
+ what npm stores anyway, and it publishes without the warning.
256
+ - README said Codex sessions could not be launched from the board yet. The
257
+ runner has started Codex sessions since the app-server engine landed; the
258
+ table and intro sentence now say so.
259
+ - A Codex session launched from the board could not read the board, ask a
260
+ question, or file a report, and finished with nothing drawn on the
261
+ canvas: codex-cli requires each board tool call to be approved, and
262
+ nothing was approving them. Codex sessions now set the one approval mode
263
+ that lets those calls through, and turn on Codex's own plan tool so its
264
+ planning steps draw as step discs on the canvas.
265
+ - A Codex session's first line in the transcript was the whole spawn
266
+ preamble and repo primer above what the person actually asked. It now
267
+ echoes just what the person typed.
268
+ - A persistent session running in place, in a folder that is not its own
269
+ Git worktree (for example, a folder that is not a Git repo at all), could
270
+ still show the merge and delete-branch buttons on its run card. That
271
+ option is now recorded only for an isolated worktree or a chat turn.
272
+ - A run that lost its claim on the board, for example because another
273
+ runner picked it up after a restart, kept executing locally instead of
274
+ stopping. The runner now renews its claim every 30 seconds and stops
275
+ local execution as soon as it learns the claim is gone.
276
+ - A run's final metadata (including the worktree path a run card uses to
277
+ decide whether to offer Merge or Delete) could be overwritten by an
278
+ earlier, stale snapshot taken before cleanup ran. Completion now always
279
+ writes the final metadata, so a cleaned-up path is not shown as still
280
+ live.
281
+ - Under sustained load, a lock file's owner record could be read in the
282
+ brief gap between another process creating the lock and writing that
283
+ record, which made a lock just taken look old enough to clear as stale
284
+ out from under its still-running holder. The lock's own age is now used
285
+ as a fallback when its owner record cannot be read yet, so a lock just
286
+ taken is never wrongly cleared.
287
+ - The coding-agent preamble's blocked-ask rule was written in capitals,
288
+ which current models tended to over-apply. It is now written at normal
289
+ case with the same instruction and the same meaning.
290
+
291
+ ### Security
292
+
293
+ - Windows no longer runs an npm `.cmd`/`.bat` shim through `cmd.exe`. The
294
+ runner resolves it directly to the Node or native program it points to,
295
+ and refuses to run anything it cannot parse safely, closing a path where
296
+ data in a request or session could have reached a shell.
297
+ - Worktree cleanup, merge, and delete now confirm with Git that a directory
298
+ is actually a worktree of this project before acting on it, and refuse a
299
+ path that reaches outside the project through a symbolic link. None of
300
+ them fall back to a raw recursive delete when Git cannot confirm this, so
301
+ a worktree operation that cannot be verified is kept on disk rather than
302
+ removed blind.
303
+ - Sign-in now asks for the runner's own access scope by name and names the
304
+ runner's own endpoint as the token's resource, instead of a general board
305
+ grant against the board's URL. A computer already signed in keeps
306
+ working; the next sign-in picks up the narrower grant.
307
+
308
+ ## [0.8.2] - 2026-09-21
309
+
310
+ ### Fixed
311
+
312
+ - A run's worktree based on whatever the main checkout happened to have
313
+ checked out, not the repo's default branch: `makeWorktreeImpl` and the
314
+ chat-worktree path both branched off `git rev-parse HEAD`, so a run cut
315
+ while the checkout sat on a side branch inherited that branch's commits,
316
+ and the merge button (which checked out literal `main`) could have dragged
317
+ the whole side branch into main. Both paths now base on a new
318
+ `defaultBranchOf(root)` helper - local `main`, else local `master`, else a
319
+ HEAD fallback that says so - and the merge op uses the same helper to
320
+ decide what "main" means, so a `master`-only repo merges into `master`
321
+ rather than refusing or checking out a branch that repo does not have. The
322
+ completion text now reads "off main at &lt;sha&gt;" (or "off HEAD at
323
+ &lt;sha&gt;, no main or master branch" on the fallback) instead of a bare
324
+ sha, so which branch a run started from is visible without a `git log`.
325
+ ## [0.8.1] - 2026-09-21
326
+
327
+ ### Fixed
328
+
329
+ - Proven live on spawn-qa3-fable-02 (Claude Code 2.1.270), 2026-09-21: with
330
+ the per-message counting above fixed, a trivial, healthy 14-turn run was
331
+ still stopped - "404k tokens against a 400k tokens cap" - because every
332
+ turn of a Claude Code session re-reads its ~55k system context from cache,
333
+ so the running total `computeTurnTokens` sums climbs roughly 60k a turn:
334
+ `DEFAULT_RUN_TOKEN_CAP` of 400,000 was only about seven turns. It had been
335
+ set to the same figure as `RUN_TOKEN_CEILING`, but that reads ONE turn's
336
+ context fill while this is a RUNNING TOTAL across every turn - the same
337
+ number was wrong by an order of magnitude for the second meaning. The
338
+ default is now 4,000,000 (about sixty turns) everywhere it is set:
339
+ `src/components/run/runLimits.ts` (and its generated copy
340
+ `bridge/lib/runLimits.mjs`), `src/agent/agentRow.ts`'s `DEFAULT_CAPS` (and
341
+ `supabase/functions/board-agent/agentRow.mjs`), and the "Most tokens in a
342
+ run" field's fallback in `CapsSection.tsx`, whose stepper now moves by
343
+ 500,000 instead of 50,000 so the new default is reachable in a reasonable
344
+ number of clicks. The $2 `DEFAULT_RUN_COST_CAP` is still the wall that
345
+ actually bounds money; this one only bounds runaway token volume.
346
+
347
+ - Proven live on spawn-qa3-fable-01, 2026-09-21: Claude Code's stream-json
348
+ emits one `assistant` event PER CONTENT BLOCK of a turn (thinking, text,
349
+ each tool_use), and every one carries the SAME `message.usage` - same
350
+ `message.id`. `bridge/lib/child.mjs` was summing it on every event, so
351
+ three real turns of ~52-58k tokens each were counted eight times over
352
+ (444,842), crossed `DEFAULT_RUN_TOKEN_CAP` (400,000) in 27 seconds, and the
353
+ run was killed with exit 143. `child.mjs` now counts a message's usage
354
+ ONCE per `message.id` - a repeat of the same id adds only the growth since
355
+ the last chunk - and exposes the running total as `outcome.totalTokens`.
356
+ `bridge/lib/runnerCompletion.mjs`'s completion text now names the wall a
357
+ run was stopped at (cost, tokens or the fixed context ceiling), the figure
358
+ it hit against its cap, and whether the child was told to wrap up or had
359
+ no channel left and was stopped - where it used to say only "The session
360
+ exited 143." with no reason. `bridge/lib/runner.mjs` passes the run's
361
+ `limits` through so the cap figure can be named.
362
+ ## [0.8.0] - 2026-09-21
363
+
364
+ ### Fixed
365
+
366
+ - 2026-09-21: the runner's completion flush wrote a run's ENTIRE streamed
367
+ log a second time into `run_events` - proven live on spawn-mub65omw-52eb3e,
368
+ where a four-step plan's TaskCreate/TaskUpdate calls were each counted
369
+ twice and the expanded run drew "eight of eleven done" instead of "four of
370
+ four". `rowsEvents.mjs`'s `settle` now tracks how much of the live stream
371
+ it has already sent and writes only the tail `outcome.log` had not already
372
+ delivered - nothing, on an ordinary run, since the live stream already
373
+ carries it all; the whole log, on a run whose stream never started.
374
+ `bridge/lib/rowsEvents.mjs`. `src/folder/sessionLog/plan.ts`'s `derivePlan`
375
+ is now defensive against an older run whose log is already doubled on
376
+ disk: a second `TaskCreate` reporting a task id already seen is read as
377
+ the same task rather than an extra step. Also rewrote three strings in
378
+ `bridge/lib/runnerCompletion.mjs` that still said "him"/"his" in words a
379
+ person reads, to "them"/"theirs".
380
+
381
+ - QA pass, 2026-09-21: the binary-not-found refusal told a person to
382
+ "restart the runner (`glytch restart`)", a verb `glytch` has never had.
383
+ It now gives the real recipe (`glytch stop`, then `glytch init --yes` in
384
+ the project folder) and says why installing the missing agent alone does
385
+ not fix it - the lookup is cached for the life of the runner process, so
386
+ nothing changes until it restarts. `bridge/lib/runner.mjs`.
387
+
388
+ - QA pass, 2026-09-21: an error the board-runner function sent back as an
389
+ OBJECT (the shape a Supabase gateway error carries) printed as
390
+ `[object Object]` in `runner.log` - seen all night as `board-runner
391
+ answered 502: [object Object]`. The detail is now read from `.message`
392
+ when that is a string, or stringified when it is not, and the same
393
+ applies to `payload.message` when there is no `payload.error` at all.
394
+ `bridge/lib/runnerApi.mjs`.
395
+
396
+ - QA pass, 2026-09-21: every runner.log line under the launchd/systemd
397
+ daemon was written TWICE - once by this package's own `appendFile`, once
398
+ because the plist already redirects the daemon's stdout into that same
399
+ file. The logger now echoes to stdout only when stdout is a real
400
+ terminal, so a person running `glytch runner watch` by hand still sees
401
+ it while the daemon writes each line once; `GLYTCH_LOG_ECHO=1` forces the
402
+ echo back on either way. `bridge/lib/logs.mjs`.
403
+
404
+ - QA pass, 2026-09-21: on a Mac where `opencode` lives only in its own
405
+ installer's `~/.opencode/bin` and an nvm bin directory, a daemon running
406
+ under bash - or with no `$SHELL` set at all, so the login-shell lookup
407
+ never even tries - could not find it. `~/.opencode/bin` is now one of the
408
+ known install directories the runner checks before giving up.
409
+ `bridge/lib/detect.mjs`.
410
+ - 2026-09-21: Claude Code 2.1.270 ships no `TodoWrite` tool at all - verified
411
+ live with `claude -p --allowedTools "... TodoWrite Task"`, which lists
412
+ `TaskCreate`/`TaskUpdate`/`TaskList`/`TaskGet` and no `TodoWrite` - so every
413
+ board-spawned session on that build reported "TodoWrite was not available
414
+ in this session" and drew no plan discs. `CLAUDE_TOOLS` now allowlists the
415
+ four Task tools alongside `TodoWrite` (kept for an older CLI that still has
416
+ it), the `claude` and `acp-claude` preambles tell a session to plan with
417
+ `TaskCreate`/`TaskUpdate` and name `TodoWrite` only as the fallback, and
418
+ `src/folder/sessionLog/plan.ts` derives the same plan from a `TaskCreate`
419
+ (one step per call, in tree order) plus `TaskUpdate` (status by task id,
420
+ read off the create call's own tool_result) as it already did from
421
+ `TodoWrite`. `runWords.ts`'s verb table no longer mislabels either call as
422
+ "Took another step". codex's `update_plan` and opencode's `todowrite` are
423
+ unrelated and untouched.
424
+
425
+ - 2026-09-21, the runner feedback batch: a runner whose daemon PATH lacked the
426
+ agent binary died with `spawn claude ENOENT` AFTER minting a worktree, the
427
+ empty worktree then held the prompt's lock, and the Delete clicked on the
428
+ card was never served because the server only handed out ops for runs
429
+ still marked running. The runner now resolves the agent binary before it
430
+ touches git (known install dirs, then the login shell with `-lc` and, when
431
+ that finds nothing because zsh skips `.zshrc` there, `-ilc`; the LAST line
432
+ of stdout that is an absolute path that exists, so a banner in an rc file
433
+ does not win), refuses in plain words with the technical detail in a
434
+ separate paragraph when it finds none, removes the worktree of a run that
435
+ never started, keeps the lock only while a failed run's tree has commits,
436
+ files or uncommitted work, and checks and retries an op result that did
437
+ not land. The server side ships separately: ops are served for a run in
438
+ any status and a claim older than five minutes with no result is served
439
+ again.
440
+
441
+ - 2026-09-19: `login()`'s own merge (the B7 fix above) moved a merged
442
+ record's `refreshToken` forward but left the still-valid OAuth ACCESS
443
+ token with nowhere to go - `{...existing, accessToken: scripted}`
444
+ overwrote it outright with the personal token, and the OLD
445
+ `accessExpiresAt` stayed on the record describing a credential no longer
446
+ in that field. `hostedToken`'s `personal: true` fallback reads
447
+ `oauthAccessToken`/`oauthAccessExpiresAt` (added the same day by the fix
448
+ above), which the merge never wrote, so a personal-token rejection right
449
+ after a `GLYTCH_BOARD_TOKEN` merge always spent one refresh it did not
450
+ need on a credential that still had hours left. Fixed by moving the
451
+ existing OAuth access token and its expiry into their own fields at merge
452
+ time and clearing the personal record's own (inapplicable)
453
+ `accessExpiresAt`; the reverse direction (`glytch login` over an existing
454
+ personal record) now keeps the personal token and writes the fresh OAuth
455
+ pair into the same shape, and no longer clears a live personal-token dead
456
+ mark it did not fix. `bridge/lib/auth.mjs`.
457
+
458
+ - 2026-09-19: a merged auth record (a `pmb_` personal token persisted
459
+ alongside an existing OAuth pair, brief 43 review B7) marked its dead
460
+ credential under the REFRESH TOKEN's fingerprint even when the PERSONAL
461
+ token was the one the endpoint rejected, because `fingerprintSubject`
462
+ always preferred the refresh token when one was on file. `hostedToken`
463
+ read that same mark, fell through to a forced OAuth refresh on every call
464
+ - not just the first - and a successful refresh cleared the mark outright,
465
+ so the next poll offered the revoked personal token again, got rejected
466
+ again, and the cycle repeated at poll cadence forever, rotating the
467
+ refresh token every time. Fixed with two independent dead marks (one per
468
+ credential, `auth.json.dead` for the OAuth pair and the new
469
+ `auth.json.dead.personal` for the personal token), callers now telling
470
+ `noteCredentialRejected` which bearer was actually sent, an ordinary
471
+ (non-forced) refresh once the OAuth fallback is engaged, and the OAuth
472
+ access token living in its own `oauthAccessToken`/`oauthAccessExpiresAt`
473
+ fields on a merged record rather than sharing `accessToken`/
474
+ `accessExpiresAt` with the personal token. `bridge/lib/auth.mjs`,
475
+ `bridge/lib/hosted.mjs`, `bridge/lib/runnerApi.mjs`.
476
+
477
+ - 2026-09-19, persona walk on the one-paste prompt (`setupPromptText`, four
478
+ findings): the pasting agent runs `glytch init --yes` with no TTY, and
479
+ `glytch init` previously left it with no way to reach the board and no
480
+ clear signal it was ever going to. Claude Code now gets the board server
481
+ registered directly - LOCAL scope (private, wins over the project entry), a bearer header, never the project's
482
+ `.mcp.json` - from a personal token already on this machine when there is
483
+ no terminal to run its own OAuth verb in; codex and opencode, whose CLIs
484
+ were not available to verify a header-add against, instead get one
485
+ actionable last line (`Type /mcp in this tab and sign in to glytch.` or
486
+ the agent's own equivalent) rather than a warn row buried mid-output.
487
+ `glytch init`'s own final line now literally reads "A runner is watching
488
+ this folder." once every check lands, which is the exact phrase the
489
+ pasted prompt's stop condition already named and had never actually seen
490
+ printed. `glytch init --yes` with no agent binary on PATH and no saved
491
+ selection no longer throws before the folder is even registered - it
492
+ finishes, with a warn row naming what to install, and the runner still
493
+ connects to the board. The prompt itself now tells the pasting agent to
494
+ install a missing Node without `sudo` (Homebrew, or `fnm`/`nvm` for one
495
+ user) and to stop and ask rather than run one, so a Mac with no Node
496
+ cannot leave the agent hung on a password prompt nobody can answer.
497
+
498
+ - Brief 43 review, 2026-09-19: three findings on the stored `personal: true`
499
+ token. A revoked personal token no longer bypasses the dead gate forever -
500
+ it now gets its own mark, backoff and `glytch doctor` wording ("rejected",
501
+ pointing at Settings rather than `glytch login`). `glytch login`, run with
502
+ `GLYTCH_BOARD_TOKEN` over an existing OAuth credential, now keeps that
503
+ credential's refresh token in the same record instead of overwriting it
504
+ whole, and `hostedToken()` falls back to it once the personal token has a
505
+ live dead mark. `glytch init --no-login`, run with `GLYTCH_BOARD_TOKEN`
506
+ set, no longer writes the token to `~/.glytch/auth.json` - the variable
507
+ still authenticates that one process, but a CI box that asked not to leave
508
+ a long-lived token on a shared runner's disk is now honoured.
509
+
510
+ - 2026-09-21, board reporting: a coding agent spawned from a prompt
511
+ on one board called `report` (or `propose`/`ask_user`/`mark`) and the
512
+ circles landed on whichever board the account had touched most recently,
513
+ not the board the prompt lived on - board-mcp's `boardFor` falls back to
514
+ `lastBoardId`, unset until the first `get_board`. The run's own row always
515
+ carried `board_id` (`src/runs/api.ts`'s `RunRequestRow`); the runner now
516
+ reads it off the claimed row and sends it on as an `X-Glytch-Board`
517
+ header, next to the existing `X-Glytch-Session`, on every engine's board
518
+ MCP config (`acpServersFor`, the headless `claude` engine's
519
+ `--mcp-config`, `prepareWorkspace` for opencode, `codexBoardArgs` for
520
+ codex). `supabase/functions/board-mcp/index.ts` reads the header the same
521
+ way it already reads `X-Glytch-Session` and seeds `boardTools`'s
522
+ `lastBoardId` with it before any tool call runs, so a session whose FIRST
523
+ call is `report` lands on the run's own board. No header behaves exactly
524
+ as before; a header naming a board this token cannot read fails the same
525
+ way an unknown `board_id` argument already does. `bridge/lib/runner.mjs`,
526
+ `bridge/lib/agents.mjs`, `bridge/lib/codex.mjs`,
527
+ `supabase/functions/board-mcp/index.ts`,
528
+ `supabase/functions/board-mcp/boardHeader.mjs` (new),
529
+ `supabase/functions/board-mcp/tools.mjs`.
530
+
531
+ ### Added
532
+
533
+ - 2026-09-21, brief 46 follow-up C: a Claude Code agent's Limits pane was
534
+ missing the row the design asked for - `AgentCaps` had no per-run token
535
+ cap, so "Most tokens in a run" was left out rather than faked. Added
536
+ `maxTokensPerRun` (optional, defaulting to 400,000 - the same figure
537
+ `RUN_TOKEN_CEILING` already fixes, but a different wall: that one is the
538
+ LAST turn's context fill and never moves, this one is the RUNNING TOTAL
539
+ of every turn's input, output and cache tokens, which an agent may raise
540
+ or lower). It travels on the request the same way `maxSpendPerRun`
541
+ already does, and `runLimitHit` (`runLimits.mjs`) now checks it - after
542
+ cost, before the fixed context ceiling - and latches `limitHit: "tokens"`
543
+ with its own wrap-up message when it fires. `bridge/lib/child.mjs` sums
544
+ input+output+cache off every assistant usage event for the running
545
+ total, where it previously only kept the LAST event for the context
546
+ snapshot.
547
+
548
+ - 2026-09-21, brief 46 (triggers) Move 4: a Codex run now reads its own
549
+ 5-hour and weekly plan windows (`account/rateLimits/read`, one extra call
550
+ on the same app-server connection, right after `initialize`) before
551
+ spending anything. A run whose request names a cap
552
+ (`request.planWindowPercent`, default 80) and whose 5-hour window is
553
+ already past it never reaches `thread/start`: it settles with a receipt -
554
+ "Its plan's 5-hour window is at N%, past the M% it stands down at; it
555
+ will try again after HH:MM" - and nothing is charged (0 turns, cost
556
+ still "unavailable"). An older Codex with no such method reads as "no
557
+ window": the run starts, and the log says so in plain words. The info
558
+ line a person reads in place of a dollar figure now names both windows
559
+ ("Codex reports its plan window, not dollars: 5-hour at N%, week at M%.")
560
+ once a read succeeds, and the runner project's presence carries the same
561
+ reading under `plan` after every Codex run, for the Limits pane to draw
562
+ as two bars. `lib/codexPlanWindow.mjs` is the whole parser/decision/
563
+ message module, deliberately pure and tested on its own; `lib/codex.mjs`
564
+ and `lib/runner.mjs` only wire it in.
565
+
566
+ - `glytch init --yes` and `glytch login`, run with `GLYTCH_BOARD_TOKEN` set,
567
+ now persist it to `~/.glytch/auth.json` as a non-expiring personal token and
568
+ open no browser - so a daemon started from a shell with no such variable of
569
+ its own still has a bearer for the board. `glytch doctor`'s sign-in row
570
+ reports it as "a personal access token is stored in ~/.glytch/auth.json".
571
+
572
+ ### Removed
573
+
574
+ - The `postinstall` script. It pre-fetched the cloudflared binary on every
575
+ install of the package, and cloudflared is only ever used by a SHARED
576
+ PREVIEW - so most machines paid for a download they never used, and a
577
+ machine that could not reach the download printed a warning line during
578
+ `npm install` (reported through the app's feedback). `lib/previewTunnel.mjs`
579
+ already calls `ensureCloudflared` lazily and says in plain words what to do
580
+ when it fails, so the fetch now happens the first time a preview is actually
581
+ shared. `lib/installCloudflared.mjs` went with it; nothing else imported it.
582
+
583
+ ## [0.7.0] - 2026-09-14
584
+
585
+ The published 0.6.0 predates the hosted endpoint and the rows runner. Against
586
+ the app deployed since 2026-09-11 it cannot work: the app no longer writes the
587
+ `.glytch/canvas/` mirror 0.6.0's local MCP server read, and a session launched
588
+ from the board is a row 0.6.0's runner never looks at. Install this one.
589
+
590
+ ### Added
591
+
592
+ - `glytch login` / `glytch logout`: sign this machine in to the hosted board
593
+ endpoint over OAuth once; the credential lands in `~/.glytch/auth.json` and
594
+ every agent session the runner starts uses it.
595
+ - The runner claims, streams and settles runs as rows over the hosted
596
+ `board-runner` endpoint: replies, stops, worktree ops, previews and the
597
+ runner's own presence are rows too. Nothing under `.glytch/inbox/` is read or
598
+ written any more.
599
+ - Read-only plan-mode drafts for Codex sessions.
600
+ - Images attached to a prompt or a reply ride the run rows to the session.
601
+
602
+ - `glytch uninstall [--project] [--keep-login] [--dry-run] [--yes]`: removing
603
+ glytch, its board grant and everything it wrote is one command instead of
604
+ `forget`, `stop` and `logout` run by hand. Confirms once with a printed plan
605
+ before touching anything (`--yes` skips the prompt; a non-TTY without it
606
+ prints the plan and refuses). Without `--project` it walks every registered
607
+ folder (delegating to `uninstallProject`), then stops the login daemon and
608
+ removes its file, revokes this machine's board sign-in at the board and
609
+ forgets the credential (a network failure is a warning, not a failure - the
610
+ credential is still deleted locally), and deletes `~/.glytch` outright.
611
+ `--project` narrows the whole run to the current folder, with no
612
+ machine-wide step. `--keep-login` leaves the machine signed in and its
613
+ credential on disk. `--dry-run` reports what every step would do and
614
+ changes nothing. `forget`, `stop` and `logout` remain as the three partial
615
+ verbs it runs together.
616
+ - `glytch init` signs this machine in when it is not already, running the same
617
+ OAuth flow `glytch login` runs instead of printing "now run `glytch login`".
618
+ `glytch login` stays as its own command for re-auth.
619
+ - `glytch init` runs each selected and installed agent's own sign-in:
620
+ `claude mcp login`, `codex mcp login`, `opencode mcp auth`. Each agent keeps
621
+ its own token under its own account - there is no shared credential written
622
+ into a config file. Claude Code's verb needs 2.1.186 or newer; below that
623
+ floor `init` prints the `/mcp` step, as it did before. An agent that already
624
+ reports holding a token is skipped; Codex does not report one either way, so
625
+ its sign-in runs every time and short-circuits itself.
626
+ - `glytch init --no-login` skips signing this machine in, and
627
+ `glytch init --no-agent-login` skips every agent's sign-in. Both print the
628
+ command they did not run. Without a terminal, `init` prints the commands
629
+ rather than opening a browser, with or without the flags.
630
+
631
+ - A run stops at its own two walls: a spend cap (2 dollars by default, or the
632
+ agent's own `maxSpendPerRun`) and a fixed 200,000-token context ceiling.
633
+ Either wall ends the run the same way - it is told to finish the turn it is
634
+ on, commit whatever is green, report and stop - and the session's report
635
+ carries an extra line naming which wall it was. The runner counts, off the
636
+ engine's own usage events and result envelopes, so nothing depends on a
637
+ session noticing its own size. The two do not land at the same moment and
638
+ the code says so: a context wall fires mid-turn while stdin is still open, a
639
+ cost wall as the turn ends. A one-shot child, which has no stdin to talk to,
640
+ falls back to the existing kill path. The ceiling is a fixed number and
641
+ never a share of the model's window.
642
+
643
+ ### Changed
644
+
645
+ - **`npm publish` no longer runs the test suite.** `prepublishOnly` was
646
+ `node --test`, so every publish re-ran 1,070 tests, two of which race real
647
+ concurrent filesystem operations and assert an exact winner count. On one
648
+ machine, in one shell, those two intermittently answer 0 or 2 instead of 1,
649
+ and a person trying to ship met that instead of a release roughly ten times.
650
+ It never caught a real defect in a release. `prepublishOnly` is now
651
+ `scripts/prepublish.mjs`: it checks that every file `files` promises exists,
652
+ that both bins carry a shebang, that all 63 shipped modules parse, and that
653
+ the version is still free on npm. It takes about a second and cannot answer
654
+ differently twice. The suite still runs in `npm run verify` and as its own
655
+ step in the release workflow, where a failure still stops the publish.
656
+ - The package description on npm. It advertised "a stdio MCP server that
657
+ speaks the app's folder contract (canvas/ mirror out, inbox/ ops in)" - a
658
+ server that was deleted and a contract that is gone. It now says what the
659
+ package does: run coding agents from the board, in a worktree, streamed back
660
+ to the canvas.
661
+ - Admission's winner-count tests print why a count is wrong - the refusals,
662
+ the lock's bytes, the directory listing - because the directory is deleted
663
+ before anyone can look at it. One real gap closed alongside: the lock's
664
+ bytes and the inode filed as their generation were two separate reads of a
665
+ path another caller could replace in between, so they come off one open
666
+ handle now.
667
+
668
+ - `glytch init` writes the hosted endpoint URL (`https://prompt.glytch.ai/mcp`)
669
+ into each selected agent's configuration instead of a local server command;
670
+ the agent's own OAuth flow finishes connecting it, and `glytch doctor` names
671
+ that step when it is missing.
672
+ - On Windows the login daemon is registered from an XML task definition
673
+ (`~/.glytch/ai.glytch.runner.xml`) naming your own account on the trigger and
674
+ the principal, which a standard user may register. The old `schtasks /sc
675
+ onlogon` command line registered an all-users task and was refused with
676
+ "Access is denied" outside an elevated shell; it stays only as a fallback.
677
+ - `glytch doctor` rows can carry their own next step; a refused Windows
678
+ registration names the elevated re-run or the by-hand runner command instead
679
+ of sending you back to `glytch init`. A refused `schtasks /query` is no longer
680
+ read as "task missing", and a row whose load failed no longer says "loaded now".
681
+
682
+ - `glytch doctor`'s `board sign-in` row reports whether the credential WORKS,
683
+ not just whether `auth.json` exists. It used to read green on exactly the
684
+ machine that could not authenticate. It reads the dead-credential mark the
685
+ daemon leaves beside the credential, so it still costs no network call.
686
+
687
+ - `glytch init` step 1 is an arrow-key checkbox picker instead of a numbered
688
+ list and a typed comma-separated answer: Up/Down (`j`/`k`) move, Enter or
689
+ Space toggles the row under the cursor, `a` toggles all, and a "Continue"
690
+ row below the agents is where Enter finishes - Escape/Ctrl-C cancels the
691
+ same way the old prompt did on Ctrl-C. Piped or non-TTY stdin, `--agents`
692
+ and `--yes` are unchanged - there is no picker to open without a terminal.
693
+ No new dependency (`bridge/lib/picker.mjs`).
694
+ - The MCP server key is `glytch` (was `board`). `glytch init` and `glytch
695
+ doctor` rename an existing entry on the hosted URL in place, the same
696
+ upgrade path a legacy spawned-command entry already took. Per this
697
+ package's own definition this is a breaking change to the folder contract
698
+ - renaming what a tool is called - made survivable by the in-place upgrade.
699
+ - The terminal output is one presentation: `init` is a numbered four-step
700
+ walk, browser hand-offs name which agent is asking and show the URL dimmed,
701
+ results are green, warnings yellow, failures red, and the first line of an
702
+ error is red with the rest left plain so a pasted error stays readable.
703
+ `NO_COLOR`, `TERM=dumb` and a pipe still get plain text, and everything
704
+ wraps to the terminal width. No new dependency.
705
+
706
+ - `glytch doctor`'s `board sign-in` row names WHICH account the credential
707
+ reaches: the email, or the first segment of the account id when the board can
708
+ name the id but not the address. "Signed in" was the whole reason before, and
709
+ a credential left on a previous account reads exactly like one on the right
710
+ account - which is how two days of session reports landed on a board nobody
711
+ was looking at. The name comes from the board's `presence` op, which
712
+ resolved that very token, and the row already made that call, so it costs no
713
+ extra round trip.
714
+
715
+ ### Removed
716
+
717
+ - Unused mirror reader, mirror-based board-root discovery, file-tool ID
718
+ validator, and generated bridge board-digest output. The canonical digest
719
+ source and the live hosted copies remain.
720
+ - Unused file heartbeat writers, receipt/grace defaults, file-based per-prompt
721
+ worktree check, and the macOS `launchagent.mjs` adapter. Row-backed
722
+ presence, admission, and historical scratch cleanup are unchanged. The
723
+ macOS tests now use the current login-daemon API directly, and the atomic
724
+ replacement tests exercise the live folder registry.
725
+ - `pm-agent`, the local stdio MCP server, and its `pm-agent` bin. `glytch mcp`
726
+ prints where the hosted endpoint is instead.
727
+ - The `.glytch/` mailbox (spawn requests, claims, receipts, logs, sidecars and
728
+ the merge/delete/relaunch/preview signals as files). Folder linking stays for
729
+ project files only.
730
+ - `GLYTCH_BOARD_SOURCE` and the `.glytch/config.json` row source added in
731
+ 0.6.0: every board read is over the hosted endpoint now. `glytch login`'s
732
+ credential is the normal way in; `GLYTCH_BOARD_TOKEN` with a personal access
733
+ token still works for a script.
734
+
735
+ - The `board mirror` row is gone from `glytch doctor`. It read `.glytch/canvas/`,
736
+ a mirror nothing has written since the `.glytch/` mailbox contract was
737
+ deleted, so on every correctly installed machine it reported FAIL, made
738
+ `allGreen` false and gave `glytch doctor` exit 1, while telling the person to
739
+ "open the board and link this folder" on a folder that was already linked.
740
+ Whether a folder is linked is what the `daemon beating` row answers now.
741
+
742
+ ### Fixed
743
+
744
+ - A credential the board has REFUSED is now presented once and then backed
745
+ off, instead of being re-presented at the watch loops' poll cadence for as
746
+ long as the machine is on. Five folders at `pollMs` 5s meant five posts of a
747
+ revoked refresh token every five seconds, and presenting a spent refresh
748
+ token is itself what makes board-oauth revoke the family, so the loops kept
749
+ re-arming the alarm that had killed them. `hostedToken` now tells a DEAD
750
+ credential (`invalid_grant`, `invalid_client`, or a 401 that survived the
751
+ forced-refresh retry) from a merely stale one, and backs off 60s doubling to
752
+ 30 minutes; a 5xx, a timeout or an unreadable body is still treated as a
753
+ blip and retried as before. The backoff clears itself the moment the
754
+ credential changes, so `glytch login` needs no other step, and a successful
755
+ refresh deletes it outright. The mark lives beside `auth.json` as a SHA-256
756
+ fingerprint of the refresh token, never the token, so a second process sees
757
+ it too.
758
+ - A person whose runner cannot authenticate is told in one sentence what to do
759
+ ("Your Prompting Machine sign-in has expired and cannot be renewed: run
760
+ `glytch login` to sign in again"), once, rather than being left to read a
761
+ log. It replaces the "Not signed in to the hosted board" wording on this
762
+ path, which was inaccurate and sent people to `glytch init`: they ARE signed
763
+ in, with a credential the board will not take.
764
+
765
+ - `glytch login` against a local app (`GLYTCH_APP_URL` pointed at a dev server
766
+ mirroring vercel.json's rewrites) derived the RFC 8707 `resource` as
767
+ `${appUrl}/mcp` and was refused by the real authorize endpoint ("This server
768
+ issues tokens for https://prompt.glytch.ai/mcp only") - the resource a token
769
+ is for is whatever the app's `/.well-known/oauth-protected-resource/mcp`
770
+ document names, not the app origin, and board-oauth's configured resource is
771
+ production regardless of which origin a person pasted. `login()` now reads
772
+ that document first and uses its `resource` (and the authorization server it
773
+ names) throughout, storing the real endpoint as `mcpUrl` while `appUrl`
774
+ stays the configured app; an older deploy with no such document falls back
775
+ to the previous guess and says so.
776
+ - `glytch doctor`'s "daemon beating" row reported a live daemon as stopped
777
+ forever: it read a local mark file nothing over rows has written since
778
+ brief 30 replaced runner presence with `runner_projects.last_seen_at`, so
779
+ the row's mtime froze at whatever an earlier, file-based daemon last wrote.
780
+ The row now asks the board directly, through a new read-only `presence` op.
781
+
782
+ - The money figure on a Claude Code run was always empty. The runner read
783
+ `costUSD`; Claude Code emits `total_cost_usd`. It now reads
784
+ `total_cost_usd` first and falls back to the two older spellings, and takes
785
+ the LAST envelope rather than adding them up, because the figure is the
786
+ session total so far and summing it bills a long run several times over.
787
+
788
+ ## [0.6.0] - 2026-09-11
789
+
790
+ ### Added
791
+
792
+ - A board with no `canvas/` mirror can now be read and written over the hosted
793
+ board endpoint, so a folder that has never had the app open on it still has
794
+ a board. Turn it on with `GLYTCH_BOARD_SOURCE=rows` and a personal access
795
+ token in `GLYTCH_BOARD_TOKEN`; `get_board`, `get_node` and `propose` answer
796
+ from the cloud, and everything else still needs the folder. A mirror on this
797
+ disk always wins where there is one.
798
+ - The row source can be configured in `.glytch/config.json` in the board root
799
+ (`boardSource`, `boardToken`, `boardUrl`) instead of the environment, for a
800
+ bridge started by a launch agent that has no environment to read.
801
+
802
+ ### Changed
803
+
804
+ - The "no canvas/ mirror found" refusal now names the row source as the second
805
+ way to read the board, rather than saying only that the app must be open.
806
+ An endpoint that answers 401 says the access token was revoked and where to
807
+ mint a new one, instead of reporting a generic refusal.
808
+ - Three unused internal exports are gone: `forgetServerRecord`,
809
+ `tellFileExists` and `ACP_PRICES_CHECKED`. No tool and no file in the folder
810
+ contract changes.
811
+
812
+ ### Fixed
813
+
814
+ - An admission lock whose bytes do not parse, including the 0-byte one an
815
+ unclean shutdown can leave, is recovered instead of refusing every caller
816
+ forever. A lock that cannot be read is still never stolen, and both
817
+ refusals now name the cause: the errno, or the pid and host holding it.
818
+ - `glytch init`/`glytch doctor` rewrite and reload the login daemon file when
819
+ it names a different script than the one running the command (resolved
820
+ with `realpath`, so an npm-linked symlink still counts as the checkout),
821
+ instead of reporting "PATH captured" and leaving a stale package running -
822
+ previously the only fix was deleting the file by hand and reinstalling.
823
+ ## [0.5.0] - 2026-09-10
824
+
825
+ ### Added
826
+
827
+ - `get_node` returns images attached to a circle as native MCP image blocks,
828
+ each labelled with the node and field it came from. A stored image the
829
+ mirror does not hold is reported as unavailable instead of being omitted.
830
+ - Reference images on a run are validated before an agent starts: PNG, JPEG,
831
+ GIF and WebP are delivered whole, and an unsupported format or damaged data
832
+ refuses the run naming the attachment, with nothing spawned and nothing
833
+ billed.
834
+ - Sessions are instructed to inspect every reference image and record what
835
+ they see before planning or editing, and to compare finished visual work
836
+ against the references.
837
+ - An ACP adapter must advertise image prompt support before an image-bearing
838
+ turn is sent; without it the turn is refused rather than sent as text alone.
839
+
840
+ ### Changed
841
+
842
+ - A planning draft researches read-only in the project checkout instead of
843
+ being given its own git worktree.
844
+ - A planning draft is asked for a structured report: findings in their own
845
+ sections and the work under an `## Implementation plan` heading with one
846
+ `### Step N: ...` heading per item.
847
+ - Engines without an image channel receive every validated image as a file
848
+ written beside the run; a write that fails aborts the run instead of
849
+ continuing with fewer images.
850
+
851
+ ### Fixed
852
+
853
+ - Runs delivered the prompt without the appended image file paths on every
854
+ path except stdin, so file-based engines were told nothing about the images
855
+ written for them.
856
+ - Reference images in a reply to a running session are validated on arrival;
857
+ a malformed one ends the turn with an error instead of being dropped
858
+ silently mid-session.
859
+
860
+ ## [0.4.2] - 2026-09-09
861
+
862
+ ### Added
863
+
864
+ - Questions created through MCP can include an optional recommended answer and
865
+ explanation. Recommendations are advisory; the human still chooses an answer.
866
+
867
+ ### Fixed
868
+
869
+ - Automatic worktree cleanup preserves the worktree when Git cannot inspect
870
+ its changes or establish a comparison base.
871
+ ## [0.4.1] - 2026-09-09
872
+
873
+ ### Changed
874
+
875
+ - `glytch init` and `glytch doctor` now use readable grouped status sections,
876
+ wrapped details, terminal colors with `NO_COLOR` support, actionable failure
877
+ summaries, and a home-folder warning. Doctor distinguishes a loaded daemon
878
+ from a fresh or stale folder heartbeat.
879
+ - README setup and troubleshooting guidance now keeps project-folder commands,
880
+ restart/update steps, and shared-preview requirements together.
881
+ ## [0.4.0] - 2026-09-09
882
+
883
+ ### Added
884
+
885
+ - Automatic member-only local previews for shared boards, with scoped short-lived
886
+ authorization grants and verified dev-server ownership. The preview issuer and
887
+ public verifier configuration are required; missing configuration fails closed.
888
+ - Automatic checksum-verified Cloudflare tunnel installation on macOS x64/ARM64,
889
+ Linux x64/ARM64 and Windows x64, with first-preview retry and a local cache.
890
+
891
+ - `glytch init` lets users choose one or more coding agents per project,
892
+ with `--agents claude,codex` for scripts and `--yes` for saved/detected
893
+ defaults. Project choices are remembered and respected by doctor and
894
+ background repair.
895
+ - Codex board MCP registration in project `.codex/config.toml`, preserving
896
+ existing settings and board entries. No global Codex config is changed.
897
+
898
+ ### Fixed
899
+
900
+ - Preview requests reject unrelated processes that take over a dev-server port.
901
+ Ownership is checked again before forwarding member requests and WebSockets.
902
+ - Preview shutdown cancels pending startup and closes remaining tunnels even if
903
+ writing an offline receipt fails. Public gateways bound slow clients and filter
904
+ reserved credentials and connection-specific headers.
905
+
906
+ - Project config updates retain private file permissions.
907
+ - Release version bumps update and commit the package lockfile alongside
908
+ package metadata and the changelog; non-increasing versions are rejected.
909
+
910
+ ### Changed
911
+
912
+ - Node.js 22 or newer is now required, matching the bundled Claude ACP
913
+ adapter's existing runtime requirement.
914
+
915
+ - Selected opencode installations receive project JSON/JSONC configuration
916
+ directly, preserving comments and avoiding a second interactive setup.
917
+ - First-time non-interactive init requires explicit choices or `--yes`;
918
+ later runs reuse saved choices. Legacy installs retain their existing
919
+ repair behavior until a selection is saved.
920
+ ## [0.3.1] - 2026-09-07
921
+
922
+ ### Changed
923
+
924
+ - **The `session-end` op says when a run was stopped, and why a start was
925
+ refused.** 2026-09-07, the runner QA sweep. `endSession` carries
926
+ `stopped: true` when the session ended because of a stop request (the
927
+ `.stop` file, which the runner now consumes only after the end op is
928
+ written), and `reason` when the start-receipt gate gave up. Both absent
929
+ unless set, so every earlier end op reads the same. The app's record read
930
+ "Finished" for a stopped run before this.
931
+ - **The start-receipt gate waits 120 s and polls every 500 ms.** It was 30 s,
932
+ sized for an app whose inbox tick was 10 s; measured receipts that day ran
933
+ 1 to 64 s and two real runs were refused as never spawned. A refusal now
934
+ files the end op with its reason instead of leaving a record whose end
935
+ came before its start.
936
+ - **The spawn preamble asks for a plan.** Before the first edit, 3 to 8 steps
937
+ through the engine's own todo tool, kept current as items finish, plus
938
+ `Decided:` lines for choices worth seeing. opencode's tool is `todowrite`,
939
+ lower case, and was listed as absent; no opencode run had ever drawn a
940
+ step disc.
941
+ - **The runner's words after brief 26.** The preamble, `ask_user`'s
942
+ description, `release` and the stop receipt spoke of a session frame, a
943
+ DONE badge and a terminal; a run is a record on the prompt now, its
944
+ card shows it, questions land as circles, and a report goes through the
945
+ Glytch Agent.
946
+ - **A prompt accepts a comment.** `report.mjs` no longer refuses the
947
+ run's outcome line on the prompt it is about; the app accepts it since the
948
+ same day.
949
+
950
+ ### Added
951
+
952
+ - **A chat turn's `cwd` is resolved against the watched folder, and
953
+ `mintIfMissing: true` cuts the agent's chat worktree.** Brief 25 session 1b,
954
+ 2026-09-07: the two halves of the brief's section 3 meeting, after session 2
955
+ fixed what the app can actually send. The app writes `cwd` RELATIVE to the
956
+ folder root, because a browser directory handle has a name and not a path,
957
+ and it cannot create a worktree at all. So the runner resolves a relative
958
+ `cwd` against the folder whose `.glytch/inbox/spawn` it is watching, uses an
959
+ absolute one as it stands, and REFUSES anything that escapes the folder
960
+ (`..`, an absolute path outside it) with the resolved path in the receipt.
961
+ When `mintIfMissing: true` and the directory is not there, it cuts it once as
962
+ a git worktree on `glytch/chat-<agentId>` off the checked-out commit - the
963
+ same base `makeWorktree` picks, and the branch name read off the directory
964
+ name - with `node_modules` symlinked the way a session's tree gets it, and
965
+ logs one line. Without the flag session 1's refusal stands unchanged, and the
966
+ mint refuses both a folder that is not a git repository (a chat needs one for
967
+ its own worktree; there is no unisolated fallback to offer) and any path that
968
+ is not `<root>/.claude/worktrees/chat-*`, so a request file cannot turn "mint
969
+ if missing" into "make a directory anywhere in my folder". The sidecar's
970
+ `worktreePath` is the RESOLVED ABSOLUTE path, which is what makes the second
971
+ turn resume: the app hands it straight back as the next turn's `cwd`.
972
+ Ruling 1 (one long-lived chat worktree per agent per folder, made once and
973
+ kept) is now held on three sides: `cleanWorktree` refuses a chat path, the
974
+ sweep's never-touch list says in words that it removes no worktree and that a
975
+ later stale-worktree rule must skip `chat-*`, and the orphan reaper steps
976
+ over a `chat-` name. A chat worktree is removed when the AGENT is deleted,
977
+ which is not built. 12 more tests in `test/chatturn.test.mjs` (39 there,
978
+ 688 in the bridge suite).
979
+
980
+ - **`kind: "chat-turn"` on a spawn request: a DM with a coding agent answered
981
+ by the daemon.** Brief 25 session 1, on the rulings of 2026-09-07. Ruling 1A
982
+ is that a coding agent's chat turn runs THROUGH the daemon, as a persistent
983
+ Claude Code conversation on his subscription, rather than as the OpenRouter
984
+ API call every chat turn was until now - so an agent whose engine said
985
+ `claude-code` had never once run on Claude Code when you talked to it. This
986
+ is the bridge half. **A request file gains one field and every request
987
+ written before today is untouched: absent `kind` means `"session"`.** A
988
+ chat-turn request names `cwd` (the room's own long-lived chat worktree,
989
+ ruling 1) and `roomId`, and differs from a session in five ways and no
990
+ others: no frame on the board and so no wait for the app's receipt (ruling
991
+ 3, chat bubble only); no worktree minted, and a `cwd` that is not a
992
+ directory is refused rather than fallen back from; no repo primer, no
993
+ attempts log and no runner preamble, all three of which are about a frame it
994
+ does not have; a per-ROOM lock (`activeChatTurnForRoom`) in place of the
995
+ per-prompt one, so one live turn per room; and `chat` on line one of the
996
+ receipt, after the `OK`/`ERROR` both sides of the contract parse by. Stop
997
+ cancels the turn and KEEPS the conversation (ruling 5): the sidecar carries
998
+ the conversation id with no `expired` mark, and the sidecar is now written
999
+ for every chat turn rather than only a persistent one, because a first
1000
+ message has no session id to resume and that file is how the room learns
1001
+ which conversation it opened. No caps are checked here (ruling 2, "no need
1002
+ for caps with coding agents"); an empty `model` passes no `--model` (ruling
1003
+ 7). An engine name this bridge does not have is REFUSED for a chat turn
1004
+ where a session falls back to the default - answering as `claude` would be
1005
+ the exact confusion the feature exists to end (`isCodingCli`,
1006
+ `lib/agents.mjs`). `SPAWN.md` gains a "Chat turns" section; 27 tests in the
1007
+ new `test/chatturn.test.mjs`, three compatibility pins in
1008
+ `test/runner.test.mjs`, and `chat-envelope` / `chat-hang` modes in the fake
1009
+ binary. The app half - room routing, the streamed bubble, the ledger - is
1010
+ sessions 2, 3 and 4 of the brief and is not in this release.
1011
+
1012
+ ### Fixed
1013
+
1014
+ - **A resume ran in the wrong directory and never started.** Claude Code keeps
1015
+ conversations per working directory, and `runRequest` built the command line
1016
+ - `--resume <id>` included - a hundred lines BEFORE it chose the working
1017
+ directory. So a "run again" whose recorded worktree had been merged away got
1018
+ the fallback's fresh tree with the resume flag still attached, and the child
1019
+ answered `{"subtype":"error_during_execution","num_turns":0,
1020
+ "total_cost_usd":0,"errors":["No conversation found with session ID: ..."]}`.
1021
+ The receipt said "ended with an error result" and the frame read DONE with
1022
+ nothing in it (spawn-mtqay776-f76882, 2026-09-06 21:06 UTC). The worktree is
1023
+ chosen FIRST now and the resume is decided from the tree actually obtained.
1024
+ Three behaviours follow: a resume whose `worktreePath` is on disk runs IN it,
1025
+ on its existing branch, with no new tree; a resume whose tree is gone starts
1026
+ a FRESH session in a fresh tree, is told in one line of prompt that its
1027
+ history could not come with it, says so in plain words at the top of the
1028
+ receipt, and records the NEW session id in the sidecar so the next run
1029
+ resumes something that exists; and a `No conversation found` envelope at 0
1030
+ turns and $0.00 - the tree was there and Claude Code still had no record -
1031
+ retries ONCE as a fresh session in that same worktree. That retry does not
1032
+ break "Exactly once": the envelope says on its face that nothing was billed
1033
+ and nothing was done, so it is the first attempt happening rather than a
1034
+ second one being paid for, and a second failure is terminal. `SPAWN.md` gains
1035
+ a "Resume - the worktree IS the session's life" section; five tests in
1036
+ `test/runner.test.mjs` and a `no-conversation` mode in the fake binary.
1037
+
1038
+ ### Added
1039
+
1040
+ - **Windows, session 5 of brief 21: the words, and the app half.** Two things
1041
+ a Windows user meets that are text rather than process control, and the one
1042
+ fact both of them need. Ruling 2A: `.mcp.json` names the board server
1043
+ `{"command":"cmd","args":["/c","glytch","mcp"]}` when the WRITER is on
1044
+ Windows, and keeps `{"command":"glytch","args":["mcp"]}` everywhere else -
1045
+ npm installs the bin as `glytch.cmd` there, and an MCP client spawning
1046
+ `glytch` with no shell hits the same Node 18.20.2 / 20.12.2 refusal every
1047
+ other binary in this package hits. Both writers take the platform as an
1048
+ argument (`lib/mcpjson.mjs` defaults to `process.platform`, because `glytch
1049
+ init` IS that machine; the app is handed the runner's), and
1050
+ `test/fixtures/mcp-json-cases.json` grew nine Windows cases that both sides'
1051
+ tests read, so the two copies still cannot drift. A file already carrying
1052
+ EITHER shape is left exactly as it is, which is the presence rule this file
1053
+ has always had and is what stops a folder shared between a Mac and a
1054
+ Windows box being rewritten back and forth. Ruling 3A: the sentence a
1055
+ spawn prompt carries when the Browser switch is on - how to start a dev
1056
+ server that outlives the session - is PowerShell's `Start-Process
1057
+ -WindowStyle Hidden` with `-PassThru` for the pid on Windows, and the
1058
+ `setsid` / `nohup` words unchanged to the byte everywhere else; option B,
1059
+ `start /b`, was rejected because it does not reliably leave the console and
1060
+ the child dies with it. The `.glytch-server.json` record is identical on
1061
+ both - only the words changed. THE MACHINE IS THE RUNNER'S, NOT THE
1062
+ BROWSER'S: the liveness mark gains an optional `platform` key beside `at`,
1063
+ `version`, `updateAvailable` and `agents` (`lib/platform.mjs`, written by
1064
+ both `lib/multiwatch.mjs` and `spawn-runner.mjs`), which is
1065
+ `process.platform` or `wsl` when `/proc/version` names Microsoft - naming
1066
+ WSL rather than folding it in, per ruling 6C, because WSL is the Linux
1067
+ that has `setsid`. A runner from before this brief writes no platform and
1068
+ the app reads that as the POSIX shape rather than as an error. `README.md`
1069
+ and `SPAWN.md` gained a Windows section.
1070
+
1071
+ - **Windows, session 1 of brief 21.** A `windows-latest` CI job
1072
+ (`.github/workflows/bridge-tests.yml`) now runs the bridge test suite
1073
+ alongside `ubuntu-latest` on every change under `bridge/`, and per
1074
+ ruling 7A it BLOCKS - no `continue-on-error`, so a Windows regression fails
1075
+ the same way a macOS one does rather than reporting green next to a red
1076
+ platform nobody looks at. Expected to be red for now: brief 19 scoped
1077
+ install to macOS, and the daemon, spawning through `.cmd` shims, and
1078
+ killing a process tree are later sessions' jobs. This session only fixed
1079
+ the seams that were pure bugs on any platform - none of them behavior
1080
+ changes on macOS, all of them proven with Windows-shaped strings rather
1081
+ than a Windows machine: `sync.mjs`'s `pathEntries`/`mergePaths` split PATH
1082
+ on `path.delimiter` instead of a hardcoded `":"`; `worktree.mjs`'s
1083
+ `ensureIgnored` recognizes an absolute `git rev-parse --git-common-dir`
1084
+ path with `path.isAbsolute` instead of `startsWith("/")`;
1085
+ `procs.mjs`'s `parseProcessTable` and `reapableWorktreeName` take an
1086
+ argv0's basename with `path.win32.basename` (a strict superset of the old
1087
+ `lastIndexOf("/")` slice, since it also treats `\` as a separator) instead
1088
+ of missing every backslash path; `reapableWorktreeName`'s worktree-path
1089
+ pattern now matches on whichever separator built `wtRoot`, not a hardcoded
1090
+ `/`; `messaging.mjs`'s session-list display name uses the same
1091
+ `path.win32.basename` instead of `.split("/").pop()`.
1092
+
1093
+ - **Windows, session 3 of brief 21: what stops a process, and what proves it is
1094
+ the right one.** Every kill in the package was a POSIX signal, and on Windows
1095
+ Node maps both SIGTERM and SIGKILL to TerminateProcess - so the grace before
1096
+ the hard kill was a fiction, and it ended one pid while `claude.cmd` runs the
1097
+ real agent as a grandchild that survives and keeps billing. Ruling 4A
1098
+ (2026-09-07): `taskkill /pid <pid> /t` then `/t /f` after the same grace,
1099
+ behind the SAME verification guard. One seam, `lib/kill.mjs`, now holds both
1100
+ spellings and is used by the session kill closure, the worktree reaper and
1101
+ the dev-server stop; the macOS path is the same two signals sent by the same
1102
+ caller, unchanged to the byte. `verifyRecordedServer` gained the two Windows
1103
+ evidence sources that guard makes it need - `netstat -ano` for the pid
1104
+ listening on the recorded port, `Get-CimInstance Win32_Process` for the start
1105
+ time and command line - injected exactly where `ps` and `lsof` were, with
1106
+ pure parsers pinned against sample output. Option B of the same ruling, kill
1107
+ with no verification, was rejected on its face: it kills whatever inherited a
1108
+ reused pid.
1109
+
1110
+ - **Windows, session 2 of brief 21: spawning at all.** The three worst things
1111
+ brief 21's audit found - the `.cmd`-shim refusal (Node's own security fix
1112
+ since 18.20.2 / 20.12.2) and the `Path`-casing bug in the child's env
1113
+ allowlist - are fixed together, since either one alone still stops every
1114
+ session. A new `runnableCommand(bin, args, {platform, env})` in
1115
+ `bridge/lib/windows-command.mjs` resolves a binary to its `.cmd`/`.exe`/
1116
+ `.bat` through `PATHEXT` and `Path` (or hands it unresolved to a shell,
1117
+ which does its own search), pure and injectable so the whole thing is
1118
+ proven on macOS with a fake platform, env and file-existence check. It is
1119
+ used by `detect.mjs`'s agent probes (and, through the same default `exec`,
1120
+ `sync.mjs`'s opencode registration), `update.mjs`'s npm version check, and
1121
+ `child.mjs`'s session spawn - both `claude` and `opencode`, per ruling
1122
+ 5B that both engines run on Windows in v1, codex staying grey. `childEnv`'s
1123
+ allowlist lookup is case-insensitive on Windows only (`Path` now matches
1124
+ `PATH`) and gains the Windows names a child needs to run at all:
1125
+ `SystemRoot`, `windir`, `COMSPEC`, `PATHEXT`, `USERPROFILE`, `HOMEDRIVE`,
1126
+ `HOMEPATH`, `APPDATA`, `LOCALAPPDATA`, `ProgramData`, `ProgramFiles`,
1127
+ `NUMBER_OF_PROCESSORS`, `OS` - without `SystemRoot` a Windows child cannot
1128
+ resolve DNS at all. Both session spawns now set `windowsHide: true`,
1129
+ harmless on POSIX. Detection and killing/cleanup (sessions 1, 3 and 4) are
1130
+ separate sessions and are not in this change.
1131
+
1132
+ - **A login daemon on Windows and on WSL, not only on macOS** (2026-09-07,
1133
+ Windows, session 4 of brief 21). `glytch init` installs a Task Scheduler
1134
+ per-user `onlogon` task on Windows (ruling 1A: Task Scheduler was chosen
1135
+ over a Startup shortcut and over a service because it can be QUERIED, which
1136
+ is what keeps `glytch doctor`'s two halves - is the daemon installed, and is
1137
+ it actually beating - working there) and a systemd user unit on WSL and
1138
+ Linux (ruling 6C: both native Windows and WSL count). One interface with
1139
+ three implementations, `lib/logindaemon.mjs`; `lib/launchagent.mjs` is now a
1140
+ thin re-export of the launchd one. `glytch doctor`'s first row names the
1141
+ machine - `macOS`, `Windows`, `WSL (Linux)` or `Linux` - and the daemon row
1142
+ is labelled in that platform's own words. An older WSL with no `systemctl
1143
+ --user` is told so in the row, with the fallback to type instead, rather
1144
+ than being reported as a daemon that merely is not running. Folder contract
1145
+ and install surface, so it belongs in this file.
1146
+ - **A `git` row in `glytch doctor`, as a warning** (ruling 8B). No git means
1147
+ no worktree, so sessions run unisolated in the linked folder - and on
1148
+ Windows it also means no Bash tool, which comes from Git for Windows. The
1149
+ row says what is lost and prints `warn`; it is not a FAIL and doctor still
1150
+ exits 0.
1151
+ - `GLYTCH_NO_DAEMON_INSTALL=1` writes the daemon's file and registers nothing,
1152
+ on all three platforms. `GLYTCH_NO_LAUNCHCTL=1` is its older name and keeps
1153
+ working.
1154
+
1155
+ - **A browser frame's dev server outlives the session that started it.** The
1156
+ second thing "a finished session exits" ended, caught by the 2026-09-06 test
1157
+ pass: a session with the Browser switch on ran `npx serve -l 8765`, filed the
1158
+ frame, finished, and the server died with the child - the frame on the board
1159
+ pointing at a port with no listener. Ruling A the same day: the session
1160
+ starts the server DETACHED and the cleanup sweep stops it later. The session
1161
+ records `{pid, port, cmd, startedAt}` in `.glytch-server.json` in its
1162
+ worktree; the runner mirrors that to `<id>.server` in the spawn channel at
1163
+ teardown so the record outlives the tree. `<id>.server` does not end in
1164
+ `.json` on purpose - the request scan would have read it as a spawn request.
1165
+ Folder contract, so it belongs in this file rather than in the internals.
1166
+ - **A fourth signal file, `<id>.relaunch`: start that dev server again.** The
1167
+ ask the same day - "When a dev server gets detached, add a button in terminal
1168
+ to relaunch dev server in this branch. Allows me to test browser." Because
1169
+ the sweep above stops the server later, a session frame outlives its port. The
1170
+ app files `<id>.relaunch` (an optional `{worktreePath}` and nothing else); the
1171
+ runner re-runs the `cmd` in the session's own record, detached, in the
1172
+ recorded worktree, writes a fresh record for both copies, and answers in
1173
+ `<id>.relaunch.result.txt` - its own file, so a relaunch can never overwrite
1174
+ the session's `.result.txt`. It costs no session and no credits. It refuses,
1175
+ by name, when there is no record, when the record has no `cmd`, when the
1176
+ worktree is gone, when the recorded port is held by something else, and when
1177
+ the command exits within 700ms of starting; a server that is still running is
1178
+ answered with the pid and port it is already on rather than started twice.
1179
+ The signal NEVER carries a command: the runner runs what a session recorded,
1180
+ so this is not a way to run arbitrary shell from the canvas.
1181
+
1182
+ ### Changed
1183
+
1184
+ - **The daily sweep stops processes as well as removing files.** It stops the
1185
+ dev server recorded for a request when that request is swept at 14 days, and
1186
+ as soon as the worktree the server was serving is gone. It never kills on a
1187
+ bare pid: the process has to be listening on the recorded port or carrying
1188
+ the recorded command line, with a start time within ten minutes of the
1189
+ record's, or the sweep logs one line and leaves it alone. Pids are reused,
1190
+ and a wrong SIGTERM here is a SIGTERM to whatever the machine handed that
1191
+ number next.
1192
+
1193
+ ### Fixed
1194
+
1195
+ - **Brief 21: CI green on ubuntu and windows, and what being red said.** The
1196
+ first run of `.github/workflows/bridge-tests.yml` (2026-09-07, on main
1197
+ `9a42d17`) failed both jobs - ubuntu 613 of 625, windows 572 of 613 with a
1198
+ file cancelled - while macOS was 625 of 625, which is the whole reason none
1199
+ of it was known. Four of the failures were REAL WINDOWS BUGS in shipped
1200
+ code, and this is the list a stranger installing the package would have met:
1201
+ `glytch runner` and `glytch mcp` both died before reaching their script,
1202
+ because `glytch.mjs` handed `import()` an absolute path and Windows reads
1203
+ `D:\...` as the URL scheme `d:` (it takes a `file://` URL now);
1204
+ `PM_CLAUDE_BIN` pointing at a file that is not there was reported as a
1205
+ session that ran and crashed, because an unresolved PATH was handed to
1206
+ cmd.exe, which exits 1 with a message rather than raising ENOENT (a path is
1207
+ never shelled now, a bare name still is); every receipt and refusal printed a
1208
+ mixed-separator `.glytch\inbox\spawn/spawn-....json`, because the words were
1209
+ built from the `join`ed constant (the contract's spelling is used for words,
1210
+ the joined one for opening files); and a real detached dev server was left
1211
+ running, because `commandsAgree` could not see through the quotes
1212
+ `Win32_Process` puts round an executable and its spaced arguments. The other
1213
+ 36 were tests asserting the HOST rather than what they meant - a launchd
1214
+ plist suite inheriting a systemd runner, `"/"`-shaped literals compared
1215
+ against `join`ed paths, a `RegExp` built out of a backslashed temp directory,
1216
+ a rename awaited while the test still held the destination open (Windows
1217
+ refuses that, which is what session 3's retry loop is for), and a fixture
1218
+ that wrote its marker only from a signal handler on a platform with no
1219
+ signals. Two assertions were simply untrue and were replaced rather than
1220
+ relaxed: `lstat` cannot tell a junction from a directory symlink, and the
1221
+ proof of the junction is the file read through the link on a runner with no
1222
+ Developer Mode. `--test-timeout` went 60s to 180s because `node --test`
1223
+ applies it to the file entry as well: `runner.test.mjs` is 86 tests that each
1224
+ spawn a child and it does not fit in a minute on a Windows runner.
1225
+
1226
+ A SECOND ROUND, because a failing test hides what is behind it. With those
1227
+ out of the way the runner found four more shipped Windows bugs: `taskkill
1228
+ /pid <pid> /t` without `/f` asks by posting WM_CLOSE, so the polite pass
1229
+ ALWAYS fails for a console-less process and `killTree` threw that - which
1230
+ `stopRecordedServer` reads as "gone", so a verified detached dev server was
1231
+ never stopped there at all (the grace declined is now not a throw, and
1232
+ everything else taskkill can say still is); Windows has no signals, so
1233
+ `process.abort()` closes with code 134 and no signal, `classifyExit` called
1234
+ that a plain exit, and both halves of the crash-retry ruling were wrong on
1235
+ that platform (a 128+N table, gated on win32); the ACP Stop path sent
1236
+ `session/cancel` and killed, which gave the adapter a moment to act on it
1237
+ only by accident of asynchronous POSIX signal delivery, so on Windows it died
1238
+ with the cancel unread (it now waits for the turn to come back cancelled,
1239
+ bounded at 2s); and the repo primer handed to a session spelled its paths
1240
+ `src\index.js`, matching neither the imports in the files nor git nor a brief.
1241
+ No test was deleted; eight were added. Both jobs green at
1242
+ [34064042188](https://github.com/glytch-ai/prompting-machine/actions/runs/34064042188).
1243
+
1244
+ - **Two more Windows seams, from the same session.** A worktree's
1245
+ `node_modules` is linked with a `"junction"` on Windows rather than a
1246
+ directory symlink, which needs Developer Mode or admin there and otherwise
1247
+ throws EPERM - leaving the session unable to run the project's own tests,
1248
+ which is most of what a brief asks it to do. And `writeFileAtomic` retries a
1249
+ rename that fails EPERM or EBUSY, bounded at five tries over about 420ms:
1250
+ Windows file locking is mandatory, so a poller, an antivirus scanner or the
1251
+ search indexer holding the destination open loses a mark file, a registry
1252
+ write or an ops file outright. Neither code is ever produced on a Mac, so
1253
+ both paths are proven with injection.
1254
+
1255
+ - **`glytch init` in a second project is watched without a restart.** The
1256
+ daemon read `~/.glytch/folders.json` once at start, so the first test pass
1257
+ of brief 19 (2026-09-06) found a second project registered, reported green
1258
+ by `glytch doctor`, and watched by nothing until `launchctl kickstart -k
1259
+ ai.glytch.runner` was run by hand - the install-once promise held for the
1260
+ first folder only. It now watches the registry file itself (fs.watch on the
1261
+ file and on its directory, because an atomic write replaces the inode, plus
1262
+ a once-a-minute poll as the floor) and starts the new folder's sync and
1263
+ watcher live. The ruling: restarting the daemon from `init` was rejected,
1264
+ because a restart kills whatever session is running in another project. A
1265
+ folder that leaves the registry has its watcher stopped through its own
1266
+ AbortSignal, which drains the sessions it started; a registry that cannot
1267
+ be parsed is one log line and the last good list, never a stop and never an
1268
+ exit.
1269
+
1270
+ ## [0.3.0] - 2026-09-06
1271
+
1272
+ NOTE FOR WHOEVER RELEASES THIS. `package.json` already reads **0.3.0**: brief
1273
+ 19 set it by hand so the daemon's own version check has a real number to
1274
+ compare against npm. `scripts/release-pm-agent.mjs` bumps from what it finds,
1275
+ so cut this release as the manual path with the version left alone, or expect
1276
+ 0.4.0 and say so here.
1277
+
1278
+ ### Changed
1279
+
1280
+ - **The package is renamed to `glytch`**, the bare name, before its first
1281
+ publish - the morning's "add a bin now, rename after release" was reversed
1282
+ the same afternoon because the install line had not shipped anywhere yet,
1283
+ and the scoped `@glytch-ai/glytch` that reversal chose was itself replaced
1284
+ within the hour: the bare name is free on npm, it is what a tester types,
1285
+ and owning it is the stronger protection. The three bins (`glytch`,
1286
+ `pm-agent`, `pm-spawn-runner`) are unchanged, so every `.mcp.json` and
1287
+ opencode registration already written keeps working.
1288
+ - **A finished session exits.** The result envelope closes the child's stdin
1289
+ at once, so a `claude --input-format stream-json` session ends when its work
1290
+ does instead of holding an idle process open as a reply window until the
1291
+ 90-minute wall clock. The exit settles exactly as before - finished, not a
1292
+ timeout, OK receipt, sidecar written, worktree kept - and the wall clock is
1293
+ unchanged for a session mid-turn. A reply that lands after the session ended
1294
+ becomes a new spawn request that resumes it in the same worktree, so nothing
1295
+ typed is lost. The ruling, 2026-09-06.
1296
+
1297
+ ### Added
1298
+
1299
+ - **A third bin, `glytch`**, beside `pm-agent` and `pm-spawn-runner`. The two
1300
+ older ones are unchanged aliases, not deprecations, so nothing already
1301
+ wired into someone's `.mcp.json` has to move.
1302
+ `glytch init | doctor | forget | stop | runner ... | mcp | --version`;
1303
+ `runner` and `mcp` hand straight over to the existing scripts.
1304
+ - **`glytch init`**: registers the folder in `~/.glytch/folders.json`, writes
1305
+ `.mcp.json` by the same rules the app uses, runs `opencode mcp add board --
1306
+ glytch mcp` when opencode is on PATH, installs a macOS login agent so the
1307
+ spawn runner watches without a terminal open, prints which of `claude`,
1308
+ `opencode` and `codex` this machine has, and says to open the board. Safe to
1309
+ run twice: it puts back what is missing and touches nothing that is there.
1310
+ - **`glytch doctor`**: ten rows and an exit code - folder registered,
1311
+ `.mcp.json`, opencode registration, login agent and its captured PATH,
1312
+ daemon loaded, daemon beating, board mirror, agents found, Claude Code
1313
+ logged in, version against npm. Exits 0 when every row is green.
1314
+ - **A multi-root watch.** `glytch runner watch` with no `PM_BOARD_ROOT` reads
1315
+ the registry and watches every folder in it, each with its own runner id and
1316
+ its own `.runner.json` mark. A folder that has disappeared is one log line,
1317
+ never an exit. With `PM_BOARD_ROOT` set, behaviour is exactly as before.
1318
+ - **A cleanup sweep**, on daemon start and once a day: finished spawn requests
1319
+ whose receipt is over 14 days old, rotated logs past the keep count, and
1320
+ registry entries whose folder no longer exists. The mark file, the mirror,
1321
+ un-ingested ops and any unfinished request are never touched.
1322
+ - **Logs** at `~/.glytch/logs/runner.log`, rotated at 5 MB, three kept.
1323
+ - `GLYTCH_HOME` (moves the registry, the logs and the plist) and
1324
+ `GLYTCH_NO_LAUNCHCTL` (write the plist, load nothing).
1325
+
1326
+ - **The runner writes what the machine has into the mark file**, as an
1327
+ `agents` block: `{ claude: {found, version, loggedIn}, opencode: {found,
1328
+ version}, codex: {found} }`. Probed at daemon start and every five minutes,
1329
+ from a cache the liveness beat reads without waiting - a beat that blocks on
1330
+ three `--version` children is a beat the app calls stopped. A probe that
1331
+ fails is `found: false`, never a missing key and never a crash; a probe that
1332
+ has not answered yet writes no `agents` key at all, because "not asked yet"
1333
+ and "not installed" are different facts. Login is asked about Claude Code
1334
+ only, by the presence of `~/.claude/.credentials.json` - the file is never
1335
+ read.
1336
+
1337
+ ### Changed
1338
+
1339
+ - **The runner mark file gains three optional keys**, `version`,
1340
+ `updateAvailable` and `agents`. This is NOT a change to the folder contract:
1341
+ `runnerId`, `pid` and `at` are exactly what they were, so an app built
1342
+ before these keys reads a new runner unchanged, and a new app reads a runner
1343
+ that writes none of them. Both directions are pinned by
1344
+ `test/fixtures/runner-mark-cases.json`, read by a test on each side of the
1345
+ import ban - `test/runnermark.test.mjs` here and
1346
+ `src/folder/spawnRequest.contract.test.ts` in the app.
1347
+
1348
+ ### What the app does with it
1349
+
1350
+ Not this package's surface, and listed only because these keys exist FOR it -
1351
+ a reader wondering why a daemon probes anything should not have to go looking.
1352
+ The app (brief 19 stages 2 and 3, same day, not on main):
1353
+
1354
+ - draws a filled dot and the version for a found engine and a hollow one for a
1355
+ missing engine on the agent editor's engine row, the prompt card's Runs-on
1356
+ row and a new toolbar pill; a mark with no `agents` block draws no dots at
1357
+ all, because "nothing has looked" is not "you have none of these";
1358
+ - shows a setup card - `npm i -g glytch`, `cd <your project> && glytch init`,
1359
+ a Copy button - when a coding engine is picked with no runner
1360
+ watching, whose status row goes green by itself when the mark appears;
1361
+ - adds a guide step, "Connect your coding agent", done when the mark says a
1362
+ runner is watching.
1363
+ ## [0.2.0] - 2026-09-02
1364
+
1365
+ ### Changed
1366
+
1367
+ - `get_board` returns a **slim** digest by default, and takes filters:
1368
+ `tag`, `group`, `kind`, `since_days`, `match`, `limit`, plus `view`,
1369
+ `geometry` and `detail` to get the old shape back. Not a breaking change to
1370
+ the tool list, but it IS a change to what a call returns: geometry, the
1371
+ 150-char body preview under each title, group member lists and the `AGES`
1372
+ block are omitted unless asked for, and every reply ends with a line naming
1373
+ what it left out. Measured on the live board: 12,014 tokens to 4,788, and
1374
+ 1,401 with `tag:"next"`. The reason is a spend audit over 201 transcripts
1375
+ and 26,841 API requests on 2026-08-31 - one `get_board` cost about $0.50
1376
+ once you count it riding in context for the rest of the session, which was
1377
+ roughly 60% of everything the board bridge cost. Call frequency was already
1378
+ fine (107 of 137 board-using sessions called it exactly once), so the fix
1379
+ was to return less, not to call it less.
1380
+ - `report`'s tool description is shorter. It was 3,497 characters sitting in
1381
+ every request's system prompt, the largest of the twelve schemas. Rules are
1382
+ unchanged - the evidence rule is still on `flags`, where it is enforced -
1383
+ and the cut rationale is retired in a comment beside it, not deleted.
1384
+ - **A finished stream is finished.** A persistent streaming session emits its
1385
+ final result envelope and then holds its reply window open, so the runner's
1386
+ old process-exit reading killed done work at the wall clock and receipted it
1387
+ as ERROR: 26 of 28 ninety-minute kills had their envelope in the log, about
1388
+ $151 of finished work called a failure. The runner reads the envelope and
1389
+ settles the receipt as success; the wall clock stays exactly what it was for
1390
+ a child that never emits one (2adf56b).
1391
+ - **A session's verdict and cost land on the frame and in the receipt**, one
1392
+ line: "finished; $7.52, 41 turns, 38m, context 212k". Context fill is the
1393
+ LAST assistant step's input plus cache-read plus cache-creation tokens, never
1394
+ the cumulative total, and events carrying `parent_tool_use_id` are skipped so
1395
+ a subagent's small turn cannot overwrite the lead's number. The session
1396
+ sidecar gains `costUsd`, `numTurns` and `contextFill` (8ba824f, 168a485).
1397
+ - **A crash before any output is retried once**, inside the same wall clock.
1398
+ SIGSEGV, SIGABRT, SIGBUS, SIGILL and the Windows access-violation and
1399
+ stack-overflow codes count as crashes; SIGKILL never retries, and a crash
1400
+ AFTER any assistant event is not retried either, because the partial work
1401
+ would be duplicated. The receipt names the class (848fac8, 12f67c4).
1402
+ - **Worktree git mutations run one at a time per repo root.** Create, clean,
1403
+ reuse, merge and delete take an in-process lock; reads stay unlocked. Two
1404
+ sessions finishing together could both run checkout-main, pull and merge
1405
+ (565758e).
1406
+ - **The merge button keeps the branch and the worktree alive**, and does NOT
1407
+ push. Merging and deleting in one gesture had made the delete control
1408
+ meaningless (6579269, fa31197).
1409
+
1410
+ Corrected 2026-09-02: this entry said "and pushes the merged main to
1411
+ origin", naming the two commits that between them prove the opposite.
1412
+ `fa31197` (23:04) added `git push` after a merge; `6579269` (23:11) took it
1413
+ out again seven minutes later, replacing it with the comment that still
1414
+ stands - "No push: the user pushes when they are ready". The entry was
1415
+ written across both and kept the half that was reverted. `git log -S` over
1416
+ `bridge/lib/worktree.mjs` returns exactly those two commits and no others.
1417
+
1418
+ `git pull --ff-only` went the same way on 2026-09-02, for the same reason
1419
+ stated out loud: a merge is local work on local branches, and a network
1420
+ operation on his main branch that nobody asked for can move main under him,
1421
+ prompt for credentials with nobody watching, or hang on an absent remote
1422
+ while a button on his board spins.
1423
+ - **A prompt keeps its worktree and its session across runs**, so a re-run
1424
+ continues work rather than starting over (8eb136c).
1425
+ - **The propose vocabulary stops refusing nine ops the app has always
1426
+ accepted** (cffd809).
1427
+
1428
+ ### Added
1429
+
1430
+ - `hooks/board-budget.mjs`, a PreToolUse cap: it shapes the digest exactly as
1431
+ the pending `get_board` would, measures it, and refuses only when the real
1432
+ number is over `PM_BOARD_MAX_TOKENS` (default 6,000), naming the filters
1433
+ that would fit. A cap rather than a confirmation prompt because MCP cannot
1434
+ prompt, the permission dialog cannot show a price, and these sessions run
1435
+ unattended - a dialog would stall an overnight run at the first board read.
1436
+ Wired in `.claude/settings.json` behind an existence check; it exits 0 on
1437
+ every ambiguous case, so it can never break a session.
1438
+ - **Two MCP tools, `list_sessions` and `send_message`**, so a session can
1439
+ discover the other sessions running in this project folder and deliver a
1440
+ message to one. Discovery scans `inbox/spawn/` for requests without
1441
+ receipts; delivery writes a from-stamped reply file the target's runner
1442
+ already polls. Twelve tools now, where 0.1.0 shipped ten (85e9e06).
1443
+ - **`PM_SPAWN_STALL_MS`** (default 15 minutes): a stall watchdog on a
1444
+ streaming session, armed only when the MODEL should be producing output -
1445
+ a turn open and no tool still running. A hung tool remains the wall clock's
1446
+ job. The receipt says "stopped responding", which is neither a success nor a
1447
+ timeout (848fac8, 12f67c4).
1448
+ - **A repository primer on a fresh session's prompt**: the file tree plus
1449
+ exported symbols per source file, deterministic, no model call, cached
1450
+ keyed to the HEAD sha and capped at ~10KB. A fresh session was spending its
1451
+ first turns rediscovering the tree (40061ad).
1452
+ - **An attempts log for a re-run.** A prompt that failed before is given
1453
+ bounded summaries of what earlier attempts tried and why they failed, built
1454
+ from receipts and log tails on disk with no model call, and the section says
1455
+ so when something was dropped (709a3c1).
1456
+ - **Images reach a coding session**, and a reply file can carry them
1457
+ (547f27d).
1458
+
1459
+ ### Fixed
1460
+
1461
+ - The request scan skips the runner's own `*.session.json` sidecars and
1462
+ dotfiles instead of refusing them as malformed requests - 47 refusal
1463
+ receipts against its own bookkeeping, each one counting against the daily
1464
+ spend bound (2adf56b).
1465
+ - A session running in a worktree writes to the real board rather than into
1466
+ its own copy of the folder (2caedb1).
1467
+ - A headless session can no longer park itself in plan mode, where nothing
1468
+ would ever answer it (915b9d1).
1469
+ - A dead pipe, a duplicated reply, and an orphan reaper that killed processes
1470
+ merely mentioning a worktree path (49093ab).
1471
+ ## [0.1.0] - 2026-08-28
1472
+
1473
+ First published version. Everything below already existed; this is the point
1474
+ it became installable by someone who does not have the repo.
1475
+
1476
+ ### Added
1477
+
1478
+ - Ten MCP tools over stdio: `get_board`, `get_node`, `propose`, `ask_user`,
1479
+ `check_answer`, `report`, `open_session`, `close_session`,
1480
+ `collect_session`, `set_brief_status`.
1481
+ - `pm-agent` and `pm-spawn-runner` on PATH.
1482
+ - MIT licence, so the package can actually be used.
1483
+ - An install written for someone who has never seen the repo.