acrylblends 1.1.0 → 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 (241) hide show
  1. package/README.md +9 -0
  2. package/dist/index.d.ts +45 -1
  3. package/dist/index.js +1980 -27
  4. package/dist/index.js.map +1 -1
  5. package/docs/framework/agents/index.md +1 -1
  6. package/docs/framework/getting-started/desktop.md +23 -4
  7. package/docs/framework/guides/create-acrylblends.md +1 -1
  8. package/docs/framework/workshop/README.md +138 -152
  9. package/docs/framework/workshop/facilitator.md +69 -101
  10. package/legacy/README.md +9 -0
  11. package/legacy/agent/157ce3db1112/src/app.ts +98 -0
  12. package/legacy/agent/157ce3db1112/src/routes.ts +63 -0
  13. package/legacy/agent/24c681a00fca/src/app.ts +160 -0
  14. package/legacy/agent/24c681a00fca/src/routes.ts +83 -0
  15. package/legacy/agent/2e790da977b2/src/app.ts +98 -0
  16. package/legacy/agent/2e790da977b2/src/routes.ts +63 -0
  17. package/legacy/agent/6307045545dc/src/app.ts +168 -0
  18. package/legacy/agent/6307045545dc/src/routes.ts +83 -0
  19. package/legacy/agent/6a5fb9c15c53/src/app.ts +158 -0
  20. package/legacy/agent/6a5fb9c15c53/src/routes.ts +83 -0
  21. package/legacy/agent/71fb54a5a942/src/app.ts +111 -0
  22. package/legacy/agent/71fb54a5a942/src/routes.ts +63 -0
  23. package/legacy/agent/74ece148269c/src/app.ts +168 -0
  24. package/legacy/agent/74ece148269c/src/routes.ts +93 -0
  25. package/legacy/agent/855359304707/src/app.ts +168 -0
  26. package/legacy/agent/855359304707/src/routes.ts +126 -0
  27. package/legacy/agent/b5ef3c786ae4/src/app.ts +127 -0
  28. package/legacy/agent/b5ef3c786ae4/src/routes.ts +76 -0
  29. package/legacy/agent/c2f30958043b/src/app.ts +98 -0
  30. package/legacy/agent/c2f30958043b/src/routes.ts +53 -0
  31. package/legacy/blank/5a91c558af22/src/routes.ts +18 -0
  32. package/legacy/blank/9c650cf52bf3/src/routes.ts +14 -0
  33. package/legacy/blank/aea589283445/src/routes.ts +16 -0
  34. package/legacy/blank/b2cbb759c889/src/routes.ts +16 -0
  35. package/legacy/blank/d6d371c9a65e/src/routes.ts +18 -0
  36. package/legacy/chat/7076cc821e2c/src/app.ts +72 -0
  37. package/legacy/chat/7076cc821e2c/src/routes.ts +13 -0
  38. package/legacy/chat/7dbe6d5170df/src/app.ts +107 -0
  39. package/legacy/chat/7dbe6d5170df/src/routes.ts +19 -0
  40. package/legacy/chat/a9a9ab44c58e/src/app.ts +107 -0
  41. package/legacy/chat/a9a9ab44c58e/src/routes.ts +15 -0
  42. package/legacy/chat/f20ad9a51b06/src/app.ts +106 -0
  43. package/legacy/chat/f20ad9a51b06/src/routes.ts +15 -0
  44. package/legacy/layouts.json +524 -0
  45. package/legacy/nodejs/1d477f37aaf4/src/app.ts +92 -0
  46. package/legacy/nodejs/1d477f37aaf4/src/routes.ts +40 -0
  47. package/legacy/nodejs/b1bd9462496c/src/app.ts +61 -0
  48. package/legacy/nodejs/b1bd9462496c/src/routes.ts +37 -0
  49. package/legacy/nodejs/d5bab75076ef/src/app.ts +95 -0
  50. package/legacy/nodejs/d5bab75076ef/src/routes.ts +40 -0
  51. package/legacy/notes/4edc36949c15/src/app.ts +107 -0
  52. package/legacy/notes/4edc36949c15/src/routes.ts +14 -0
  53. package/legacy/notes/6cab2d09747c/src/app.ts +72 -0
  54. package/legacy/notes/6cab2d09747c/src/routes.ts +12 -0
  55. package/legacy/notes/7ca2dfed623a/src/app.ts +106 -0
  56. package/legacy/notes/7ca2dfed623a/src/routes.ts +14 -0
  57. package/legacy/todo/02e613b3625c/src/app.ts +71 -0
  58. package/legacy/todo/02e613b3625c/src/todo.ts +123 -0
  59. package/legacy/todo/45694a8d9e15/src/app.ts +71 -0
  60. package/legacy/todo/45694a8d9e15/src/todo.ts +96 -0
  61. package/legacy/todo/5923d3a6099f/src/app.ts +71 -0
  62. package/legacy/todo/5923d3a6099f/src/todo.ts +125 -0
  63. package/legacy/todo/849a18d66dc1/src/app.ts +91 -0
  64. package/legacy/todo/849a18d66dc1/src/todo.ts +125 -0
  65. package/legacy/todo/8e64211312f3/src/app.ts +71 -0
  66. package/legacy/todo/8e64211312f3/src/todo.ts +125 -0
  67. package/legacy/todo/b79078a87565/src/app.ts +71 -0
  68. package/legacy/todo/b79078a87565/src/todo.ts +125 -0
  69. package/lib/{audit-BFpULBCj.d.ts → audit-Bok_UNCn.d.ts} +26 -3
  70. package/lib/{client-modules-drj4mjhj.js → client-modules-Bkdr83jO.js} +1 -1
  71. package/lib/cordis/door.d.ts +42 -7
  72. package/lib/cordis/door.js +204 -60
  73. package/lib/cordis/inspect.js +1 -1
  74. package/lib/cordis/node-server.d.ts +101 -3
  75. package/lib/cordis/node-server.js +3 -3
  76. package/lib/cordis/router-http.d.ts +7 -1
  77. package/lib/cordis/router-http.js +44 -3
  78. package/lib/cordis/router.d.ts +1 -1
  79. package/lib/cordis/router.js +1 -1
  80. package/lib/cordis/state.js +1 -1
  81. package/lib/door/host.d.ts +2 -2
  82. package/lib/door/host.js +1 -1
  83. package/lib/door/page.d.ts +10 -1
  84. package/lib/door/page.js +46 -7
  85. package/lib/door/protocol.d.ts +1 -1
  86. package/lib/drivers.d.ts +10 -1
  87. package/lib/{index-DMOtA-NJ.d.ts → index-DGq7H0lb.d.ts} +11 -1
  88. package/lib/node-server.d.ts +7 -1
  89. package/lib/node-server.js +2 -2
  90. package/lib/{protocol-a2aqrp2X.d.ts → protocol-BeIYCgTo.d.ts} +4 -0
  91. package/lib/{snapshot-text-BwM6T2nG.js → snapshot-text-CXTnjxGx.js} +87 -7
  92. package/lib/src-CtkIX_Gg.js +427 -0
  93. package/lib/{src-BTBm4OPD.js → src-iBJ2ysV9.js} +77 -14
  94. package/lib/{test-websocket-BLeEvybj.js → test-websocket-CQOSvBth.js} +17 -7
  95. package/lib/{test-websocket-DyeS50Tr.d.ts → test-websocket-GUTAkFQT.d.ts} +3 -1
  96. package/package.json +2 -1
  97. package/plugins/acryl-door/src/host/channel.ts +86 -9
  98. package/plugins/acryl-door/src/host/chat-tools.ts +8 -6
  99. package/plugins/acryl-door/src/host/door-file.ts +7 -2
  100. package/plugins/acryl-door/src/host/door.ts +53 -23
  101. package/plugins/acryl-door/src/host/page-routes.ts +3 -1
  102. package/plugins/acryl-door/src/host/page-socket.ts +18 -8
  103. package/plugins/acryl-door/src/host/verbs.ts +3 -0
  104. package/plugins/acryl-door/src/index.ts +166 -29
  105. package/plugins/acryl-door/src/page/base.ts +32 -0
  106. package/plugins/acryl-door/src/page/channel.ts +3 -1
  107. package/plugins/acryl-door/src/page/driver/accessibility.ts +11 -0
  108. package/plugins/acryl-door/src/page/driver/actions.ts +4 -0
  109. package/plugins/acryl-door/src/page/driver/driver.ts +32 -4
  110. package/plugins/acryl-door/src/page/driver/snapshot.ts +5 -2
  111. package/plugins/acryl-door/src/page/index.ts +41 -2
  112. package/plugins/acryl-door/src/page/outline.ts +56 -0
  113. package/plugins/acryl-door/src/page/protocol.ts +4 -0
  114. package/plugins/acryl-door/src/page/snapshot-text.ts +2 -1
  115. package/plugins/acryl-node-server/src/adapter/listener.ts +4 -0
  116. package/plugins/acryl-node-server/src/adapter/raw.ts +18 -0
  117. package/plugins/acryl-node-server/src/adapter/websocket.ts +15 -9
  118. package/plugins/acryl-node-server/src/client-modules.ts +7 -1
  119. package/plugins/acryl-node-server/src/index.ts +304 -16
  120. package/plugins/acryl-node-server/src/upgrade-hub.ts +2 -2
  121. package/plugins/acryl-page/plugin.json +12 -0
  122. package/plugins/acryl-page/src/index.ts +163 -0
  123. package/plugins/acryl-router/src/service.ts +77 -14
  124. package/plugins/acryl-router-http/src/index.ts +15 -1
  125. package/plugins/acryl-router-http/src/serve.ts +31 -0
  126. package/plugins/acryl-web-server/plugin.json +14 -0
  127. package/plugins/acryl-web-server/src/index.ts +164 -0
  128. package/plugins/agent/agent.ts +38 -7
  129. package/plugins/agent/index.ts +1 -1
  130. package/plugins/agent/model.ts +10 -2
  131. package/plugins/agent/tools.ts +9 -3
  132. package/plugins/agent-panel/client/describe.ts +57 -0
  133. package/plugins/agent-panel/client/driver-pick.ts +22 -0
  134. package/plugins/agent-panel/client/frame.ts +51 -0
  135. package/plugins/agent-panel/client/main.ts +238 -26
  136. package/plugins/agent-panel/drivers.ts +7 -2
  137. package/plugins/agent-panel/headless.ts +5 -2
  138. package/plugins/agent-panel/index.ts +146 -57
  139. package/plugins/agent-panel/mount.ts +27 -0
  140. package/plugins/agent-panel/visitors.ts +231 -0
  141. package/plugins/desktop/frame.ts +231 -0
  142. package/plugins/desktop/launch.ts +44 -9
  143. package/plugins/desktop/main.ts +100 -32
  144. package/plugins/desktop/window.ts +21 -4
  145. package/plugins/driver-pty/client/terminal.ts +4 -1
  146. package/plugins/driver-pty/index.ts +38 -11
  147. package/plugins/driver-pty/spawn.ts +38 -0
  148. package/plugins/driver-pty/terminal.ts +2 -0
  149. package/plugins/extensions/index.ts +26 -1
  150. package/plugins/extensions/resolve.ts +13 -0
  151. package/plugins/feedback/client.d.ts +22 -0
  152. package/plugins/feedback/client.js +471 -0
  153. package/plugins/feedback/index.ts +143 -0
  154. package/plugins/feedback/plugin.json +11 -0
  155. package/plugins/feedback/store.ts +307 -0
  156. package/plugins/home/index.ts +122 -0
  157. package/plugins/home/plugin.json +11 -0
  158. package/plugins/instance/index.ts +6 -0
  159. package/plugins/manifest/app-plugins.ts +86 -0
  160. package/plugins/manifest/index.ts +12 -2
  161. package/plugins/manifest/snapshot.ts +275 -0
  162. package/plugins/openai/index.ts +64 -5
  163. package/plugins/openai/prompt.ts +23 -0
  164. package/shared/AGENTS.core.md +36 -16
  165. package/shared/skills/acryl-add-ui/SKILL.md +19 -0
  166. package/shared/skills/acryl-app/SKILL.md +13 -0
  167. package/shared/skills/acryl-build-plugin/SKILL.md +22 -0
  168. package/shared/skills/acryl-change-plugin/SKILL.md +14 -0
  169. package/shared/skills/acryl-diagnose-plugin/SKILL.md +13 -0
  170. package/shared/skills/acryl-fix-plugin/SKILL.md +13 -0
  171. package/shared/skills/acryl-remove-plugin/SKILL.md +12 -0
  172. package/shared/skills/acryl-share-plugin/SKILL.md +16 -0
  173. package/shared/skills/cordis-plugin/SKILL.md +26 -0
  174. package/templates/agent/AGENTS.md +1 -1
  175. package/templates/agent/blend.yaml +4 -0
  176. package/templates/agent/gitignore +1 -0
  177. package/templates/agent/package.json +1 -0
  178. package/templates/agent/src/app.ts +150 -33
  179. package/templates/agent/src/frame.ts +17 -0
  180. package/templates/agent/src/main.ts +5 -3
  181. package/templates/agent/src/routes.ts +38 -19
  182. package/templates/agent/template.json +2 -0
  183. package/templates/agent/test/app.test.ts +45 -3
  184. package/templates/blank/AGENTS.md +3 -2
  185. package/templates/blank/blend.yaml +11 -0
  186. package/templates/blank/gitignore +1 -0
  187. package/templates/blank/package.json +1 -0
  188. package/templates/blank/src/app.ts +100 -29
  189. package/templates/blank/src/door.ts +7 -0
  190. package/templates/blank/src/first-page.ts +5 -0
  191. package/templates/blank/src/frame.ts +17 -0
  192. package/templates/blank/src/routes.ts +65 -8
  193. package/templates/blank/template.json +14 -1
  194. package/templates/blank/test/app.test.ts +103 -16
  195. package/templates/chat/blend.yaml +2 -1
  196. package/templates/chat/gitignore +1 -0
  197. package/templates/chat/package.json +1 -0
  198. package/templates/chat/src/app.ts +72 -22
  199. package/templates/chat/src/chat-app.ts +11 -2
  200. package/templates/chat/src/client/chat.tsx +4 -2
  201. package/templates/chat/src/frame.ts +17 -0
  202. package/templates/chat/src/page.tsx +6 -4
  203. package/templates/chat/src/routes.ts +14 -4
  204. package/templates/chat/src/runtime.ts +9 -3
  205. package/templates/chat/template.json +3 -1
  206. package/templates/chat/test/chat.test.ts +46 -1
  207. package/templates/chat/test/server.test.ts +17 -3
  208. package/templates/nodejs/blend.yaml +2 -1
  209. package/templates/nodejs/gitignore +1 -0
  210. package/templates/nodejs/package.json +1 -0
  211. package/templates/nodejs/src/app.ts +76 -21
  212. package/templates/nodejs/src/frame.ts +17 -0
  213. package/templates/nodejs/src/page.tsx +3 -3
  214. package/templates/nodejs/src/routes.ts +32 -13
  215. package/templates/nodejs/template.json +3 -1
  216. package/templates/nodejs/test/app.test.ts +38 -3
  217. package/templates/nodejs/test/server.test.ts +17 -3
  218. package/templates/notes/blend.yaml +2 -1
  219. package/templates/notes/gitignore +1 -0
  220. package/templates/notes/package.json +1 -0
  221. package/templates/notes/src/app.ts +72 -22
  222. package/templates/notes/src/frame.ts +17 -0
  223. package/templates/notes/src/notes-app.ts +24 -17
  224. package/templates/notes/src/routes.ts +11 -4
  225. package/templates/notes/src/runtime.ts +9 -3
  226. package/templates/notes/src/views.tsx +26 -17
  227. package/templates/notes/template.json +3 -1
  228. package/templates/notes/test/notes.test.ts +39 -1
  229. package/templates/notes/test/server.test.ts +17 -3
  230. package/templates/todo/blend.yaml +3 -0
  231. package/templates/todo/gitignore +1 -0
  232. package/templates/todo/package.json +1 -0
  233. package/templates/todo/src/app.ts +81 -27
  234. package/templates/todo/src/door.ts +7 -0
  235. package/templates/todo/src/frame.ts +17 -0
  236. package/templates/todo/src/page.ts +13 -6
  237. package/templates/todo/src/todo.ts +18 -6
  238. package/templates/todo/template.json +11 -1
  239. package/templates/todo/test/todo.test.ts +67 -9
  240. package/lib/src-LX0E4ec4.js +0 -117
  241. 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-BFpULBCj.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;
@@ -142,10 +151,10 @@ declare class Door {
142
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. */
143
152
  driveFor(windowId: string): () => void;
144
153
  /** Run one verb. @throws DoorError or UiControlError. */
145
- run(verb: string, args: unknown, signal: AbortSignal): Promise<VerbAnswer>;
154
+ run(verb: string, args: unknown, signal: AbortSignal, scope?: VerbScope): Promise<VerbAnswer>;
146
155
  brief(): VerbAnswer;
147
- look(input: Record<string, unknown>, signal: AbortSignal): Promise<UiSnapshot>;
148
- 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>;
149
158
  /**
150
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.
151
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.
@@ -174,6 +183,8 @@ declare class Door {
174
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.
175
184
  */
176
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"];
177
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"];
178
189
  type Verb = (typeof VERBS)[number];
179
190
  /** The JSON Schema of a verb's arguments, as MCP lists it. */
@@ -229,6 +240,12 @@ interface DoorFile {
229
240
  declare function doorFilePath(appRoot: string): string;
230
241
  /** The connection file as written, or undefined when there is none or it does not parse. */
231
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;
232
249
  //#endregion
233
250
  //#region ../../plugins/acryl-door/src/host/operator-routes.d.ts
234
251
  type CallResponse = {
@@ -259,12 +276,30 @@ interface Config {
259
276
  * run it in production (a packaged desktop app whose user drives it with an agent) says so here; `ACRYL_DOOR=off` still wins.
260
277
  */
261
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;
262
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. */
263
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;
264
299
  }
265
300
  declare const Config: z<Config>;
266
301
  /** The page of a path the app does not serve, development only: what is missing, a way back, and the door's script. */
267
302
  declare function notFoundPage(path: string, scriptTag: string): string;
268
303
  declare function apply(ctx: Context, rawConfig?: Config): void;
269
304
  //#endregion
270
- 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-BwM6T2nG.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",
@@ -625,19 +642,19 @@ var Door = class {
625
642
  return this.deps.channel.driveFor(windowId);
626
643
  }
627
644
  /** Run one verb. @throws DoorError or UiControlError. */
628
- async run(verb, args, signal) {
645
+ async run(verb, args, signal, scope = {}) {
629
646
  const input = typeof args === "object" && args !== null && !Array.isArray(args) ? args : {};
630
647
  switch (verb) {
631
648
  case "brief": return this.brief();
632
649
  case "look": {
633
- const snapshot = await this.look(input, signal);
650
+ const snapshot = await this.look(input, signal, scope);
634
651
  return {
635
652
  value: snapshot,
636
653
  text: renderSnapshot(snapshot, (cursor) => `run look --cursor ${String(cursor)}`)
637
654
  };
638
655
  }
639
656
  case "act": {
640
- const result = await this.act(input, signal);
657
+ const result = await this.act(input, signal, scope);
641
658
  return {
642
659
  value: result,
643
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`
@@ -694,33 +711,47 @@ var Door = class {
694
711
  ].join("\n")
695
712
  };
696
713
  }
697
- look(input, signal) {
698
- const { window, rest } = this.windowOf(input);
714
+ look(input, signal, scope = {}) {
715
+ const { window, rest } = this.windowOf(input, scope);
699
716
  const request = this.parse({
700
717
  ...rest,
701
718
  op: "snapshot"
702
719
  });
703
- return this.callPage("look", request, signal, window);
720
+ return this.callPage("look", request, signal, window, scope.page);
704
721
  }
705
- async act(input, signal) {
722
+ async act(input, signal, scope = {}) {
706
723
  if (input.op === "snapshot") throw new DoorError("invalid", "use look to see the page");
707
- const { window, rest } = this.windowOf(input);
724
+ const { window, rest } = this.windowOf(input, scope);
708
725
  const request = this.parse(rest);
709
- if (request.op !== "reload" && request.op !== "navigate") return await this.callPage(`act ${String(input.op)}`, request, signal, window);
710
- 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);
711
732
  let answer;
733
+ let left = moves;
712
734
  try {
713
- 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;
714
737
  } catch (cause) {
715
- if (!(cause instanceof UiControlError && cause.code === "disconnected")) throw cause;
738
+ if (!(cause instanceof UiControlError && cause.code === "disconnected")) {
739
+ stay.abort();
740
+ throw cause;
741
+ }
716
742
  answer = {
717
743
  ok: true,
718
- 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"
719
745
  };
746
+ left = true;
747
+ }
748
+ if (!left) {
749
+ stay.abort();
750
+ return answer;
720
751
  }
721
752
  const came = await back;
722
- if (came !== void 0) this.deps.channel.prefer(came);
723
- 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?`);
724
755
  const landed = request.op === "navigate" ? await this.statusAt(request.path) : void 0;
725
756
  return {
726
757
  ...answer,
@@ -735,7 +766,8 @@ var Door = class {
735
766
  const base = this.deps.app.url;
736
767
  if (base === void 0) return void 0;
737
768
  try {
738
- 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), {
739
771
  headers: { accept: "text/html" },
740
772
  signal: AbortSignal.timeout(3e3)
741
773
  });
@@ -800,12 +832,13 @@ var Door = class {
800
832
  }
801
833
  }
802
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. */
803
- windowOf(input) {
835
+ windowOf(input, scope = {}) {
804
836
  const { window, ...rest } = input;
805
837
  if (window === void 0) return {
806
838
  window: void 0,
807
839
  rest
808
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");
809
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");
810
843
  return {
811
844
  window,
@@ -820,12 +853,13 @@ var Door = class {
820
853
  }
821
854
  }
822
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. */
823
- async callPage(verb, request, signal, window) {
856
+ async callPage(verb, request, signal, window, page) {
824
857
  const approval = "token";
825
858
  try {
826
859
  const value = await this.deps.channel.call(request, {
827
860
  signal,
828
861
  ...window === void 0 ? {} : { window },
862
+ ...page === void 0 ? {} : { page },
829
863
  ...request.op === "wait" ? { timeoutMs: (request.timeoutMs ?? 3e3) + 5e3 } : {}
830
864
  });
831
865
  const target = "target" in value ? value.target : void 0;
@@ -862,7 +896,7 @@ var Door = class {
862
896
  }
863
897
  async plugin(run, mutates = true) {
864
898
  const extensions = this.deps.extensions();
865
- 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");
866
900
  const value = await run(extensions);
867
901
  const unchanged = typeof value === "object" && value !== null && value.action === "unchanged";
868
902
  if (mutates && !unchanged && this.deps.reloadOnChange !== false) await this.deps.channel.reloadAll();
@@ -914,8 +948,8 @@ var Door = class {
914
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
915
949
  * client prints it.
916
950
  */
917
- /** Register every verb. @returns the function that removes them all; it also aborts the calls still running. */
918
- 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) {
919
953
  const running = new AbortController();
920
954
  const removers = [];
921
955
  const removeAll = () => {
@@ -923,12 +957,13 @@ function registerChatTools(tools, door) {
923
957
  for (const remove of removers.splice(0).reverse()) remove();
924
958
  };
925
959
  try {
926
- for (const verb of VERBS) removers.push(tools.register(chatToolName(verb), {
960
+ for (const verb of verbs) removers.push(tools.register(chatToolName(verb), {
927
961
  description: VERB_SPECS[verb].description,
928
962
  parameters: VERB_SPECS[verb].arguments,
929
- run: async (input) => {
963
+ run: async (input, options) => {
964
+ const signal = options?.signal === void 0 ? running.signal : AbortSignal.any([running.signal, options.signal]);
930
965
  try {
931
- 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;
932
967
  } catch (cause) {
933
968
  if (cause instanceof DoorError || cause instanceof UiControlError) throw new Error(`${cause.code}: ${cause.message}`, { cause });
934
969
  throw cause;
@@ -997,10 +1032,14 @@ function alive(pid) {
997
1032
  * Write this run's connection file, replacing a stale one.
998
1033
  * @throws when another live process already serves this folder's door: two apps from one folder would race for one file.
999
1034
  */
1000
- function writeDoorFile(appRoot, url, secret) {
1001
- 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) {
1002
1037
  const existing = readDoorFile(appRoot);
1003
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);
1004
1043
  const file = doorFilePath(appRoot);
1005
1044
  const temporary = join(dir, `${DOOR_FILE}.${String(process.pid)}.tmp`);
1006
1045
  const content = {
@@ -1171,11 +1210,13 @@ const ownOrigin = async (c, next) => {
1171
1210
  };
1172
1211
  function pageSocketRoutes(channel, options) {
1173
1212
  const heartbeatMs = options.heartbeatMs ?? 15e3;
1174
- return new Acryl().get(`${options.prefix}/channel/ws`, ownOrigin, options.upgrade((c) => {
1213
+ const events = (c) => {
1175
1214
  const windowId = c.req.query("window");
1176
1215
  const focused = c.req.query("focused") === "1";
1177
1216
  const shellQuery = Number(c.req.query("shell"));
1178
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;
1179
1220
  let detach;
1180
1221
  let beat;
1181
1222
  return {
@@ -1199,7 +1240,7 @@ function pageSocketRoutes(channel, options) {
1199
1240
  close: () => {
1200
1241
  ws.close(1e3, "replaced");
1201
1242
  }
1202
- }, shellWindow);
1243
+ }, shellWindow, tab);
1203
1244
  write({ e: "ready" });
1204
1245
  beat = setInterval(() => {
1205
1246
  try {
@@ -1223,7 +1264,12 @@ function pageSocketRoutes(channel, options) {
1223
1264
  detach?.();
1224
1265
  }
1225
1266
  };
1226
- }, { 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
+ });
1227
1273
  }
1228
1274
  /** The oldest Node the client runs on (the framework's own floor). */
1229
1275
  const MIN_NODE = [22, 19];
@@ -1343,14 +1389,13 @@ function writeShims(root, target) {
1343
1389
  */
1344
1390
  /** Stable Cordis plugin name, equal to the package name and the Loader row id. */
1345
1391
  const name = "acryl-door";
1346
- const inject = [
1347
- "router",
1348
- "appInstance",
1349
- "nodeServer"
1350
- ];
1392
+ const inject = ["router", "appInstance"];
1351
1393
  const Config = z.object({
1352
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."),
1353
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."),
1354
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.") })
1355
1400
  });
1356
1401
  /** The page module, served compiled from source by the app's own TypeScript. */
@@ -1382,25 +1427,51 @@ function notFoundPage(path, scriptTag) {
1382
1427
  }
1383
1428
  function apply(ctx, rawConfig = {}) {
1384
1429
  const config = Config(rawConfig);
1385
- 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;
1386
1432
  const app = ctx.get("appInstance");
1387
- 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
+ };
1388
1448
  if (typeof app?.root !== "string") throw new Error("acryl-door: the appInstance service has no app folder (root)");
1389
1449
  const reportError = (operation, cause) => {
1390
1450
  ctx.logger?.error?.(`acryl-door: failed to ${operation}: ${cause instanceof Error ? cause.message : String(cause)}`);
1391
1451
  };
1452
+ const address = { url: "" };
1453
+ if (!pageOnly) assertDoorIsFree(app.root);
1392
1454
  const secret = newSecret();
1393
- const channel = new UiChannel({ attachWaitMs: 2e3 });
1455
+ const channel = new UiChannel({
1456
+ attachWaitMs: 2e3,
1457
+ requirePage: pageOnly
1458
+ });
1394
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, "") : "";
1395
1463
  const door = new Door({
1396
1464
  channel,
1397
1465
  shell,
1398
- audit: new AuditLog(auditPath(stateDir(app.root))),
1466
+ audit,
1399
1467
  app: {
1400
1468
  name: app.name,
1401
1469
  root: app.root,
1402
- url: loopbackUrl(server.url)
1470
+ get url() {
1471
+ return address.url;
1472
+ }
1403
1473
  },
1474
+ ...appBase === "" ? {} : { appBase },
1404
1475
  extensions: () => ctx.get("extensions"),
1405
1476
  tools: () => ctx.get("tools"),
1406
1477
  expose: config.tools?.expose ?? [],
@@ -1412,8 +1483,12 @@ function apply(ctx, rawConfig = {}) {
1412
1483
  ctx.effect(() => () => {
1413
1484
  shell.close();
1414
1485
  }, "acryl-door: desktop shell channel");
1415
- 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
+ }));
1416
1490
  const tag = `<script type="module" src="${DOOR_PREFIX}/page.js"><\/script>`;
1491
+ const meta = appBase === "" ? "" : `<meta name="acryl-app-base" content="${appBase}">`;
1417
1492
  const addScript = async (c, next) => {
1418
1493
  await next();
1419
1494
  if (!(c.res.headers.get("content-type") ?? "").includes("text/html")) {
@@ -1423,11 +1498,12 @@ function apply(ctx, rawConfig = {}) {
1423
1498
  });
1424
1499
  return;
1425
1500
  }
1426
- const html = await c.res.text();
1501
+ let html = await c.res.text();
1427
1502
  const headers = new Headers(c.res.headers);
1428
1503
  headers.delete("content-length");
1429
- const carries = html.includes(`${DOOR_PREFIX}/page.js`);
1430
- 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, {
1431
1507
  status: c.res.status,
1432
1508
  headers
1433
1509
  });
@@ -1435,31 +1511,99 @@ function apply(ctx, rawConfig = {}) {
1435
1511
  const router = ctx.router;
1436
1512
  if (typeof router.wrap === "function") router.wrap(addScript);
1437
1513
  else ctx.router.route("/", new Acryl().use("*", addScript));
1438
- let upgrade;
1439
- try {
1440
- upgrade = ctx.nodeServer?.upgrades?.();
1441
- } catch (cause) {
1442
- reportError("open the page channel over a WebSocket (the pages use the event stream)", cause);
1443
- }
1444
- 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, {
1445
1574
  prefix: DOOR_PREFIX,
1446
- upgrade
1447
- })], 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
+ ]));
1448
1582
  ctx.inject(["tools"], (inner) => {
1449
- 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");
1450
1584
  });
1451
1585
  ctx.effect(function* () {
1452
1586
  yield ctx.reflect.provide("door", door);
1453
- 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);
1454
1598
  try {
1455
1599
  writeShims(app.root, shimTarget());
1456
1600
  } catch (cause) {
1457
1601
  reportError("write .acryl/bin", cause);
1458
1602
  }
1459
- yield () => {
1603
+ return () => {
1460
1604
  removeDoorFile(app.root, secret);
1461
1605
  };
1462
- }, "acryl-door: the door service, .acryl/door.json and .acryl/bin/acryl");
1606
+ }, "acryl-door: .acryl/door.json and .acryl/bin/acryl");
1463
1607
  }
1464
1608
  //#endregion
1465
- 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