viewable-one 0.0.0-stage → 0.12.1

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