acrylblends 1.0.9 → 1.1.0

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 (47) hide show
  1. package/dist/index.d.ts +15 -1
  2. package/dist/index.js +13 -7
  3. package/dist/index.js.map +1 -1
  4. package/docs/framework/api/summary.md +1 -1
  5. package/docs/framework/guides/bring-your-own-tool.md +2 -0
  6. package/docs/framework/guides/production.md +20 -0
  7. package/docs/framework/workshop/README.md +177 -274
  8. package/docs/framework/workshop/facilitator.md +107 -84
  9. package/lib/{audit-Dw-siYTp.d.ts → audit-BFpULBCj.d.ts} +15 -3
  10. package/lib/cordis/door.d.ts +3 -1
  11. package/lib/cordis/door.js +11 -1
  12. package/lib/cordis/state.d.ts +3 -2
  13. package/lib/cordis/state.js +370 -6
  14. package/lib/db/conformance.d.ts +6 -2
  15. package/lib/db/conformance.js +14 -2
  16. package/lib/door/host.d.ts +1 -1
  17. package/lib/door/host.js +1 -1
  18. package/lib/door/page.d.ts +10 -0
  19. package/lib/door/page.js +29 -7
  20. package/lib/kv/conformance.d.ts +6 -2
  21. package/lib/kv/conformance.js +14 -2
  22. package/lib/{snapshot-text-CBi3EMnj.js → snapshot-text-BwM6T2nG.js} +37 -5
  23. package/lib/vitest-binding-CctNu3jT.d.ts +15 -0
  24. package/lib/vitest-binding-CrH7rKg2.js +17 -0
  25. package/package.json +1 -5
  26. package/plugins/acryl-door/src/host/channel.ts +43 -5
  27. package/plugins/acryl-door/src/host/door.ts +5 -0
  28. package/plugins/acryl-door/src/host/page-routes.ts +1 -1
  29. package/plugins/acryl-door/src/host/page-socket.ts +1 -1
  30. package/plugins/acryl-door/src/page/channel.ts +8 -0
  31. package/plugins/acryl-door/src/page/driver/driver.ts +17 -3
  32. package/plugins/acryl-door/src/page/index.ts +6 -1
  33. package/plugins/acryl-door/src/page/indicator.ts +14 -6
  34. package/plugins/acryl-state/plugin.json +4 -3
  35. package/plugins/acryl-state/src/client-source.ts +244 -0
  36. package/plugins/acryl-state/src/service.ts +33 -6
  37. package/plugins/acryl-state/src/socket.ts +96 -0
  38. package/plugins/agent-panel/client/main.ts +105 -55
  39. package/plugins/agent-panel/index.ts +42 -5
  40. package/plugins/desktop/main.ts +5 -1
  41. package/plugins/instance/index.ts +6 -3
  42. package/templates/agent/AGENTS.md +3 -1
  43. package/templates/agent/src/routes.ts +38 -5
  44. package/templates/agent/test/app.test.ts +38 -2
  45. package/templates/chat/src/main.ts +1 -6
  46. package/templates/nodejs/src/main.ts +1 -6
  47. package/templates/notes/src/main.ts +1 -6
@@ -1,108 +1,131 @@
1
- # Facilitator notes: the 90-minute ACRYL Blends workshop
1
+ # Facilitator notes: an app that builds and drives itself
2
2
 
3
- Read the attendee handout first (`docs/framework/workshop/README.md`); this page is the room around it: what to put on each machine, what actually breaks, and
4
- where to cut when the clock runs. Every timing and every failure below was measured on the published `acrylblends` 1.0.0 during the readiness walk
5
- (`docs/plans/workshop-readiness.md`).
3
+ Read the handout first (`docs/framework/workshop/README.md`); this page is the room around it. The workshop changed shape on 2026-10-29: the agent living in
4
+ the app both builds its interface and drives it (clicks, sidebars, dialogs, settings), on the web and in a desktop window. The data layer and the template
5
+ tour are asides now, and `/hello` is only the introduction to the framework core. The handout is written for **acrylblends 1.0.10 or newer**.
6
6
 
7
7
  ## Before the room: put this on every machine
8
8
 
9
9
  | What | Why | How |
10
10
  | --- | --- | --- |
11
- | Node.js 22.19 or newer (24 LTS or 26 tested), npm 11 | the app runs TypeScript directly, no build step | `node -v`; if missing, the LTS from nodejs.org or `brew install node` (Node 26 today, which is tested) |
12
- | git | the registry block clones the registry | `git --version` |
13
- | A warm npm cache | the first `npm create` downloads the initializer, `npm install` downloads Electron | create one app and add the desktop shell once per machine (below) |
14
- | A coding agent, logged in | step 7 drives Codex or Claude Code through the panel | `codex login status` or run `claude` once |
15
- | `~/acryl-workshop/prepared/` | the no-network fallback and the reset | copy the two prepared apps (below) |
16
- | A GitHub account with `gh auth login` | step 10 forks the workshop registry and opens a pull request | `gh auth status` |
17
- | A device with `git` and a text editor | the edits in steps 2, 4 and 8 are small but real | `git --version` |
11
+ | The preflight passing | it checks Node, npm, git, an agent with its login or key, a free port, and on Windows PowerShell and long paths | `npx acrylblends@latest preflight` ends with `Ready for the workshop.` |
12
+ | Node.js 22.19 or newer (24 and 26 tested) | the app runs TypeScript directly | `node -v`; the app's own `scripts/check-node.cjs` refuses an older Node with a clear line |
13
+ | git, and a GitHub account with `gh auth login` | the last block forks the workshop registry and opens a pull request | `git --version`, `gh auth status` |
14
+ | One coding agent, logged in | the panel drives it; `codex`, `claude` and `pi` all worked | `codex login` once (and a model the account supports), or run `claude` once, or pi with `DEEPSEEK_API_KEY` set in the terminal that starts the app |
15
+ | A prepared app with Electron already downloaded | the desktop block is 15 minutes and the download is the slowest part | run the handout's blocks 1 and 4 once per machine, then keep the folder as `~/acryl-workshop/prepared/` |
16
+ | A warm npm cache | the first `npm create` and the Electron download dominate the first ten minutes | the prepared app does it |
18
17
 
19
- Warm-up, once per machine (this is also the exact smoke test that the room works):
18
+ **The agent config that decides the day.** The panel's agent is configured to act rather than describe. Check on every machine that a plain request
19
+ produces files, not a plan: ask the prepared app's panel "say hello in a card on the page" and watch the panel's `Working...` line and the first file
20
+ appear. If it only describes, the model is waiting for permission; the handout's sentence is "do it now".
20
21
 
21
- ```sh
22
- mkdir -p ~/acryl-workshop && cd ~/acryl-workshop
23
- npm create acrylblends@latest warmup notes
24
- cd warmup && npm install && npm test
25
- npx acrylblends add desktop && npm install
26
- cd ~/acryl-workshop && cp -R warmup prepared-notes && rm -rf warmup
27
- ```
22
+ **Agent-specific checks.** Codex answering `model_not_found` means the account does not have the default model: pick one it supports (`/model` in codex,
23
+ or `model = "..."` in `~/.codex/config.toml`). pi with DeepSeek needs `DEEPSEEK_API_KEY` in the environment of the terminal that runs `npm run dev`
24
+ (the panel starts pi from the app); the preflight reports `PASS agent pi (DeepSeek key found)` when it is there. The package is
25
+ `@earendil-works/pi-coding-agent` (1.4.0 on npm on 2026-10-30), installed with `npm i -g @earendil-works/pi-coding-agent`.
28
26
 
29
- That leaves `prepared-notes` with everything installed, including Electron.
27
+ ## Timings, from the 1.0.10 walks (macOS and Windows, pi on DeepSeek)
30
28
 
31
- ## Timings, checkpoints, and what to make optional
32
-
33
- The handout's own timings, as measured on the published 1.0.2 (macOS, cold npm cache in a throwaway home):
34
-
35
- | Minutes | Step | Measured | If late |
29
+ | Minutes | Block | Measured | If late |
36
30
  | --- | --- | --- | --- |
37
- | 0-10 | installs and checks | seconds each | never cut |
38
- | 10-15 | create and run the blank app | create 9 s cold (2 s warm), install 2 s, boot 2 s | never cut |
39
- | 15-22 | change one route | reload is instant | never cut |
40
- | 22-30 | tests and the typecheck | test 0.5-2 s, check 2.5-4 s | keep; cut the "write a test" exercise |
41
- | 30-38 | data: JSON then SQLite (**optional**) | two adds plus edits, ~8 min | **cut first** |
42
- | 38-45 | the desktop window | add 2 s, install 3 s, first Electron boot ~20 s | pre-warm Electron on every machine or demo it |
43
- | 45-53 | pull a starter | create 4 s, install 3 s, runs at once | keep; it is the fastest win in the room |
44
- | 53-60 | create from a template | create + install + add desktop 4 s | drop the second app |
45
- | 60-75 | the agent builds a feature | set-up 4 s; codex wrote a page and tests in about 7 minutes | never cut; demo it if the room is behind |
46
- | 75-82 | upgrade | seconds: a fresh app answers "everything is up to date" | keep; it is a 2-minute talk |
47
- | 82-90 | publish and the hero moment | the registry steps are seconds; the PR is talk | always keep at least the hero talk |
48
-
49
- Checkpoints in the handout, and what to look for as you walk the room: **15 minutes** every screen shows `{"app":"my-hero","ok":true}`; **45 minutes**
50
- every screen has the app in a desktop window; **75 minutes** every screen has `/board` open with the agent's page. A machine behind at a checkpoint is
51
- what the optional column is for, and the handout names the same choices so nobody has to guess.
52
-
53
- ## What breaks in the room, and what to say
54
-
55
- These are the defects found by the walk. They are known; say so rather than letting people think they broke something.
56
-
57
- - **An agent with its own settings acts differently.** Skills, hooks and instructions in a person's own Codex or Claude configuration travel into the panel's run: on the owner's laptop Codex stopped to "present a design for approval" instead of building. Word every task for the agent as "do it now, without asking for approval", and for a demo machine start the agent from a clean configuration (a fresh `CODEX_HOME`, no personal skills).
58
- - **The data step is the slow one.** Wiring `kv` then `db` by hand is about ten minutes of edits for a newcomer (two rows, two registry lines, two
59
- type imports, the migrations folder). It is the block the handout names optional, and the one to help with first.
60
- - **A relative migrations path in `blend.yaml` fails.** `migrations: migrations` in the row stops the app with "this app has no file base URL to
61
- resolve it from". The folder is named in `src/app.ts`'s compose options instead; the handout teaches that form and the stuck line names the error.
62
- - **`pi` shows up only when the app can see its login.** 1.0.2 offers pi as a chat driver when `DEEPSEEK_API_KEY` is exported or `~/.pi/agent/auth.json`
63
- holds a deepseek entry, read from the HOME the app runs in. On a normal attendee machine it is there; in a run with a moved HOME it is not (and pi
64
- would fail on its first message anyway). `pty:pi` needs only the program.
65
- - **The agent run takes minutes, not seconds.** Codex wrote the page and its tests in about seven; tell the room to start it and keep talking.
66
- - **Publishing.** The workshop registry is `github.com/acryldev/workshop-2026-11-20`, public and open. Attendees fork it, run
67
- `node scripts/make-entry.mjs ../agent-app <login>.agent-app --description "..."`, push and open a pull request; a check merges it, usually well under
68
- two minutes, and a refusal comes as a comment. Taking an app down is a pull request that removes its folder. Pulling is
69
- `--from <id> --registry github:acryldev/workshop-2026-11-20` (rehearsed on 1.0.2: create 12 s, install 3 s, runs at once).
70
- - **Browser-only attendees (TODO(M9)).** The handout's hosted-workspace stub is empty until M9 is live; until then a browser-only attendee can follow
71
- along but not create an app.
72
- - **node-pty's install script (C5).** `npm install` warns that npm 11 blocked `node-pty`'s script. The terminal drivers still list; the workshop does
73
- not open one.
31
+ | 0-10 | preflight, setup and `/hello` | preflight 12 s; create, install, dev, the route edit, `npm test` (14 tests) and `npm run check` 14 s of commands | never cut |
32
+ | 10-40 | the agent builds the interface | 5:51 for pi to build, install and check the board (an earlier codex run: about 2.5 minutes) | if late, keep the board minimal (one column) |
33
+ | 40-60 | the agent drives the app | 1:41 for the tick-and-filter prompt (six door calls, most of the time the model thinking), 2:07 for the New-card prompt | the drive is the point; cut the second "check its own work" prompt |
34
+ | 60-75 | the same app in a window | add + install 5 s; first `npm run desktop` 10 s including the Electron download on a fast line; the drive in the window 22 s; the windows, menu and settings prompt 1:11 | pre-warm Electron or demo it |
35
+ | 75-90 | deploy and publish | production start in seconds; the registry check merges in about 2 minutes | keep the publish; the deploy talk can be 3 minutes |
36
+
37
+ The Windows walk on 1.0.10 (2026-10-30, Windows 10, Node 22, pi on DeepSeek) matched: preflight 13 s; `npm create` 8 s and install 4 s; pi built the
38
+ board from the panel in 5:56; the agent opened Settings through `File > Settings...`, set the appearance and saw `Saved: Dark`, and window 1 showed it
39
+ after a reload; with two windows open during a live run, `look` answered in 317-353 ms in each and the panel's Stop ended the run in 3 s; production served
40
+ the board read-only with the agent routes gone.
41
+
42
+ Checkpoints in the handout: 10 minutes a route answers; 40 minutes the agent's board is on screen; 60 minutes the agent has clicked through it; 75 minutes
43
+ the window is open. Walk the room at those minutes and help the machine that is behind, rather than the one that is ahead.
44
+
45
+ ## What to say in the room
46
+
47
+ - **Close the browser tab before block 4.** While a browser tab of the app is open, a door action without `--window` can go to that tab: in the 1.0.10
48
+ walk the agent, asked from the window's panel, moved the hidden browser tab and reported success while the window stayed on `/`. With the tab closed
49
+ the same prompt drove the window in 22 s. The handout says so at the top of block 4; it stays until the door aims at the window the agent was asked
50
+ from (reported to Blends).
51
+ - **The Settings item.** It is in the application menu on macOS (`my-app > Settings...`, `Cmd+,`) and the first item of the File menu on Windows and Linux
52
+ (`Ctrl+,`). An app gets it because it serves `/settings`; the template does. In the walk, pi found it in the app menu by itself.
53
+ - **Appearance.** The template's `/settings` saves light, dark or "match the system" for the app's own pages. A window that is already open shows the
54
+ change after a reload, and a page the agent built follows the system theme unless asked otherwise. On a dark-mode machine, demo with Light.
55
+ - **The driving bar.** `Agent is driving: clicking` with a Stop button shows at the bottom of the page only while each action runs, a moment each; the
56
+ panel's `Working... m:ss` line and its Stop button are what people see during a run.
57
+ - **Every agent builds a different board.** pi's board needed a title typed before "New card" added a card, and kept its sidebar always open when the
58
+ prompt did not ask for a toggle. The handout's block 2 prompt now asks for a button that opens and closes the sidebar, so block 3 has something to open.
59
+ - **Production listens where it is told.** The app reads `ACRYLBLENDS_HOST` and `ACRYLBLENDS_PORT`, not `PORT`; without them it listens on `127.0.0.1` at a
60
+ port chosen from its folder. A server needs `ACRYLBLENDS_HOST=0.0.0.0 ACRYLBLENDS_PORT=8080 NODE_ENV=production npm start` (verified: it answered on the
61
+ LAN). If Blends ships `PORT`, the handout switches to it.
62
+ - **Windows.** Windows PowerShell 5.1 has no `&&` (two lines, or `;`); PowerShell sets variables with `$env:NAME="value"`; cmd.exe's `Ctrl-C` asks
63
+ `Terminate batch job (Y/N)?` (answer `Y`); cmd.exe does not expand `~` (use `%USERPROFILE%`). Node 22 prints only `Restarting 'src/main.ts'` on a save;
64
+ Node 24 and 26 print `Change detected …` before it.
65
+ - **F (the door through a DeepSeek model with no login).** pi with a DeepSeek key works today; do not promise more than that.
66
+
67
+ ## What the 1.0.10 walk found, block by block
68
+
69
+ The final macOS walk on 1.0.10 (2026-10-30), verbatim from the handout as it stood before this revision, with pi on DeepSeek. Every block passed; these
70
+ are the places where the handout text differed from what the walk saw, and each one is now handled in the handout.
71
+
72
+ 1. Block 1: npm test now has 14 tests (the guide gives no number). "Change detected … Restarting" matches on Node 24; on Node 22 only "Restarting"
73
+ prints (seen on z370n).
74
+ 2. Block 2: "keep the app's own page (/) open in a second tab, because the panel lives there" is outdated: the panel is on every page now and followed the
75
+ agent to /board. The page is dark on a dark-mode Mac (screenshots s1-first-page/s3-board look different); the panel stays light on the dark page.
76
+ 3. Block 3: the guide's block 2 prompt asks for no sidebar toggle, so "open the sidebar" has nothing to open (pi says so and goes on). "an indicator in
77
+ the corner says the agent is driving, with a stop button": it shows only while each single door call runs (a fraction of a second), so an attendee
78
+ will hardly see it; it never showed in 33 frames taken every 3 s. "Click New card twice" added no cards: pi's board needs a title first; the prompt
79
+ only asks whether the counts agree. "seven door calls in about twenty seconds" was 6 calls in 1:41 with pi.
80
+ 4. Block 4: the guide says "use the File menu". On macOS Settings is in the app menu (my-app>Settings..., Cmd+,); pi found it there itself. Elsewhere it
81
+ is the first File item. "switch the appearance to dark" shows no change on a dark-mode Mac, and the agent-built /board ignores the app theme (its CSS
82
+ follows the system); the change shows on the template's / and /settings, and in other open windows only after a reload (switching to Light in window
83
+ 3, window 1 on / changed only after act reload). The TODO(1.0.4-C) paragraph is outdated: the window, menu and settings control has shipped.
84
+ Important: if the browser tab from blocks 2-3 is still open, the agent's door calls from the window's panel go to that browser tab, not the window. My
85
+ first in-window drive moved the hidden tab and the window stayed on /. With the tab closed it worked.
86
+ 5. Block 5: "That is the same app on any Node host … one port" names no port: the app binds 127.0.0.1 on a port derived from its folder path, ignores PORT
87
+ (PORT=3299 still gave 3991), and needs ACRYLBLENDS_PORT and ACRYLBLENDS_HOST (only the app's README names them; production.md does not).
74
88
 
75
89
  ## The no-network fallback
76
90
 
77
- If the venue has no reliable network:
91
+ 1. Prepared apps on every machine cover blocks 1-3; the agent's model still needs the network, so block 2 and 3 need a connection or a demo.
92
+ 2. Block 5's registry needs GitHub; if the venue is offline, publish from your machine on a hotspot at the end, or show the recording.
93
+ 3. Keep one phone hotspot for the room.
78
94
 
79
- 1. Every machine gets `prepared-notes` and a prepared agent app from the sheet above, so steps 1-6 skip creation and installs.
80
- 2. Step 8 uses the cloned registry and the installed `blends-core`; no network needed.
81
- 3. Step 7 needs the agent's own model, so it cannot run offline. Demo it from your machine on a tethered connection, or record it (a 4-minute
82
- screen recording of the codex run is enough).
83
- 4. Keep one machine on a phone hotspot for `npm create` failures.
95
+ ## Demo script (five minutes, if the room is behind)
84
96
 
85
- ## Demo script for the agent block (5 minutes, if you demo instead of hands-on)
97
+ 1. Prepared app, panel open: ask for a card with a count. Narrate the panel's `Working...` line and the page as it reloads.
98
+ 2. Ask: "open the sidebar, tick one card, change the filter to done, tell me the counts." Point at the page changing and at the driving bar as each action
99
+ runs.
100
+ 3. Show `.acryl/audit/` afterwards: every action is a line.
101
+ 4. Close the browser tab, switch to the desktop window and repeat one click there; then open Settings from the menu.
102
+ 5. Show the registry listing with an app already published, and the four commands that put it there.
86
103
 
87
- 1. On the prepared agent app: `npm run dev`, open the page, click the panel button.
88
- 2. Ask: "Add a route GET /agent-built to src/routes.ts that returns {\"built\": true}, then run the tests and tell me what happened."
89
- 3. Narrate while it runs: the agent reads `AGENTS.md`, reads `docs/framework`, edits `src/routes.ts`, runs the tests; the terminal prints
90
- `Change detected … Restarting`; reload `/agent-built` and show `{"built":true}`.
91
- 4. Point at the panel's stop button and the audit line the panel writes under `.acrylblends/audit/`.
104
+ ## Screenshots
92
105
 
93
- ## Resetting between sessions
106
+ Taken on 1.0.10 on macOS in dark mode (the pages follow the system theme), 1280 wide, no personal data, tokens or real home paths:
94
107
 
95
- Everything a person makes lives inside their app folder and `~/acryl-workshop`. To reset a machine:
108
+ | File | Shows |
109
+ | --- | --- |
110
+ | `s1-first-page.webp` | the first page with the panel open (block 1) |
111
+ | `s2-panel-building.webp` | the panel with the build request and `Working... 0:04` (block 2) |
112
+ | `s3-board.webp` | a board an agent built (block 2; from an earlier run, kept because it shows the result best) |
113
+ | `s4-agent-drives-window.webp` | the agent driving the board in the desktop window: the Done filter it just set (block 4) |
114
+ | `s5-two-windows.webp` | two desktop windows: the app's page in the dark appearance, and the Settings window showing `Saved: Dark` (block 4) |
115
+ | `s6-registry.webp` | the registry listing (block 5) |
96
116
 
97
- ```sh
98
- rm -rf ~/acryl-workshop/[a-z]*-app ~/acryl-workshop/registry
99
- cp -R ~/acryl-workshop/prepared/* ~/acryl-workshop/ # if you keep a prepared/ folder
100
- ```
117
+ S4 and S5 are page captures of the desktop windows (through the window's DevTools), without the window frame; a capture with the frame needs the
118
+ screen-recording permission on the machine that takes it.
119
+
120
+ ## Resetting between sessions
101
121
 
102
- Nothing is installed globally except the agent and Bun; neither needs cleaning between sessions.
122
+ Everything a person makes lives inside their app folder and `~/acryl-workshop`. To reset a machine, delete the app folders and copy the prepared one back;
123
+ nothing is installed globally except the agent, `gh` and Node.
103
124
 
104
125
  ## What is still open
105
126
 
106
- - Starter pull (B1), the panel's rows (C1) and the upgrade command in the docs (B2) are with the framework team; when they land, the handout's
107
- TODO lines become normal steps and this page's "what breaks" list shrinks.
108
- - The Windows and Linux runs of the attendee handout are being replayed separately; add what differs to the handout's OS column rather than here.
127
+ - Close-the-tab advice in block 4 until the door aims at the window the agent was asked from.
128
+ - The appearance reaching open windows live, and an agent-built page following the app's appearance: reported to Blends; the handout says "reload".
129
+ - `PORT` for production: if Blends ships it, the handout's server line switches from `ACRYLBLENDS_PORT` to `PORT`.
130
+ - The handout's hosted-workspace section (browser-only attendees) stays TODO(M9) until that workspace is live; the one-click WebBoxes.ai deploy lives
131
+ there too.
@@ -4,6 +4,8 @@ import { _ as UiRequest, n as AuditEntry, r as ChannelToHost, v as UiResult } fr
4
4
  /** The slice of a page's stream the channel needs, so tests can supply their own. */
5
5
  interface PageStream {
6
6
  send(data: string): void;
7
+ /** Tell the page something that is not a call (`run`: `1` while a run asked for from its panel goes, `0` when it ends). A stream that cannot (a test's) leaves it out. */
8
+ notify?(event: string, data: string): void;
7
9
  close(): void;
8
10
  }
9
11
  interface ChannelOptions {
@@ -16,14 +18,16 @@ interface ChannelOptions {
16
18
  readonly attachWaitMs?: number;
17
19
  }
18
20
  /**
19
- * The pages and who drives which. One page answers a call, chosen the same way every time: the page the Host just sent somewhere (it comes back as a new page, `prefer`), else the
20
- * one in front (it reports `focused`), else the most recently active; a page that did not acknowledge a call in time goes to the end of the line. An explicit desktop `window` picks its page.
21
+ * The pages and who drives which. One page answers a call, chosen the same way every time: the page whose panel asked for the run that is making the call (`driveFor`), else the page the
22
+ * Host just sent somewhere (it comes back as a new page, `prefer`), else the one in front (it reports `focused`), else the most recently active; a page that did not acknowledge a call in time goes to the end of the line. An explicit desktop `window` picks its page.
21
23
  */
22
24
  declare class UiChannel {
23
25
  private readonly ackMs;
24
26
  private readonly attachWaitMs;
25
27
  /** The page the Host sent to a new place and that came back: it keeps being driven until the person focuses another page. */
26
28
  private preferred;
29
+ /** The pages whose panels have a run going, the latest last: a call with no `window` goes to the latest of them that is still connected. */
30
+ private readonly askers;
27
31
  constructor(options?: ChannelOptions);
28
32
  private readonly connections;
29
33
  private readonly pending;
@@ -47,6 +51,14 @@ declare class UiChannel {
47
51
  focused: boolean;
48
52
  shellWindow?: number;
49
53
  }[];
54
+ /**
55
+ * A run asked for from this page's panel is going: its calls (which name no window) are for this page, whatever else is open and whichever was driven last, until the run ends.
56
+ * An explicit desktop `window` still picks its own page.
57
+ * @returns the function to call when the run ends.
58
+ */
59
+ driveFor(windowId: string): () => void;
60
+ private tellPage;
61
+ private tell;
50
62
  /** Drive this page from now on (it is the one the Host just sent to a new place), until the person focuses another. */
51
63
  prefer(windowId: string): void;
52
64
  /**
@@ -78,7 +90,7 @@ declare class UiChannel {
78
90
  reloadAll(timeoutMs?: number): Promise<number>;
79
91
  /** Close every page's stream and settle every pending call as `unloaded`. */
80
92
  close(): void;
81
- /** The order pages are driven in: the one the Host sent somewhere, suspect ones last, the one in front, then the most recently active. */
93
+ /** The order pages are driven in: the one whose panel asked, the one the Host sent somewhere, suspect ones last, the one in front, then the most recently active. */
82
94
  private ordered;
83
95
  /** Forget a connection; its calls in flight settle as `disconnected` at once rather than waiting for their timeout. */
84
96
  private drop;
@@ -1,5 +1,5 @@
1
1
  import { f as UiActionResult, y as UiSnapshot } from "../protocol-a2aqrp2X.js";
2
- import { i as UiChannel, r as PageStream, t as AuditLog } from "../audit-Dw-siYTp.js";
2
+ import { i as UiChannel, r as PageStream, t as AuditLog } from "../audit-BFpULBCj.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
 
@@ -139,6 +139,8 @@ interface VerbAnswer {
139
139
  declare class Door {
140
140
  private readonly deps;
141
141
  constructor(deps: DoorDeps);
142
+ /** A run asked for from the panel of this page is going: its verbs that name no window are for that page. @returns the function to call when the run ends. */
143
+ driveFor(windowId: string): () => void;
142
144
  /** Run one verb. @throws DoorError or UiControlError. */
143
145
  run(verb: string, args: unknown, signal: AbortSignal): Promise<VerbAnswer>;
144
146
  brief(): VerbAnswer;
@@ -4,7 +4,7 @@ import { r as loopbackAuthority } from "../loopback-only-BbasoF7R.js";
4
4
  import "../helpers-BEjcdbgw.js";
5
5
  import { n as clientModules } from "../client-modules-drj4mjhj.js";
6
6
  import { a as MAX_WAIT_MS, d as parseChannelToHost, l as UiControlError, p as parseUiRequest, r as MAX_SNAPSHOT_NODES, u as WINDOW_ID } from "../protocol-CQ_if7Dg.js";
7
- import { a as INVALID_JSON, c as operatorOnly, d as AuditLog, f as auditPath, i as DOOR_PREFIX, l as privateApi, o as error, p as UiChannel, r as pageChannelRoutes, s as jsonBody, t as renderSnapshot, u as readJson } from "../snapshot-text-CBi3EMnj.js";
7
+ import { a as INVALID_JSON, c as operatorOnly, d as AuditLog, f as auditPath, i as DOOR_PREFIX, l as privateApi, o as error, p as UiChannel, r as pageChannelRoutes, s as jsonBody, t as renderSnapshot, u as readJson } from "../snapshot-text-BwM6T2nG.js";
8
8
  import "../src-BTBm4OPD.js";
9
9
  import "../middleware-BfG_fp8P.js";
10
10
  import { isAbsolute, join, relative, resolve } from "node:path";
@@ -620,6 +620,10 @@ var Door = class {
620
620
  constructor(deps) {
621
621
  this.deps = deps;
622
622
  }
623
+ /** A run asked for from the panel of this page is going: its verbs that name no window are for that page. @returns the function to call when the run ends. */
624
+ driveFor(windowId) {
625
+ return this.deps.channel.driveFor(windowId);
626
+ }
623
627
  /** Run one verb. @throws DoorError or UiControlError. */
624
628
  async run(verb, args, signal) {
625
629
  const input = typeof args === "object" && args !== null && !Array.isArray(args) ? args : {};
@@ -1186,6 +1190,12 @@ function pageSocketRoutes(channel, options) {
1186
1190
  d: data
1187
1191
  });
1188
1192
  },
1193
+ notify: (event, data) => {
1194
+ write({
1195
+ e: event,
1196
+ d: data
1197
+ });
1198
+ },
1189
1199
  close: () => {
1190
1200
  ws.close(1e3, "replaced");
1191
1201
  }
@@ -24,9 +24,10 @@ declare class State extends Service {
24
24
  keys(): string[];
25
25
  /**
26
26
  * The current value of `key` as a server-sent event stream: the value now, then one event per change, until the client cancels or the router
27
- * is disposed (both abort the stream). A comment line goes out every 15 seconds so proxies and browsers keep an idle stream open.
27
+ * is disposed (both abort the stream). A comment line goes out every 15 seconds so proxies and browsers keep an idle stream open. A string value travels as it is; with `json`
28
+ * (the `?json=1` of the browser client) every value is JSON, so `"12"` and `12` can be told apart.
28
29
  */
29
- stream(key: string, c: Context$1): Response | undefined;
30
+ stream(key: string, c: Context$1, json?: boolean): Response | undefined;
30
31
  }
31
32
  declare function apply(ctx: Context): void;
32
33
  //#endregion