acrylblends 1.0.10 → 1.2.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 (252) hide show
  1. package/README.md +9 -0
  2. package/dist/index.d.ts +60 -2
  3. package/dist/index.js +1992 -33
  4. package/dist/index.js.map +1 -1
  5. package/docs/framework/agents/index.md +1 -1
  6. package/docs/framework/api/summary.md +1 -1
  7. package/docs/framework/getting-started/desktop.md +23 -4
  8. package/docs/framework/guides/create-acrylblends.md +1 -1
  9. package/docs/framework/guides/production.md +20 -0
  10. package/docs/framework/workshop/README.md +174 -285
  11. package/docs/framework/workshop/facilitator.md +81 -90
  12. package/legacy/README.md +9 -0
  13. package/legacy/agent/157ce3db1112/src/app.ts +98 -0
  14. package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
  15. package/legacy/agent/24c681a00fca/src/app.ts +160 -0
  16. package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
  17. package/legacy/agent/2e790da977b2/src/app.ts +98 -0
  18. package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
  19. package/legacy/agent/6307045545dc/src/app.ts +168 -0
  20. package/legacy/agent/6307045545dc/src/routes.ts +83 -0
  21. package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
  22. package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
  23. package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
  24. package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
  25. package/legacy/agent/74ece148269c/src/app.ts +168 -0
  26. package/legacy/agent/74ece148269c/src/routes.ts +93 -0
  27. package/legacy/agent/855359304707/src/app.ts +168 -0
  28. package/legacy/agent/855359304707/src/routes.ts +126 -0
  29. package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
  30. package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
  31. package/legacy/agent/c2f30958043b/src/app.ts +98 -0
  32. package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
  33. package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
  34. package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
  35. package/legacy/blank/aea589283445/src/routes.ts +16 -0
  36. package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
  37. package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
  38. package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
  39. package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
  40. package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
  41. package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
  42. package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
  43. package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
  44. package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
  45. package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
  46. package/legacy/layouts.json +524 -0
  47. package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
  48. package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
  49. package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
  50. package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
  51. package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
  52. package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
  53. package/legacy/notes/4edc36949c15/src/app.ts +107 -0
  54. package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
  55. package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
  56. package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
  57. package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
  58. package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
  59. package/legacy/todo/02e613b3625c/src/app.ts +71 -0
  60. package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
  61. package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
  62. package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
  63. package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
  64. package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
  65. package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
  66. package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
  67. package/legacy/todo/8e64211312f3/src/app.ts +71 -0
  68. package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
  69. package/legacy/todo/b79078a87565/src/app.ts +71 -0
  70. package/legacy/todo/b79078a87565/src/todo.ts +125 -0
  71. package/lib/{audit-Dw-siYTp.d.ts → audit-Bok_UNCn.d.ts} +41 -6
  72. package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
  73. package/lib/cordis/door.d.ts +44 -7
  74. package/lib/cordis/door.js +214 -60
  75. package/lib/cordis/inspect.js +1 -1
  76. package/lib/cordis/node-server.d.ts +101 -3
  77. package/lib/cordis/node-server.js +3 -3
  78. package/lib/cordis/router-http.d.ts +7 -1
  79. package/lib/cordis/router-http.js +44 -3
  80. package/lib/cordis/router.d.ts +1 -1
  81. package/lib/cordis/router.js +1 -1
  82. package/lib/cordis/state.d.ts +3 -2
  83. package/lib/cordis/state.js +371 -7
  84. package/lib/door/host.d.ts +2 -2
  85. package/lib/door/host.js +1 -1
  86. package/lib/door/page.d.ts +20 -1
  87. package/lib/door/page.js +75 -14
  88. package/lib/door/protocol.d.ts +1 -1
  89. package/lib/drivers.d.ts +10 -1
  90. package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
  91. package/lib/node-server.d.ts +7 -1
  92. package/lib/node-server.js +2 -2
  93. package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
  94. package/lib/{snapshot-text-BfEbEOjA.js → snapshot-text-CXTnjxGx.js} +121 -9
  95. package/lib/src-CtkIX_Gg.js +427 -0
  96. package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
  97. package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
  98. package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
  99. package/package.json +2 -1
  100. package/plugins/acryl-door/src/host/channel.ts +124 -9
  101. package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
  102. package/plugins/acryl-door/src/host/door-file.ts +7 -2
  103. package/plugins/acryl-door/src/host/door.ts +58 -23
  104. package/plugins/acryl-door/src/host/page-routes.ts +3 -1
  105. package/plugins/acryl-door/src/host/page-socket.ts +18 -8
  106. package/plugins/acryl-door/src/host/verbs.ts +3 -0
  107. package/plugins/acryl-door/src/index.ts +166 -29
  108. package/plugins/acryl-door/src/page/base.ts +32 -0
  109. package/plugins/acryl-door/src/page/channel.ts +11 -1
  110. package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
  111. package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
  112. package/plugins/acryl-door/src/page/driver/driver.ts +49 -7
  113. package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
  114. package/plugins/acryl-door/src/page/index.ts +46 -2
  115. package/plugins/acryl-door/src/page/indicator.ts +14 -6
  116. package/plugins/acryl-door/src/page/outline.ts +56 -0
  117. package/plugins/acryl-door/src/page/protocol.ts +4 -0
  118. package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
  119. package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
  120. package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
  121. package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
  122. package/plugins/acryl-node-server/src/client-modules.ts +7 -1
  123. package/plugins/acryl-node-server/src/index.ts +304 -16
  124. package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
  125. package/plugins/acryl-page/plugin.json +12 -0
  126. package/plugins/acryl-page/src/index.ts +163 -0
  127. package/plugins/acryl-router/src/service.ts +77 -14
  128. package/plugins/acryl-router-http/src/index.ts +15 -1
  129. package/plugins/acryl-router-http/src/serve.ts +31 -0
  130. package/plugins/acryl-state/plugin.json +4 -3
  131. package/plugins/acryl-state/src/client-source.ts +244 -0
  132. package/plugins/acryl-state/src/service.ts +33 -6
  133. package/plugins/acryl-state/src/socket.ts +96 -0
  134. package/plugins/acryl-web-server/plugin.json +14 -0
  135. package/plugins/acryl-web-server/src/index.ts +164 -0
  136. package/plugins/agent/agent.ts +38 -7
  137. package/plugins/agent/index.ts +1 -1
  138. package/plugins/agent/model.ts +10 -2
  139. package/plugins/agent/tools.ts +9 -3
  140. package/plugins/agent-panel/client/describe.ts +57 -0
  141. package/plugins/agent-panel/client/driver-pick.ts +22 -0
  142. package/plugins/agent-panel/client/frame.ts +51 -0
  143. package/plugins/agent-panel/client/main.ts +343 -81
  144. package/plugins/agent-panel/drivers.ts +7 -2
  145. package/plugins/agent-panel/headless.ts +5 -2
  146. package/plugins/agent-panel/index.ts +187 -61
  147. package/plugins/agent-panel/mount.ts +27 -0
  148. package/plugins/agent-panel/visitors.ts +231 -0
  149. package/plugins/desktop/frame.ts +231 -0
  150. package/plugins/desktop/launch.ts +44 -9
  151. package/plugins/desktop/main.ts +100 -32
  152. package/plugins/desktop/window.ts +21 -4
  153. package/plugins/driver-pty/client/terminal.ts +4 -1
  154. package/plugins/driver-pty/index.ts +38 -11
  155. package/plugins/driver-pty/spawn.ts +38 -0
  156. package/plugins/driver-pty/terminal.ts +2 -0
  157. package/plugins/extensions/index.ts +26 -1
  158. package/plugins/extensions/resolve.ts +13 -0
  159. package/plugins/feedback/client.d.ts +22 -0
  160. package/plugins/feedback/client.js +471 -0
  161. package/plugins/feedback/index.ts +143 -0
  162. package/plugins/feedback/plugin.json +11 -0
  163. package/plugins/feedback/store.ts +307 -0
  164. package/plugins/home/index.ts +122 -0
  165. package/plugins/home/plugin.json +11 -0
  166. package/plugins/instance/index.ts +12 -3
  167. package/plugins/manifest/app-plugins.ts +86 -0
  168. package/plugins/manifest/index.ts +12 -2
  169. package/plugins/manifest/snapshot.ts +275 -0
  170. package/plugins/openai/index.ts +64 -5
  171. package/plugins/openai/prompt.ts +23 -0
  172. package/shared/AGENTS.core.md +36 -16
  173. package/shared/skills/acryl-add-ui/SKILL.md +19 -0
  174. package/shared/skills/acryl-app/SKILL.md +13 -0
  175. package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
  176. package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
  177. package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
  178. package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
  179. package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
  180. package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
  181. package/shared/skills/cordis-plugin/SKILL.md +26 -0
  182. package/templates/agent/AGENTS.md +4 -2
  183. package/templates/agent/blend.yaml +4 -0
  184. package/templates/agent/gitignore +1 -0
  185. package/templates/agent/package.json +1 -0
  186. package/templates/agent/src/app.ts +150 -33
  187. package/templates/agent/src/frame.ts +17 -0
  188. package/templates/agent/src/main.ts +5 -3
  189. package/templates/agent/src/routes.ts +73 -21
  190. package/templates/agent/template.json +2 -0
  191. package/templates/agent/test/app.test.ts +83 -5
  192. package/templates/blank/AGENTS.md +3 -2
  193. package/templates/blank/blend.yaml +11 -0
  194. package/templates/blank/gitignore +1 -0
  195. package/templates/blank/package.json +1 -0
  196. package/templates/blank/src/app.ts +100 -29
  197. package/templates/blank/src/door.ts +7 -0
  198. package/templates/blank/src/first-page.ts +5 -0
  199. package/templates/blank/src/frame.ts +17 -0
  200. package/templates/blank/src/routes.ts +65 -8
  201. package/templates/blank/template.json +14 -1
  202. package/templates/blank/test/app.test.ts +103 -16
  203. package/templates/chat/blend.yaml +2 -1
  204. package/templates/chat/gitignore +1 -0
  205. package/templates/chat/package.json +1 -0
  206. package/templates/chat/src/app.ts +72 -22
  207. package/templates/chat/src/chat-app.ts +11 -2
  208. package/templates/chat/src/client/chat.tsx +4 -2
  209. package/templates/chat/src/frame.ts +17 -0
  210. package/templates/chat/src/main.ts +1 -6
  211. package/templates/chat/src/page.tsx +6 -4
  212. package/templates/chat/src/routes.ts +14 -4
  213. package/templates/chat/src/runtime.ts +9 -3
  214. package/templates/chat/template.json +3 -1
  215. package/templates/chat/test/chat.test.ts +46 -1
  216. package/templates/chat/test/server.test.ts +17 -3
  217. package/templates/nodejs/blend.yaml +2 -1
  218. package/templates/nodejs/gitignore +1 -0
  219. package/templates/nodejs/package.json +1 -0
  220. package/templates/nodejs/src/app.ts +76 -21
  221. package/templates/nodejs/src/frame.ts +17 -0
  222. package/templates/nodejs/src/main.ts +1 -6
  223. package/templates/nodejs/src/page.tsx +3 -3
  224. package/templates/nodejs/src/routes.ts +32 -13
  225. package/templates/nodejs/template.json +3 -1
  226. package/templates/nodejs/test/app.test.ts +38 -3
  227. package/templates/nodejs/test/server.test.ts +17 -3
  228. package/templates/notes/blend.yaml +2 -1
  229. package/templates/notes/gitignore +1 -0
  230. package/templates/notes/package.json +1 -0
  231. package/templates/notes/src/app.ts +72 -22
  232. package/templates/notes/src/frame.ts +17 -0
  233. package/templates/notes/src/main.ts +1 -6
  234. package/templates/notes/src/notes-app.ts +24 -17
  235. package/templates/notes/src/routes.ts +11 -4
  236. package/templates/notes/src/runtime.ts +9 -3
  237. package/templates/notes/src/views.tsx +26 -17
  238. package/templates/notes/template.json +3 -1
  239. package/templates/notes/test/notes.test.ts +39 -1
  240. package/templates/notes/test/server.test.ts +17 -3
  241. package/templates/todo/blend.yaml +3 -0
  242. package/templates/todo/gitignore +1 -0
  243. package/templates/todo/package.json +1 -0
  244. package/templates/todo/src/app.ts +81 -27
  245. package/templates/todo/src/door.ts +7 -0
  246. package/templates/todo/src/frame.ts +17 -0
  247. package/templates/todo/src/page.ts +13 -6
  248. package/templates/todo/src/todo.ts +18 -6
  249. package/templates/todo/template.json +11 -1
  250. package/templates/todo/test/todo.test.ts +67 -9
  251. package/lib/src-LX0E4ec4.js +0 -117
  252. package/shared/SKILL.md +0 -14
@@ -1,5 +1,5 @@
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";
1
+ import { f as UiActionResult, y as UiSnapshot } from "../protocol-BeIYCgTo.js";
2
+ import { i as UiChannel, r as PageStream, t as AuditLog } from "../audit-Bok_UNCn.js";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
 
@@ -102,7 +102,10 @@ interface ToolsPort {
102
102
  register(name: string, tool: {
103
103
  readonly description: string;
104
104
  readonly parameters?: Readonly<Record<string, unknown>>;
105
- run(input: unknown): unknown;
105
+ run(input: unknown, options?: {
106
+ signal?: AbortSignal;
107
+ page?: string;
108
+ }): unknown;
106
109
  }): () => void;
107
110
  list(): ReadonlyArray<{
108
111
  readonly name: string;
@@ -121,6 +124,8 @@ interface DoorDeps {
121
124
  readonly root: string;
122
125
  readonly url?: string | undefined;
123
126
  };
127
+ /** The prefix the app's pages answer under (the frame's canvas, `/app`); absent on the app's own server, at the root. */
128
+ readonly appBase?: string | undefined;
124
129
  /** Read at each call: the services may come and go while the door runs. */
125
130
  readonly extensions: () => ExtensionsPort | undefined;
126
131
  readonly tools: () => ToolsPort | undefined;
@@ -131,6 +136,10 @@ interface DoorDeps {
131
136
  /** How long a reload or a move may take before the new page has connected again (tests shorten it). Default 15 s. */
132
137
  readonly pageBackMs?: number;
133
138
  }
139
+ /** Who a verb runs for: `page`, the door's id of the page a run was asked from; its look and act go to that page alone (followed into its tab), never to another one. */
140
+ interface VerbScope {
141
+ readonly page?: string;
142
+ }
134
143
  /** What one verb answered: the value for `--json` and programs, the text for an agent reading a terminal. */
135
144
  interface VerbAnswer {
136
145
  readonly value: unknown;
@@ -139,11 +148,13 @@ interface VerbAnswer {
139
148
  declare class Door {
140
149
  private readonly deps;
141
150
  constructor(deps: DoorDeps);
151
+ /** 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. */
152
+ driveFor(windowId: string): () => void;
142
153
  /** Run one verb. @throws DoorError or UiControlError. */
143
- run(verb: string, args: unknown, signal: AbortSignal): Promise<VerbAnswer>;
154
+ run(verb: string, args: unknown, signal: AbortSignal, scope?: VerbScope): Promise<VerbAnswer>;
144
155
  brief(): VerbAnswer;
145
- look(input: Record<string, unknown>, signal: AbortSignal): Promise<UiSnapshot>;
146
- act(input: Record<string, unknown>, signal: AbortSignal): Promise<UiActionResult>;
156
+ look(input: Record<string, unknown>, signal: AbortSignal, scope?: VerbScope): Promise<UiSnapshot>;
157
+ act(input: Record<string, unknown>, signal: AbortSignal, scope?: VerbScope): Promise<UiActionResult>;
147
158
  /**
148
159
  * What the app answers at a path the page was sent to, so an agent that mistyped one is told ("404 at /nope") and not left to find out from a page it did not expect.
149
160
  * A second GET of the same path (a page is read-only); nothing when the app has no address or does not answer in time.
@@ -172,6 +183,8 @@ declare class Door {
172
183
  * MCP (`tools/list`) and the built-in chat (the `tools` registry). A verb added here is in all three, or the comparison test fails.
173
184
  */
174
185
  /** Every verb, in the order `brief` lists them. */
186
+ /** The verbs that only use the page: what a page-only door (a production assistant) offers. */
187
+ declare const PAGE_VERBS: readonly ["look", "act"];
175
188
  declare const VERBS: readonly ["brief", "look", "act", "plugin-list", "plugin-add", "plugin-remove", "plugin-reload", "tool-list", "tool-call", "window-list", "window-open", "window-focus", "window-close", "window-resize", "menu-list", "menu-click"];
176
189
  type Verb = (typeof VERBS)[number];
177
190
  /** The JSON Schema of a verb's arguments, as MCP lists it. */
@@ -227,6 +240,12 @@ interface DoorFile {
227
240
  declare function doorFilePath(appRoot: string): string;
228
241
  /** The connection file as written, or undefined when there is none or it does not parse. */
229
242
  declare function readDoorFile(appRoot: string): DoorFile | undefined;
243
+ /**
244
+ * Write this run's connection file, replacing a stale one.
245
+ * @throws when another live process already serves this folder's door: two apps from one folder would race for one file.
246
+ */
247
+ /** @throws when another live process already serves this folder's door: two apps from one folder would race for one file. */
248
+ declare function assertDoorIsFree(appRoot: string): void;
230
249
  //#endregion
231
250
  //#region ../../plugins/acryl-door/src/host/operator-routes.d.ts
232
251
  type CallResponse = {
@@ -257,12 +276,30 @@ interface Config {
257
276
  * run it in production (a packaged desktop app whose user drives it with an agent) says so here; `ACRYL_DOOR=off` still wins.
258
277
  */
259
278
  allowInProduction?: boolean;
279
+ /**
280
+ * Page only, for a production assistant: the page channel and two chat tools, `door_look` and `door_act`, and nothing else - no operator routes, no MCP, no desktop
281
+ * shell channel, no `.acryl/` file, no plugin, tool or window verbs. The app's agent can then use the page for a visitor and cannot change the app. Runs in production
282
+ * without `allowInProduction`; `ACRYL_DOOR=off` still wins.
283
+ */
284
+ pageOnly?: boolean;
260
285
  /** Reload the open pages after a live plugin change, so an agent that built a feature sees it on the page it has open. Default: true. */
261
286
  reloadOnChange?: boolean;
287
+ /**
288
+ * Put the page script in the host's own index page (DSH's web UI) too, so an agent can drive that UI. Default: true. A host whose index is a frame
289
+ * around the app (the ACRYL Blends frame, whose centre shows the app's page) turns it off: the door then drives the app's page in the canvas, and an
290
+ * agent's `act navigate` or a reload after a plugin change never takes the person's tab out of the frame.
291
+ */
292
+ hostIndex?: boolean;
293
+ /**
294
+ * The host prefix the app's pages are served under (the frame's canvas, `/app`); absent on the app's own server, where they are at the root. The
295
+ * door puts it on every page as `<meta name="acryl-app-base">`, and `act navigate` with an app path goes under it: a page the app serves at
296
+ * `/board` is reached at `/app/board` in the frame and at `/board` in production, and the same verb works in both.
297
+ */
298
+ appBase?: string;
262
299
  }
263
300
  declare const Config: z<Config>;
264
301
  /** The page of a path the app does not serve, development only: what is missing, a way back, and the door's script. */
265
302
  declare function notFoundPage(path: string, scriptTag: string): string;
266
303
  declare function apply(ctx: Context, rawConfig?: Config): void;
267
304
  //#endregion
268
- export { type CallResponse, Config, DOOR_PREFIX, Door, DoorError, type DoorFile, STATE_DIR, VERBS, VERB_SPECS, type Verb, type VerbAnswer, type VerbSpec, apply, chatToolName, doorFilePath, inject, mcpTools, name, notFoundPage, readDoorFile };
305
+ export { type CallResponse, Config, DOOR_PREFIX, Door, DoorError, type DoorFile, PAGE_VERBS, STATE_DIR, VERBS, VERB_SPECS, type Verb, type VerbAnswer, type VerbSpec, apply, assertDoorIsFree, chatToolName, doorFilePath, inject, mcpTools, name, notFoundPage, readDoorFile };
@@ -2,16 +2,31 @@ import { t as Acryl } from "../app-CQcGWjrk.js";
2
2
  import { r as streamSSE } from "../streaming-BlDaph8U.js";
3
3
  import { r as loopbackAuthority } from "../loopback-only-BbasoF7R.js";
4
4
  import "../helpers-BEjcdbgw.js";
5
- import { n as clientModules } from "../client-modules-drj4mjhj.js";
5
+ import { n as clientModules } from "../client-modules-Bkdr83jO.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-BfEbEOjA.js";
8
- import "../src-BTBm4OPD.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-CXTnjxGx.js";
8
+ import "../src-iBJ2ysV9.js";
9
9
  import "../middleware-BfG_fp8P.js";
10
10
  import { isAbsolute, join, relative, resolve } from "node:path";
11
11
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
12
12
  import z from "@deepseek-ai/schemastery";
13
13
  import { randomBytes } from "node:crypto";
14
14
  import { spawnSync } from "node:child_process";
15
+ //#region ../../plugins/acryl-door/src/page/base.ts
16
+ /**
17
+ * `path` (an app path, optionally with a query) under `base`: '/board' under '/app' is '/app/board'; the app's root is the base itself, so '/' is
18
+ * '/app' (its mount) and not '/app/'; without a base, the path as written.
19
+ */
20
+ function underAppBase(base, path) {
21
+ if (base === "") return path;
22
+ const at = path.search(/[?#]/u);
23
+ const route = at === -1 ? path : path.slice(0, at);
24
+ const rest = at === -1 ? "" : path.slice(at);
25
+ if (route === base || route.startsWith(`${base}/`)) return path;
26
+ if (route === "/" || route === "") return `${base}${rest}`;
27
+ return `${base}${route.startsWith("/") ? "" : "/"}${route}${rest}`;
28
+ }
29
+ //#endregion
15
30
  //#region ../../plugins/acryl-door/src/shell/protocol.ts
16
31
  var ShellError = class extends Error {
17
32
  code;
@@ -283,6 +298,8 @@ function shellRoutes(prefix, secret, channel, heartbeatMs = SHELL_HEARTBEAT_MS)
283
298
  * MCP (`tools/list`) and the built-in chat (the `tools` registry). A verb added here is in all three, or the comparison test fails.
284
299
  */
285
300
  /** Every verb, in the order `brief` lists them. */
301
+ /** The verbs that only use the page: what a page-only door (a production assistant) offers. */
302
+ const PAGE_VERBS = ["look", "act"];
286
303
  const VERBS = [
287
304
  "brief",
288
305
  "look",
@@ -620,20 +637,24 @@ var Door = class {
620
637
  constructor(deps) {
621
638
  this.deps = deps;
622
639
  }
640
+ /** 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. */
641
+ driveFor(windowId) {
642
+ return this.deps.channel.driveFor(windowId);
643
+ }
623
644
  /** Run one verb. @throws DoorError or UiControlError. */
624
- async run(verb, args, signal) {
645
+ async run(verb, args, signal, scope = {}) {
625
646
  const input = typeof args === "object" && args !== null && !Array.isArray(args) ? args : {};
626
647
  switch (verb) {
627
648
  case "brief": return this.brief();
628
649
  case "look": {
629
- const snapshot = await this.look(input, signal);
650
+ const snapshot = await this.look(input, signal, scope);
630
651
  return {
631
652
  value: snapshot,
632
653
  text: renderSnapshot(snapshot, (cursor) => `run look --cursor ${String(cursor)}`)
633
654
  };
634
655
  }
635
656
  case "act": {
636
- const result = await this.act(input, signal);
657
+ const result = await this.act(input, signal, scope);
637
658
  return {
638
659
  value: result,
639
660
  text: `done${result.target === void 0 ? "" : `: ${result.target.role} "${result.target.name}"`}${result.detail === void 0 ? "" : ` (${result.detail})`}; look again before the next act`
@@ -690,33 +711,47 @@ var Door = class {
690
711
  ].join("\n")
691
712
  };
692
713
  }
693
- look(input, signal) {
694
- const { window, rest } = this.windowOf(input);
714
+ look(input, signal, scope = {}) {
715
+ const { window, rest } = this.windowOf(input, scope);
695
716
  const request = this.parse({
696
717
  ...rest,
697
718
  op: "snapshot"
698
719
  });
699
- return this.callPage("look", request, signal, window);
720
+ return this.callPage("look", request, signal, window, scope.page);
700
721
  }
701
- async act(input, signal) {
722
+ async act(input, signal, scope = {}) {
702
723
  if (input.op === "snapshot") throw new DoorError("invalid", "use look to see the page");
703
- const { window, rest } = this.windowOf(input);
724
+ const { window, rest } = this.windowOf(input, scope);
704
725
  const request = this.parse(rest);
705
- if (request.op !== "reload" && request.op !== "navigate") return await this.callPage(`act ${String(input.op)}`, request, signal, window);
706
- const back = this.deps.channel.expectNewPage(this.deps.pageBackMs ?? PAGE_BACK_MS);
726
+ const page = scope.page;
727
+ const moves = request.op === "reload" || request.op === "navigate";
728
+ if (!moves && request.op !== "click" && request.op !== "press" && request.op !== "type") return await this.callPage(`act ${String(input.op)}`, request, signal, window, page);
729
+ const backMs = this.deps.pageBackMs ?? PAGE_BACK_MS;
730
+ const stay = new AbortController();
731
+ const back = page === void 0 ? this.deps.channel.expectNewPage(backMs, stay.signal) : this.deps.channel.expectPageBack(page, backMs, stay.signal);
707
732
  let answer;
733
+ let left = moves;
708
734
  try {
709
- answer = await this.callPage(`act ${request.op}`, request, signal, window);
735
+ answer = await this.callPage(`act ${request.op}`, request, signal, window, page);
736
+ left ||= answer.leaving === true;
710
737
  } catch (cause) {
711
- if (!(cause instanceof UiControlError && cause.code === "disconnected")) throw cause;
738
+ if (!(cause instanceof UiControlError && cause.code === "disconnected")) {
739
+ stay.abort();
740
+ throw cause;
741
+ }
712
742
  answer = {
713
743
  ok: true,
714
- detail: request.op === "reload" ? "reloading" : `going to ${request.path}`
744
+ detail: request.op === "reload" ? "reloading" : request.op === "navigate" ? `going to ${request.path}` : "the page is going to another address"
715
745
  };
746
+ left = true;
747
+ }
748
+ if (!left) {
749
+ stay.abort();
750
+ return answer;
716
751
  }
717
752
  const came = await back;
718
- if (came !== void 0) this.deps.channel.prefer(came);
719
- if (came === void 0) throw new UiControlError("timeout", request.op === "reload" ? "the page did not come back after the reload" : `the page did not come back after going to ${request.path}: is there a page at that path, and does it load the door's script?`);
753
+ if (came !== void 0 && page === void 0) this.deps.channel.prefer(came);
754
+ if (came === void 0) throw new UiControlError("timeout", request.op === "reload" ? "the page did not come back after the reload" : request.op === "navigate" ? `the page did not come back after going to ${request.path}: is there a page at that path, and does it load the door's script?` : `the ${request.op} took the page to another address, and no page came back: does the page there load the door's script?`);
720
755
  const landed = request.op === "navigate" ? await this.statusAt(request.path) : void 0;
721
756
  return {
722
757
  ...answer,
@@ -731,7 +766,8 @@ var Door = class {
731
766
  const base = this.deps.app.url;
732
767
  if (base === void 0) return void 0;
733
768
  try {
734
- const response = await fetch(new URL(path, base), {
769
+ const target = underAppBase(this.deps.appBase ?? "", path);
770
+ const response = await fetch(new URL(target, base), {
735
771
  headers: { accept: "text/html" },
736
772
  signal: AbortSignal.timeout(3e3)
737
773
  });
@@ -796,12 +832,13 @@ var Door = class {
796
832
  }
797
833
  }
798
834
  /** `window` on look and act: the desktop window (the number `window list` prints) whose page the call is for; without it, the page used last. */
799
- windowOf(input) {
835
+ windowOf(input, scope = {}) {
800
836
  const { window, ...rest } = input;
801
837
  if (window === void 0) return {
802
838
  window: void 0,
803
839
  rest
804
840
  };
841
+ if (scope.page !== void 0) throw new DoorError("invalid", "this run drives only the page it was asked from; window does not apply");
805
842
  if (typeof window !== "number" || !Number.isSafeInteger(window) || window < 1) throw new DoorError("invalid", "window must be the number `window list` prints for the window");
806
843
  return {
807
844
  window,
@@ -816,12 +853,13 @@ var Door = class {
816
853
  }
817
854
  }
818
855
  /** Run one call on the page and record it. The audit holds which control was touched and how it ended, never what was typed. */
819
- async callPage(verb, request, signal, window) {
856
+ async callPage(verb, request, signal, window, page) {
820
857
  const approval = "token";
821
858
  try {
822
859
  const value = await this.deps.channel.call(request, {
823
860
  signal,
824
861
  ...window === void 0 ? {} : { window },
862
+ ...page === void 0 ? {} : { page },
825
863
  ...request.op === "wait" ? { timeoutMs: (request.timeoutMs ?? 3e3) + 5e3 } : {}
826
864
  });
827
865
  const target = "target" in value ? value.target : void 0;
@@ -858,7 +896,7 @@ var Door = class {
858
896
  }
859
897
  async plugin(run, mutates = true) {
860
898
  const extensions = this.deps.extensions();
861
- if (extensions === void 0) throw new DoorError("unavailable", "this app has no extensions plugin, so plugins cannot be installed while it runs");
899
+ if (extensions === void 0) throw new DoorError("unavailable", "this door has no extensions plugin to drive. In ACRYL's frame none is needed: a saved change in src/ or plugins/ reloads the app by itself, so do not restart it. Elsewhere, the app has no way to install plugins while it runs");
862
900
  const value = await run(extensions);
863
901
  const unchanged = typeof value === "object" && value !== null && value.action === "unchanged";
864
902
  if (mutates && !unchanged && this.deps.reloadOnChange !== false) await this.deps.channel.reloadAll();
@@ -910,8 +948,8 @@ var Door = class {
910
948
  * `door_<verb>`. A tool answers the verb's text, what an agent reads in a terminal; a failed verb throws with its code, as the operator
911
949
  * client prints it.
912
950
  */
913
- /** Register every verb. @returns the function that removes them all; it also aborts the calls still running. */
914
- function registerChatTools(tools, door) {
951
+ /** Register the verbs (default: every one). @returns the function that removes them all; it also aborts the calls still running. */
952
+ function registerChatTools(tools, door, verbs = VERBS) {
915
953
  const running = new AbortController();
916
954
  const removers = [];
917
955
  const removeAll = () => {
@@ -919,12 +957,13 @@ function registerChatTools(tools, door) {
919
957
  for (const remove of removers.splice(0).reverse()) remove();
920
958
  };
921
959
  try {
922
- for (const verb of VERBS) removers.push(tools.register(chatToolName(verb), {
960
+ for (const verb of verbs) removers.push(tools.register(chatToolName(verb), {
923
961
  description: VERB_SPECS[verb].description,
924
962
  parameters: VERB_SPECS[verb].arguments,
925
- run: async (input) => {
963
+ run: async (input, options) => {
964
+ const signal = options?.signal === void 0 ? running.signal : AbortSignal.any([running.signal, options.signal]);
926
965
  try {
927
- return (await door.run(verb, input ?? {}, running.signal)).text;
966
+ return (await door.run(verb, input ?? {}, signal, options?.page === void 0 ? {} : { page: options.page })).text;
928
967
  } catch (cause) {
929
968
  if (cause instanceof DoorError || cause instanceof UiControlError) throw new Error(`${cause.code}: ${cause.message}`, { cause });
930
969
  throw cause;
@@ -993,10 +1032,14 @@ function alive(pid) {
993
1032
  * Write this run's connection file, replacing a stale one.
994
1033
  * @throws when another live process already serves this folder's door: two apps from one folder would race for one file.
995
1034
  */
996
- function writeDoorFile(appRoot, url, secret) {
997
- const dir = ensureStateDir(appRoot);
1035
+ /** @throws when another live process already serves this folder's door: two apps from one folder would race for one file. */
1036
+ function assertDoorIsFree(appRoot) {
998
1037
  const existing = readDoorFile(appRoot);
999
1038
  if (existing !== void 0 && existing.pid !== process.pid && alive(existing.pid)) throw new Error(`acryl-door: another process (pid ${String(existing.pid)}) serves the door of ${appRoot}; stop it first`);
1039
+ }
1040
+ function writeDoorFile(appRoot, url, secret) {
1041
+ const dir = ensureStateDir(appRoot);
1042
+ assertDoorIsFree(appRoot);
1000
1043
  const file = doorFilePath(appRoot);
1001
1044
  const temporary = join(dir, `${DOOR_FILE}.${String(process.pid)}.tmp`);
1002
1045
  const content = {
@@ -1167,11 +1210,13 @@ const ownOrigin = async (c, next) => {
1167
1210
  };
1168
1211
  function pageSocketRoutes(channel, options) {
1169
1212
  const heartbeatMs = options.heartbeatMs ?? 15e3;
1170
- return new Acryl().get(`${options.prefix}/channel/ws`, ownOrigin, options.upgrade((c) => {
1213
+ const events = (c) => {
1171
1214
  const windowId = c.req.query("window");
1172
1215
  const focused = c.req.query("focused") === "1";
1173
1216
  const shellQuery = Number(c.req.query("shell"));
1174
1217
  const shellWindow = Number.isSafeInteger(shellQuery) && shellQuery > 0 ? shellQuery : void 0;
1218
+ const tabQuery = c.req.query("tab");
1219
+ const tab = tabQuery !== void 0 && WINDOW_ID.test(tabQuery) ? tabQuery : void 0;
1175
1220
  let detach;
1176
1221
  let beat;
1177
1222
  return {
@@ -1186,10 +1231,16 @@ function pageSocketRoutes(channel, options) {
1186
1231
  d: data
1187
1232
  });
1188
1233
  },
1234
+ notify: (event, data) => {
1235
+ write({
1236
+ e: event,
1237
+ d: data
1238
+ });
1239
+ },
1189
1240
  close: () => {
1190
1241
  ws.close(1e3, "replaced");
1191
1242
  }
1192
- }, shellWindow);
1243
+ }, shellWindow, tab);
1193
1244
  write({ e: "ready" });
1194
1245
  beat = setInterval(() => {
1195
1246
  try {
@@ -1213,7 +1264,12 @@ function pageSocketRoutes(channel, options) {
1213
1264
  detach?.();
1214
1265
  }
1215
1266
  };
1216
- }, { textOnly: true }));
1267
+ };
1268
+ return new Acryl().get(`${options.prefix}/channel/ws`, ownOrigin, async (c, next) => {
1269
+ const allow = options.upgrade();
1270
+ if (allow === void 0) return c.json(error("the page channel is not ready yet"), 503);
1271
+ return allow(events, { textOnly: true })(c, next);
1272
+ });
1217
1273
  }
1218
1274
  /** The oldest Node the client runs on (the framework's own floor). */
1219
1275
  const MIN_NODE = [22, 19];
@@ -1333,14 +1389,13 @@ function writeShims(root, target) {
1333
1389
  */
1334
1390
  /** Stable Cordis plugin name, equal to the package name and the Loader row id. */
1335
1391
  const name = "acryl-door";
1336
- const inject = [
1337
- "router",
1338
- "appInstance",
1339
- "nodeServer"
1340
- ];
1392
+ const inject = ["router", "appInstance"];
1341
1393
  const Config = z.object({
1342
1394
  allowInProduction: z.boolean().default(false).description("Run in production too (NODE_ENV=production); ACRYL_DOOR=off still turns it off."),
1395
+ pageOnly: z.boolean().default(false).description("Only the page and the look and act chat tools (a production assistant); runs in production."),
1343
1396
  reloadOnChange: z.boolean().default(true).description("Reload the open pages after a plugin was added, removed or reloaded while the app runs."),
1397
+ hostIndex: z.boolean().default(true).description("Put the page script in the host's own index page too; off when that index is a frame around the app."),
1398
+ appBase: z.string().default("").description("The prefix the app pages are served under (the frame canvas), without a trailing slash; empty at the root."),
1344
1399
  tools: z.object({ expose: z.array(z.string().pattern(/^(?!door_)[A-Za-z][A-Za-z0-9_-]{0,63}$/u)).description("App tools (from the tools registry) the door offers; none by default.") })
1345
1400
  });
1346
1401
  /** The page module, served compiled from source by the app's own TypeScript. */
@@ -1372,25 +1427,51 @@ function notFoundPage(path, scriptTag) {
1372
1427
  }
1373
1428
  function apply(ctx, rawConfig = {}) {
1374
1429
  const config = Config(rawConfig);
1375
- if (process.env.ACRYL_DOOR === "off" || process.env.NODE_ENV === "production" && config.allowInProduction !== true) return;
1430
+ const pageOnly = config.pageOnly === true;
1431
+ if (process.env.ACRYL_DOOR === "off" || process.env.NODE_ENV === "production" && config.allowInProduction !== true && !pageOnly) return;
1376
1432
  const app = ctx.get("appInstance");
1377
- const server = ctx.get("nodeServer");
1433
+ const carrier = () => {
1434
+ const web = ctx.get("acrylWeb");
1435
+ if (web !== void 0) return web;
1436
+ const node = ctx.get("nodeServer");
1437
+ if (typeof node?.url !== "string") return void 0;
1438
+ try {
1439
+ const parsed = new URL(node.url);
1440
+ return {
1441
+ host: parsed.hostname,
1442
+ port: Number(parsed.port)
1443
+ };
1444
+ } catch {
1445
+ return;
1446
+ }
1447
+ };
1378
1448
  if (typeof app?.root !== "string") throw new Error("acryl-door: the appInstance service has no app folder (root)");
1379
1449
  const reportError = (operation, cause) => {
1380
1450
  ctx.logger?.error?.(`acryl-door: failed to ${operation}: ${cause instanceof Error ? cause.message : String(cause)}`);
1381
1451
  };
1452
+ const address = { url: "" };
1453
+ if (!pageOnly) assertDoorIsFree(app.root);
1382
1454
  const secret = newSecret();
1383
- const channel = new UiChannel({ attachWaitMs: 2e3 });
1455
+ const channel = new UiChannel({
1456
+ attachWaitMs: 2e3,
1457
+ requirePage: pageOnly
1458
+ });
1384
1459
  const shell = new ShellChannel();
1460
+ const audit = new AuditLog(auditPath(stateDir(app.root)));
1461
+ const rawBase = typeof config.appBase === "string" ? config.appBase.trim() : "";
1462
+ const appBase = rawBase.startsWith("/") && rawBase !== "/" ? rawBase.replace(/\/+$/u, "") : "";
1385
1463
  const door = new Door({
1386
1464
  channel,
1387
1465
  shell,
1388
- audit: new AuditLog(auditPath(stateDir(app.root))),
1466
+ audit,
1389
1467
  app: {
1390
1468
  name: app.name,
1391
1469
  root: app.root,
1392
- url: loopbackUrl(server.url)
1470
+ get url() {
1471
+ return address.url;
1472
+ }
1393
1473
  },
1474
+ ...appBase === "" ? {} : { appBase },
1394
1475
  extensions: () => ctx.get("extensions"),
1395
1476
  tools: () => ctx.get("tools"),
1396
1477
  expose: config.tools?.expose ?? [],
@@ -1402,8 +1483,12 @@ function apply(ctx, rawConfig = {}) {
1402
1483
  ctx.effect(() => () => {
1403
1484
  shell.close();
1404
1485
  }, "acryl-door: desktop shell channel");
1405
- const pageModule = new Acryl().get(`${DOOR_PREFIX}/page.js`, (c) => c.body(`import '${DOOR_PREFIX}/page/main.ts'\n`, 200, { "content-type": "text/javascript; charset=utf-8" })).route(`${DOOR_PREFIX}/page`, clientModules({ root: PAGE_DIR }));
1486
+ const pageModule = new Acryl().get(`${DOOR_PREFIX}/page.js`, (c) => c.body(`import '${DOOR_PREFIX}/page/main.ts'\n`, 200, { "content-type": "text/javascript; charset=utf-8" })).route(`${DOOR_PREFIX}/page`, clientModules({
1487
+ root: PAGE_DIR,
1488
+ mount: `${DOOR_PREFIX}/page`
1489
+ }));
1406
1490
  const tag = `<script type="module" src="${DOOR_PREFIX}/page.js"><\/script>`;
1491
+ const meta = appBase === "" ? "" : `<meta name="acryl-app-base" content="${appBase}">`;
1407
1492
  const addScript = async (c, next) => {
1408
1493
  await next();
1409
1494
  if (!(c.res.headers.get("content-type") ?? "").includes("text/html")) {
@@ -1413,11 +1498,12 @@ function apply(ctx, rawConfig = {}) {
1413
1498
  });
1414
1499
  return;
1415
1500
  }
1416
- const html = await c.res.text();
1501
+ let html = await c.res.text();
1417
1502
  const headers = new Headers(c.res.headers);
1418
1503
  headers.delete("content-length");
1419
- const carries = html.includes(`${DOOR_PREFIX}/page.js`);
1420
- c.res = new Response(carries ? html : html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag, {
1504
+ if (meta !== "" && !html.includes("name=\"acryl-app-base\"")) html = html.includes("</head>") ? html.replace("</head>", `${meta}</head>`) : meta + html;
1505
+ if (!html.includes(`/api/acryl-door/page.js`)) html = html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag;
1506
+ c.res = new Response(html, {
1421
1507
  status: c.res.status,
1422
1508
  headers
1423
1509
  });
@@ -1425,31 +1511,99 @@ function apply(ctx, rawConfig = {}) {
1425
1511
  const router = ctx.router;
1426
1512
  if (typeof router.wrap === "function") router.wrap(addScript);
1427
1513
  else ctx.router.route("/", new Acryl().use("*", addScript));
1428
- let upgrade;
1429
- try {
1430
- upgrade = ctx.nodeServer?.upgrades?.();
1431
- } catch (cause) {
1432
- reportError("open the page channel over a WebSocket (the pages use the event stream)", cause);
1433
- }
1434
- ctx.router.route("/", privateApi(DOOR_PREFIX, pageModule, pageChannelRoutes(channel, { prefix: DOOR_PREFIX }), ...upgrade === void 0 ? [] : [pageSocketRoutes(channel, {
1514
+ if (config.hostIndex !== false) ctx.inject(["webServer"], (inner) => {
1515
+ inner.effect(() => {
1516
+ const taps = inner.get("webServer");
1517
+ if (typeof taps?.tapIndex !== "function") return () => {};
1518
+ return taps.tapIndex((html) => {
1519
+ if (html.includes(`/api/acryl-door/page.js`)) return html;
1520
+ return html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag;
1521
+ });
1522
+ }, "acryl-door: the page script in the host index");
1523
+ });
1524
+ let pageUpgrade;
1525
+ let nodeUpgrade;
1526
+ const endNodeOwner = () => {
1527
+ if (pageUpgrade === nodeUpgrade) pageUpgrade = void 0;
1528
+ nodeUpgrade = void 0;
1529
+ };
1530
+ const nodeFallback = () => {
1531
+ if (ctx.get("routerHttp") !== void 0 || nodeUpgrade !== void 0) return;
1532
+ try {
1533
+ const allow = ctx.get("nodeServer")?.upgrades?.();
1534
+ if (allow !== void 0) {
1535
+ nodeUpgrade = allow;
1536
+ pageUpgrade = allow;
1537
+ }
1538
+ } catch (cause) {
1539
+ reportError("open the page channel over a WebSocket (the pages use the event stream)", cause);
1540
+ }
1541
+ };
1542
+ ctx.inject(["routerHttp"], (inner) => {
1543
+ endNodeOwner();
1544
+ const http = inner.get("routerHttp");
1545
+ try {
1546
+ const allow = http?.upgrades?.();
1547
+ if (allow === void 0 || http === void 0 || typeof http.mount !== "function") return;
1548
+ pageUpgrade = allow;
1549
+ const remove = http.mount(DOOR_PREFIX, { upgrade: [`${DOOR_PREFIX}/channel/ws`] });
1550
+ inner.effect(() => () => {
1551
+ remove();
1552
+ if (pageUpgrade === allow) pageUpgrade = void 0;
1553
+ nodeFallback();
1554
+ }, "acryl-door: the page socket via routerHttp");
1555
+ } catch (cause) {
1556
+ reportError("open the page channel over a WebSocket (the pages use the event stream)", cause);
1557
+ }
1558
+ });
1559
+ ctx.inject(["nodeServer"], (inner) => {
1560
+ inner.effect(() => {
1561
+ nodeFallback();
1562
+ const onService = inner.on;
1563
+ const off = onService("internal/service", () => {
1564
+ if (ctx.get("routerHttp") !== void 0) endNodeOwner();
1565
+ else nodeFallback();
1566
+ });
1567
+ return () => {
1568
+ off();
1569
+ endNodeOwner();
1570
+ };
1571
+ }, "acryl-door: the page socket on the node server");
1572
+ });
1573
+ ctx.router.route("/", pageSocketRoutes(channel, {
1435
1574
  prefix: DOOR_PREFIX,
1436
- upgrade
1437
- })], shellRoutes(DOOR_PREFIX, secret, shell), operatorRoutes(DOOR_PREFIX, secret, door, reportError), mcpRoutes(DOOR_PREFIX, secret, door, reportError)));
1575
+ upgrade: () => pageUpgrade
1576
+ }));
1577
+ ctx.router.route("/", privateApi(DOOR_PREFIX, pageModule, pageChannelRoutes(channel, { prefix: DOOR_PREFIX }), ...pageOnly ? [] : [
1578
+ shellRoutes(DOOR_PREFIX, secret, shell),
1579
+ operatorRoutes(DOOR_PREFIX, secret, door, reportError),
1580
+ mcpRoutes(DOOR_PREFIX, secret, door, reportError)
1581
+ ]));
1438
1582
  ctx.inject(["tools"], (inner) => {
1439
- inner.effect(() => registerChatTools(inner.get("tools"), door), "acryl-door: the verbs in the tools registry");
1583
+ inner.effect(() => registerChatTools(inner.get("tools"), door, pageOnly ? PAGE_VERBS : VERBS), "acryl-door: the verbs in the tools registry");
1440
1584
  });
1441
1585
  ctx.effect(function* () {
1442
1586
  yield ctx.reflect.provide("door", door);
1443
- writeDoorFile(app.root, loopbackUrl(server.url), secret);
1587
+ }, "acryl-door: the door service");
1588
+ if (pageOnly) return;
1589
+ ctx.effect(async () => {
1590
+ let bound = carrier();
1591
+ for (let at = 0; at < 500 && (bound === void 0 || !(bound.port > 0)); at += 1) {
1592
+ await new Promise((done) => setTimeout(done, 10));
1593
+ bound = carrier();
1594
+ }
1595
+ if (bound === void 0) throw new Error("acryl-door: no carrier bound the app (neither acrylWeb nor nodeServer answered)");
1596
+ address.url = loopbackUrl(`http://${bound.host}:${String(bound.port)}`);
1597
+ writeDoorFile(app.root, address.url, secret);
1444
1598
  try {
1445
1599
  writeShims(app.root, shimTarget());
1446
1600
  } catch (cause) {
1447
1601
  reportError("write .acryl/bin", cause);
1448
1602
  }
1449
- yield () => {
1603
+ return () => {
1450
1604
  removeDoorFile(app.root, secret);
1451
1605
  };
1452
- }, "acryl-door: the door service, .acryl/door.json and .acryl/bin/acryl");
1606
+ }, "acryl-door: .acryl/door.json and .acryl/bin/acryl");
1453
1607
  }
1454
1608
  //#endregion
1455
- export { Config, DOOR_PREFIX, Door, DoorError, STATE_DIR, VERBS, VERB_SPECS, apply, chatToolName, doorFilePath, inject, mcpTools, name, notFoundPage, readDoorFile };
1609
+ export { Config, DOOR_PREFIX, Door, DoorError, PAGE_VERBS, STATE_DIR, VERBS, VERB_SPECS, apply, assertDoorIsFree, chatToolName, doorFilePath, inject, mcpTools, name, notFoundPage, readDoorFile };
@@ -1,6 +1,6 @@
1
1
  import { t as Acryl } from "../app-CQcGWjrk.js";
2
2
  import { i as loopbackOnly } from "../loopback-only-BbasoF7R.js";
3
- import "../src-BTBm4OPD.js";
3
+ import "../src-iBJ2ysV9.js";
4
4
  import "../middleware-BfG_fp8P.js";
5
5
  import { Service } from "@deepseek-ai/cordis";
6
6
  //#region ../../plugins/acryl-inspect/src/report.ts