acrylblends 1.0.3 → 1.0.4
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.
- package/docs/framework/workshop/README.md +3 -3
- package/docs/framework/workshop/facilitator.md +1 -0
- package/lib/{audit-BXnYsLge.d.ts → audit-D5z9uwOP.d.ts} +14 -1
- package/lib/cordis/door.d.ts +8 -2
- package/lib/cordis/door.js +88 -12
- package/lib/cordis/inspect.js +1 -1
- package/lib/cordis/router-http.d.ts +1 -1
- package/lib/cordis/router.d.ts +1 -1
- package/lib/cordis/router.js +1 -1
- package/lib/cordis/state.js +1 -1
- package/lib/door/host.d.ts +2 -2
- package/lib/door/host.js +1 -1
- package/lib/door/page.d.ts +12 -1
- package/lib/door/page.js +21 -0
- package/lib/door/protocol.d.ts +2 -2
- package/lib/door/protocol.js +19 -3
- package/lib/{index-D4nVR3wu.d.ts → index-DMOtA-NJ.d.ts} +7 -1
- package/lib/{protocol-BizVt4J6.d.ts → protocol-C37gPePD.d.ts} +9 -2
- package/lib/{snapshot-text-DmE0YfeZ.js → snapshot-text-BLz8nlSc.js} +49 -1
- package/lib/{src-2Z4mJg0x.js → src-dy3Fk9tA.js} +16 -0
- package/package.json +1 -1
- package/plugins/acryl-door/src/cli/acryl.ts +5 -3
- package/plugins/acryl-door/src/host/channel.ts +36 -0
- package/plugins/acryl-door/src/host/door.ts +26 -4
- package/plugins/acryl-door/src/host/shim.ts +23 -0
- package/plugins/acryl-door/src/host/verbs.ts +4 -3
- package/plugins/acryl-door/src/index.ts +26 -3
- package/plugins/acryl-door/src/page/driver/driver.ts +22 -1
- package/plugins/acryl-door/src/page/protocol.ts +13 -2
- package/plugins/acryl-router/src/service.ts +19 -1
- package/plugins/agent-panel/client/main.ts +63 -34
- package/plugins/agent-panel/index.ts +115 -25
- package/shared/DEFAULTS.md +4 -0
|
@@ -182,8 +182,8 @@ Then `inject: ['router', 'kv', 'db']` with `import type {} from '../plugins/acry
|
|
|
182
182
|
`await ctx.db.run('INSERT INTO visits (at) VALUES (?)', new Date().toISOString())` before the reply. The database is `my-hero/.acrylblends/data.sqlite`,
|
|
183
183
|
and the migrations run at start, in order.
|
|
184
184
|
|
|
185
|
-
**Stuck?** `migrations "migrations" is relative, but this app has no file base URL` means the
|
|
186
|
-
`src/app.ts` as above. "no such table" means the file is not in `migrations/` at the app root.
|
|
185
|
+
**Stuck?** `migrations "migrations" is relative, but this app has no file base URL` means the app has `acrylblends` older than 1.0.3, where a relative path in `blend.yaml` was not read
|
|
186
|
+
from the app folder; move it into `src/app.ts` as above, or update (`npx acrylblends@latest upgrade`). "no such table" means the file is not in `migrations/` at the app root.
|
|
187
187
|
|
|
188
188
|
## 5. A desktop window (7 minutes)
|
|
189
189
|
|
|
@@ -280,7 +280,7 @@ few minutes; the panel shows each step, and has a stop button. Open `/board` whe
|
|
|
280
280
|
A terminal agent works too: choose `pty:codex` (or `pty:claude`, `pty:pi`) for its normal interactive session inside the app folder, where it can use
|
|
281
281
|
the door (`.acryl/bin/acryl`) to look at the page it built.
|
|
282
282
|
|
|
283
|
-
**Stuck?** The panel uses your own agent account: `codex login` (or `claude`) in a terminal first. If `npm install` warns that npm blocked
|
|
283
|
+
**Stuck?** The panel uses your own agent account: `codex login` (or `claude`) in a terminal first. Your own agent settings travel with it: skills or hooks of your own can make it stop and ask for approval instead of acting, so write the task as "do it now, without asking for approval", or start the agent from a clean configuration (a fresh `CODEX_HOME`, or no personal skills). If `npm install` warns that npm blocked
|
|
284
284
|
`node-pty`'s install script, ignore it: prebuilt binaries ship. Only if the panel lists no `pty:` terminal, run `npm approve-scripts node-pty` and
|
|
285
285
|
`npm rebuild node-pty` (the `add` output says the same).
|
|
286
286
|
|
|
@@ -54,6 +54,7 @@ what the optional column is for, and the handout names the same choices so nobod
|
|
|
54
54
|
|
|
55
55
|
These are the defects found by the walk. They are known; say so rather than letting people think they broke something.
|
|
56
56
|
|
|
57
|
+
- **An agent with its own settings acts differently.** Skills, hooks and instructions in a person's own Codex or Claude configuration travel into the panel's run: on the owner's laptop Codex stopped to "present a design for approval" instead of building. Word every task for the agent as "do it now, without asking for approval", and for a demo machine start the agent from a clean configuration (a fresh `CODEX_HOME`, no personal skills).
|
|
57
58
|
- **The data step is the slow one.** Wiring `kv` then `db` by hand is about ten minutes of edits for a newcomer (two rows, two registry lines, two
|
|
58
59
|
type imports, the migrations folder). It is the block the handout names optional, and the one to help with first.
|
|
59
60
|
- **A relative migrations path in `blend.yaml` fails.** `migrations: migrations` in the row stops the app with "this app has no file base URL to
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as UiRequest, n as AuditEntry, r as ChannelToHost, v as UiResult } from "./protocol-C37gPePD.js";
|
|
2
2
|
|
|
3
3
|
//#region ../../plugins/acryl-door/src/host/channel.d.ts
|
|
4
4
|
/** The slice of a page's stream the channel needs, so tests can supply their own. */
|
|
@@ -12,6 +12,8 @@ declare class UiChannel {
|
|
|
12
12
|
private nextId;
|
|
13
13
|
private clock;
|
|
14
14
|
private closed;
|
|
15
|
+
/** Callers waiting for a page to (re)connect: each is told once, by the next stream that attaches. */
|
|
16
|
+
private readonly attachWaiters;
|
|
15
17
|
/**
|
|
16
18
|
* Register a page's stream. A stream already open for the same window is closed first, and its calls settle as `disconnected`.
|
|
17
19
|
* @returns the function to call when this stream ends.
|
|
@@ -34,6 +36,17 @@ declare class UiChannel {
|
|
|
34
36
|
signal?: AbortSignal;
|
|
35
37
|
timeoutMs?: number;
|
|
36
38
|
}): Promise<UiResult>;
|
|
39
|
+
/**
|
|
40
|
+
* A promise for the next time a page connects (a page that reloaded or moved opens a new stream). Ask for it BEFORE the call that makes the page leave, or a fast page is missed.
|
|
41
|
+
* @returns whether a page connected within `timeoutMs`.
|
|
42
|
+
*/
|
|
43
|
+
expectAttach(timeoutMs: number, count?: number): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Ask every connected page to reload (the app just changed under them) and wait, up to `timeoutMs`, for them to come back. Best effort: a page that does not come back
|
|
46
|
+
* (it was closed, or it moved to a page without the door) is not an error here.
|
|
47
|
+
* @returns how many pages were asked.
|
|
48
|
+
*/
|
|
49
|
+
reloadAll(timeoutMs?: number): Promise<number>;
|
|
37
50
|
/** Close every page's stream and settle every pending call as `unloaded`. */
|
|
38
51
|
close(): void;
|
|
39
52
|
private byRecency;
|
package/lib/cordis/door.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as UiChannel, t as AuditLog } from "../audit-
|
|
1
|
+
import { f as UiActionResult, y as UiSnapshot } from "../protocol-C37gPePD.js";
|
|
2
|
+
import { i as UiChannel, t as AuditLog } from "../audit-D5z9uwOP.js";
|
|
3
3
|
import { Context } from "@deepseek-ai/cordis";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
|
|
@@ -43,6 +43,10 @@ interface DoorDeps {
|
|
|
43
43
|
readonly tools: () => ToolsPort | undefined;
|
|
44
44
|
/** The app tools the door offers; anything else is refused before it reaches the registry. */
|
|
45
45
|
readonly expose: readonly string[];
|
|
46
|
+
/** Reload the open pages after a plugin was added, removed or reloaded, so the page an agent looks at next is the new one. Default: true. */
|
|
47
|
+
readonly reloadOnChange?: boolean;
|
|
48
|
+
/** How long a reload or a move may take before the new page has connected again (tests shorten it). Default 15 s. */
|
|
49
|
+
readonly pageBackMs?: number;
|
|
46
50
|
}
|
|
47
51
|
/** What one verb answered: the value for `--json` and programs, the text for an agent reading a terminal. */
|
|
48
52
|
interface VerbAnswer {
|
|
@@ -158,6 +162,8 @@ interface Config {
|
|
|
158
162
|
* run it in production (a packaged desktop app whose user drives it with an agent) says so here; `ACRYL_DOOR=off` still wins.
|
|
159
163
|
*/
|
|
160
164
|
allowInProduction?: boolean;
|
|
165
|
+
/** 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. */
|
|
166
|
+
reloadOnChange?: boolean;
|
|
161
167
|
}
|
|
162
168
|
declare const Config: z<Config>;
|
|
163
169
|
declare function apply(ctx: Context, rawConfig?: Config): void;
|
package/lib/cordis/door.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { t as Acryl } from "../app-BM0rTCp7.js";
|
|
2
2
|
import { n as clientModules } from "../client-modules-CLvWOjbd.js";
|
|
3
3
|
import { MAX_SNAPSHOT_NODES, MAX_WAIT_MS, UiControlError, parseUiRequest } from "../door/protocol.js";
|
|
4
|
-
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-
|
|
5
|
-
import "../src-
|
|
4
|
+
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-BLz8nlSc.js";
|
|
5
|
+
import "../src-dy3Fk9tA.js";
|
|
6
6
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
7
7
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
import z from "@deepseek-ai/schemastery";
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
10
11
|
//#region ../../plugins/acryl-door/src/host/verbs.ts
|
|
11
12
|
/**
|
|
12
13
|
* The one verb list (spec 049 slice 13). Every face of the door reads it: the operator client (`acryl <verb>`, its `verbs` and `brief`),
|
|
@@ -56,8 +57,8 @@ const VERB_SPECS = {
|
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
"act": {
|
|
59
|
-
usage: "act click|type|select|press|scroll|wait ...: do one thing on the page (refs from the latest look)",
|
|
60
|
-
description: "Do one thing on the page: click, type, select, press a key, scroll,
|
|
60
|
+
usage: "act click|type|select|press|scroll|wait|reload|navigate ...: do one thing on the page (refs from the latest look)",
|
|
61
|
+
description: "Do one thing on the page: click, type, select, press a key, scroll, wait for text or a control, reload the page, or navigate to another path of the app. Look again before the next act.",
|
|
61
62
|
arguments: {
|
|
62
63
|
type: "object",
|
|
63
64
|
properties: {
|
|
@@ -69,9 +70,15 @@ const VERB_SPECS = {
|
|
|
69
70
|
"select",
|
|
70
71
|
"press",
|
|
71
72
|
"scroll",
|
|
72
|
-
"wait"
|
|
73
|
+
"wait",
|
|
74
|
+
"reload",
|
|
75
|
+
"navigate"
|
|
73
76
|
]
|
|
74
77
|
},
|
|
78
|
+
path: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "navigate: a path of this app, e.g. /demo (the page must load the door script to be controlled afterwards)"
|
|
81
|
+
},
|
|
75
82
|
ref: {
|
|
76
83
|
type: "string",
|
|
77
84
|
description: "a ref from the latest look, e.g. 3.12"
|
|
@@ -211,6 +218,8 @@ const SELF_KNOWLEDGE = [
|
|
|
211
218
|
"docs",
|
|
212
219
|
"plugins"
|
|
213
220
|
];
|
|
221
|
+
/** How long a reload or a move may take before the new page has connected to the door again. */
|
|
222
|
+
const PAGE_BACK_MS = 15e3;
|
|
214
223
|
var Door = class {
|
|
215
224
|
deps;
|
|
216
225
|
constructor(deps) {
|
|
@@ -235,7 +244,7 @@ var Door = class {
|
|
|
235
244
|
text: `done${result.target === void 0 ? "" : `: ${result.target.role} "${result.target.name}"`}${result.detail === void 0 ? "" : ` (${result.detail})`}; look again before the next act`
|
|
236
245
|
};
|
|
237
246
|
}
|
|
238
|
-
case "plugin-list": return this.plugin((extensions) => extensions.list());
|
|
247
|
+
case "plugin-list": return this.plugin((extensions) => extensions.list(), false);
|
|
239
248
|
case "plugin-add": return this.plugin((extensions) => extensions.install(this.insideApp(input.folder)));
|
|
240
249
|
case "plugin-remove": {
|
|
241
250
|
const name = input.name;
|
|
@@ -286,9 +295,26 @@ var Door = class {
|
|
|
286
295
|
});
|
|
287
296
|
return this.callPage("look", request, signal);
|
|
288
297
|
}
|
|
289
|
-
act(input, signal) {
|
|
298
|
+
async act(input, signal) {
|
|
290
299
|
if (input.op === "snapshot") throw new DoorError("invalid", "use look to see the page");
|
|
291
|
-
|
|
300
|
+
const request = this.parse(input);
|
|
301
|
+
if (request.op !== "reload" && request.op !== "navigate") return await this.callPage(`act ${String(input.op)}`, request, signal);
|
|
302
|
+
const back = this.deps.channel.expectAttach(this.deps.pageBackMs ?? PAGE_BACK_MS);
|
|
303
|
+
let answer;
|
|
304
|
+
try {
|
|
305
|
+
answer = await this.callPage(`act ${request.op}`, request, signal);
|
|
306
|
+
} catch (cause) {
|
|
307
|
+
if (!(cause instanceof UiControlError && cause.code === "disconnected")) throw cause;
|
|
308
|
+
answer = {
|
|
309
|
+
ok: true,
|
|
310
|
+
detail: request.op === "reload" ? "reloading" : `going to ${request.path}`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (!await back) 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?`);
|
|
314
|
+
return {
|
|
315
|
+
...answer,
|
|
316
|
+
detail: `${answer.detail ?? "done"}; the page is back: look again`
|
|
317
|
+
};
|
|
292
318
|
}
|
|
293
319
|
parse(input) {
|
|
294
320
|
try {
|
|
@@ -335,10 +361,12 @@ var Door = class {
|
|
|
335
361
|
throw cause;
|
|
336
362
|
}
|
|
337
363
|
}
|
|
338
|
-
async plugin(run) {
|
|
364
|
+
async plugin(run, mutates = true) {
|
|
339
365
|
const extensions = this.deps.extensions();
|
|
340
366
|
if (extensions === void 0) throw new DoorError("unavailable", "this app has no extensions plugin, so plugins cannot be installed while it runs");
|
|
341
367
|
const value = await run(extensions);
|
|
368
|
+
const unchanged = typeof value === "object" && value !== null && value.action === "unchanged";
|
|
369
|
+
if (mutates && !unchanged && this.deps.reloadOnChange !== false) await this.deps.channel.reloadAll();
|
|
342
370
|
return {
|
|
343
371
|
value,
|
|
344
372
|
text: JSON.stringify(value, null, 2)
|
|
@@ -631,6 +659,34 @@ function operatorRoutes(prefix, secret, door, reportError) {
|
|
|
631
659
|
}
|
|
632
660
|
});
|
|
633
661
|
}
|
|
662
|
+
/** The oldest Node the client runs on (the framework's own floor). */
|
|
663
|
+
const MIN_NODE = [22, 19];
|
|
664
|
+
/**
|
|
665
|
+
* A real Node for the command, when the app runs inside Electron: Electron can run the client as Node, but on macOS every such call prints a code-signing complaint on stderr,
|
|
666
|
+
* which lands in an agent's transcript. A `node` on the PATH that is new enough is used instead; without one, Electron runs the client. `find` is replaceable for tests.
|
|
667
|
+
*/
|
|
668
|
+
function realNode(find = findNodeOnPath) {
|
|
669
|
+
const found = find();
|
|
670
|
+
if (found === void 0) return void 0;
|
|
671
|
+
const [major = 0, minor = 0] = found.version.replace(/^v/u, "").split(".").map(Number);
|
|
672
|
+
return major > MIN_NODE[0] || major === MIN_NODE[0] && minor >= MIN_NODE[1] ? found.path : void 0;
|
|
673
|
+
}
|
|
674
|
+
function findNodeOnPath() {
|
|
675
|
+
const located = spawnSync(process.platform === "win32" ? "where" : "which", ["node"], {
|
|
676
|
+
encoding: "utf8",
|
|
677
|
+
env: {
|
|
678
|
+
...process.env,
|
|
679
|
+
ELECTRON_RUN_AS_NODE: void 0
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
const path = located.status === 0 ? located.stdout.split(/\r?\n/u).find((line) => line.trim() !== "")?.trim() : void 0;
|
|
683
|
+
if (path === void 0) return void 0;
|
|
684
|
+
const asked = spawnSync(path, ["--version"], { encoding: "utf8" });
|
|
685
|
+
return asked.status === 0 ? {
|
|
686
|
+
path,
|
|
687
|
+
version: asked.stdout.trim()
|
|
688
|
+
} : void 0;
|
|
689
|
+
}
|
|
634
690
|
/** A word for `sh`: single-quoted, with each `'` closed, escaped and reopened. */
|
|
635
691
|
function shWord(value) {
|
|
636
692
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
@@ -698,6 +754,7 @@ const inject = [
|
|
|
698
754
|
];
|
|
699
755
|
const Config = z.object({
|
|
700
756
|
allowInProduction: z.boolean().default(false).description("Run in production too (NODE_ENV=production); ACRYL_DOOR=off still turns it off."),
|
|
757
|
+
reloadOnChange: z.boolean().default(true).description("Reload the open pages after a plugin was added, removed or reloaded while the app runs."),
|
|
701
758
|
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.") })
|
|
702
759
|
});
|
|
703
760
|
/** The page module, served compiled from source by the app's own TypeScript. */
|
|
@@ -707,11 +764,13 @@ const CLIENT = import.meta.url.endsWith(".ts") ? join(import.meta.dirname, "cli"
|
|
|
707
764
|
/** How the folder's `.acryl/bin/acryl` starts the client: with the Node that runs the app, and its type-stripping flag when run from source. */
|
|
708
765
|
function shimTarget() {
|
|
709
766
|
const fromSource = CLIENT.endsWith(".ts");
|
|
767
|
+
const inElectron = process.versions.electron !== void 0;
|
|
768
|
+
const node = inElectron ? realNode() : void 0;
|
|
710
769
|
return {
|
|
711
|
-
node: process.execPath,
|
|
770
|
+
node: node ?? process.execPath,
|
|
712
771
|
nodeOptions: fromSource ? process.execArgv.filter((option) => /^--experimental-(strip|transform)-types$/u.test(option)) : [],
|
|
713
772
|
client: CLIENT,
|
|
714
|
-
electron:
|
|
773
|
+
electron: inElectron && node === void 0
|
|
715
774
|
};
|
|
716
775
|
}
|
|
717
776
|
/** A listening address as one the operator reaches: an any-address bind is reached on loopback. */
|
|
@@ -741,12 +800,29 @@ function apply(ctx, rawConfig = {}) {
|
|
|
741
800
|
},
|
|
742
801
|
extensions: () => ctx.get("extensions"),
|
|
743
802
|
tools: () => ctx.get("tools"),
|
|
744
|
-
expose: config.tools?.expose ?? []
|
|
803
|
+
expose: config.tools?.expose ?? [],
|
|
804
|
+
reloadOnChange: config.reloadOnChange !== false
|
|
745
805
|
});
|
|
746
806
|
ctx.effect(() => () => {
|
|
747
807
|
channel.close();
|
|
748
808
|
}, "acryl-door: page channel");
|
|
749
809
|
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 }));
|
|
810
|
+
const tag = `<script type="module" src="${DOOR_PREFIX}/page.js"><\/script>`;
|
|
811
|
+
const addScript = async (c, next) => {
|
|
812
|
+
await next();
|
|
813
|
+
if (!(c.res.headers.get("content-type") ?? "").includes("text/html")) return;
|
|
814
|
+
const html = await c.res.text();
|
|
815
|
+
const headers = new Headers(c.res.headers);
|
|
816
|
+
headers.delete("content-length");
|
|
817
|
+
const carries = html.includes(`${DOOR_PREFIX}/page.js`);
|
|
818
|
+
c.res = new Response(carries ? html : html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag, {
|
|
819
|
+
status: c.res.status,
|
|
820
|
+
headers
|
|
821
|
+
});
|
|
822
|
+
};
|
|
823
|
+
const router = ctx.router;
|
|
824
|
+
if (typeof router.wrap === "function") router.wrap(addScript);
|
|
825
|
+
else ctx.router.route("/", new Acryl().use("*", addScript));
|
|
750
826
|
ctx.router.route("/", privateApi(DOOR_PREFIX, pageModule, pageChannelRoutes(channel, { prefix: DOOR_PREFIX }), operatorRoutes(DOOR_PREFIX, secret, door, reportError), mcpRoutes(DOOR_PREFIX, secret, door, reportError)));
|
|
751
827
|
ctx.inject(["tools"], (inner) => {
|
|
752
828
|
inner.effect(() => registerChatTools(inner.get("tools"), door), "acryl-door: the verbs in the tools registry");
|
package/lib/cordis/inspect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Acryl } from "../app-BM0rTCp7.js";
|
|
2
2
|
import { loopbackOnly } from "../loopback-only.js";
|
|
3
|
-
import "../src-
|
|
3
|
+
import "../src-dy3Fk9tA.js";
|
|
4
4
|
import "../middleware-CPQ8himN.js";
|
|
5
5
|
import { Service } from "@deepseek-ai/cordis";
|
|
6
6
|
//#region ../../plugins/acryl-inspect/src/report.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as UpgradeWebSocket } from "../index-C-SklY6u.js";
|
|
2
|
-
import { i as RouterHttpService } from "../index-
|
|
2
|
+
import { i as RouterHttpService } from "../index-DMOtA-NJ.js";
|
|
3
3
|
import { IncomingMessage, ServerResponse } from "node:http";
|
|
4
4
|
import { Duplex } from "node:stream";
|
|
5
5
|
import { Context, Service } from "@deepseek-ai/cordis";
|
package/lib/cordis/router.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { B as NotFoundHandler, L as MiddlewareHandler, O as Handler, Q as Valida
|
|
|
2
2
|
import { c as StatusCode, n as ContentfulStatusCode } from "../http-status-DaAwGAFL.js";
|
|
3
3
|
import { a as getConnInfo, i as connectionOf, n as RouterBindings, r as RouterEnv, t as ConnectionInfo } from "../connection-ZLEso7XL.js";
|
|
4
4
|
import { t as Acryl } from "../app-D8riatvv.js";
|
|
5
|
-
import { a as TrailingSlash, i as RouterHttpService, n as MountableApp, o as apply, r as Router, s as name, t as Config } from "../index-
|
|
5
|
+
import { a as TrailingSlash, i as RouterHttpService, n as MountableApp, o as apply, r as Router, s as name, t as Config } from "../index-DMOtA-NJ.js";
|
|
6
6
|
import { t as HTTPException } from "../http-exception-kpGJkmpi.js";
|
|
7
7
|
export { Acryl, type AcrylRequest, Config, type ConnectionInfo, type ContentfulStatusCode, Context, type ContextRenderer, type ContextVariableMap, type Env, type ErrorHandler, type ExecutionContext, HTTPException, type Handler, type Input, type MiddlewareHandler, type MountableApp, type Next, type NotFoundHandler, type NotFoundResponse, Router, type RouterBindings, type RouterEnv, type RouterHttpService, type Schema, type StatusCode, type ToSchema, type TrailingSlash, type TypedResponse, type ValidationTargets, apply, connectionOf, getConnInfo, name };
|
package/lib/cordis/router.js
CHANGED
|
@@ -2,5 +2,5 @@ import { HTTPException } from "../http-exception.js";
|
|
|
2
2
|
import { t as Context } from "../context-DDO0cfuP.js";
|
|
3
3
|
import { t as Acryl } from "../app-BM0rTCp7.js";
|
|
4
4
|
import { n as getConnInfo, t as connectionOf } from "../connection-C7xgP1hP.js";
|
|
5
|
-
import { i as name, n as Router, r as apply, t as Config } from "../src-
|
|
5
|
+
import { i as name, n as Router, r as apply, t as Config } from "../src-dy3Fk9tA.js";
|
|
6
6
|
export { Acryl, Config, Context, HTTPException, Router, apply, connectionOf, getConnInfo, name };
|
package/lib/cordis/state.js
CHANGED
|
@@ -3,7 +3,7 @@ import { methodNotAllowed } from "../method-not-allowed.js";
|
|
|
3
3
|
import { r as streamSSE } from "../streaming-DHST3Scg.js";
|
|
4
4
|
import { loopbackOnly } from "../loopback-only.js";
|
|
5
5
|
import "../helpers-CvTauQT5.js";
|
|
6
|
-
import "../src-
|
|
6
|
+
import "../src-dy3Fk9tA.js";
|
|
7
7
|
import "../middleware-CPQ8himN.js";
|
|
8
8
|
import { Service } from "@deepseek-ai/cordis";
|
|
9
9
|
//#region ../../plugins/acryl-state/src/cell.ts
|
package/lib/door/host.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Acryl } from "../app-D8riatvv.js";
|
|
2
|
-
import {
|
|
3
|
-
import { i as UiChannel, n as auditPath, r as PageStream, t as AuditLog } from "../audit-
|
|
2
|
+
import { n as AuditEntry, y as UiSnapshot } from "../protocol-C37gPePD.js";
|
|
3
|
+
import { i as UiChannel, n as auditPath, r as PageStream, t as AuditLog } from "../audit-D5z9uwOP.js";
|
|
4
4
|
|
|
5
5
|
//#region ../../plugins/acryl-door/src/host/page-routes.d.ts
|
|
6
6
|
/** How often the Host writes a `ping`; the page gives up on a stream that is silent for three of these. */
|
package/lib/door/host.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-
|
|
1
|
+
import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-BLz8nlSc.js";
|
|
2
2
|
export { AuditLog, HEARTBEAT_MS, UiChannel, auditPath, pageChannelRoutes, renderSnapshot };
|
package/lib/door/page.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as UiRequest, v as UiResult } from "../protocol-C37gPePD.js";
|
|
2
2
|
|
|
3
3
|
//#region ../../plugins/acryl-door/src/page/driver/driver.d.ts
|
|
4
4
|
interface DriverActivity {
|
|
@@ -14,6 +14,12 @@ interface UiDriverOptions {
|
|
|
14
14
|
/** Milliseconds since the user last used the page (mouse, keyboard); Infinity when never. */
|
|
15
15
|
readonly msSinceUserInput?: () => number;
|
|
16
16
|
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
17
|
+
/** Leave the page: reload it, or go to a path of the app. Default: the document's own `location`, a moment after the answer is sent. */
|
|
18
|
+
readonly leave?: (to: {
|
|
19
|
+
readonly reload: true;
|
|
20
|
+
} | {
|
|
21
|
+
readonly path: string;
|
|
22
|
+
}) => void;
|
|
17
23
|
}
|
|
18
24
|
declare class UiDriver {
|
|
19
25
|
private readonly refs;
|
|
@@ -40,6 +46,11 @@ declare class UiDriver {
|
|
|
40
46
|
/** @param signal - the Host cancelled the call. @throws UiControlError. */
|
|
41
47
|
handle(request: UiRequest, signal?: AbortSignal): Promise<UiResult>;
|
|
42
48
|
private run;
|
|
49
|
+
/**
|
|
50
|
+
* The page is about to be replaced, so the answer is given first and the move happens a moment later (the answer is a POST that the page's own unload would cut off).
|
|
51
|
+
* The Host does not wait for this answer to be the last word: it waits for the page to come back.
|
|
52
|
+
*/
|
|
53
|
+
private leave;
|
|
43
54
|
private yieldToUser;
|
|
44
55
|
private emit;
|
|
45
56
|
}
|
package/lib/door/page.js
CHANGED
|
@@ -894,8 +894,29 @@ var UiDriver = class {
|
|
|
894
894
|
case "press": return pressKey(this.env, request);
|
|
895
895
|
case "scroll": return scroll(this.env, request);
|
|
896
896
|
case "wait": return wait(this.env, request, signal);
|
|
897
|
+
case "reload": return this.leave({ reload: true }, "reloading");
|
|
898
|
+
case "navigate": return this.leave({ path: request.path }, `going to ${request.path}`);
|
|
897
899
|
}
|
|
898
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* The page is about to be replaced, so the answer is given first and the move happens a moment later (the answer is a POST that the page's own unload would cut off).
|
|
903
|
+
* The Host does not wait for this answer to be the last word: it waits for the page to come back.
|
|
904
|
+
*/
|
|
905
|
+
leave(to, detail) {
|
|
906
|
+
const go = this.options.leave ?? ((target) => {
|
|
907
|
+
const location = this.options.document().defaultView?.location;
|
|
908
|
+
if (location === void 0) return;
|
|
909
|
+
if ("reload" in target) location.reload();
|
|
910
|
+
else location.assign(target.path);
|
|
911
|
+
});
|
|
912
|
+
setTimeout(() => {
|
|
913
|
+
if (!this.disposed) go(to);
|
|
914
|
+
}, 150);
|
|
915
|
+
return {
|
|
916
|
+
ok: true,
|
|
917
|
+
detail
|
|
918
|
+
};
|
|
919
|
+
}
|
|
899
920
|
async yieldToUser(signal) {
|
|
900
921
|
const since = this.options.msSinceUserInput;
|
|
901
922
|
if (since === void 0) return;
|
package/lib/door/protocol.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { AuditEntry, ChannelToHost, ChannelToPage, DEFAULT_SNAPSHOT_NODES, MAX_SNAPSHOT_NODES, MAX_TEXT_LENGTH, MAX_WAIT_MS, MUTATING_OPS, UI_ERROR_CODES, UI_OPS, UiActionResult, UiControlError, UiErrorCode, UiNode, UiOp, UiRequest, UiResult, UiSnapshot, WINDOW_ID, parseChannelToHost, parseChannelToPage, parseUiRequest };
|
|
1
|
+
import { C as parseUiRequest, S as parseChannelToPage, _ as UiRequest, a as DEFAULT_SNAPSHOT_NODES, b as WINDOW_ID, c as MAX_WAIT_MS, d as UI_OPS, f as UiActionResult, g as UiOp, h as UiNode, i as ChannelToPage, l as MUTATING_OPS, m as UiErrorCode, n as AuditEntry, o as MAX_SNAPSHOT_NODES, p as UiControlError, r as ChannelToHost, s as MAX_TEXT_LENGTH, t as APP_PATH, u as UI_ERROR_CODES, v as UiResult, x as parseChannelToHost, y as UiSnapshot } from "../protocol-C37gPePD.js";
|
|
2
|
+
export { APP_PATH, AuditEntry, ChannelToHost, ChannelToPage, DEFAULT_SNAPSHOT_NODES, MAX_SNAPSHOT_NODES, MAX_TEXT_LENGTH, MAX_WAIT_MS, MUTATING_OPS, UI_ERROR_CODES, UI_OPS, UiActionResult, UiControlError, UiErrorCode, UiNode, UiOp, UiRequest, UiResult, UiSnapshot, WINDOW_ID, parseChannelToHost, parseChannelToPage, parseUiRequest };
|
package/lib/door/protocol.js
CHANGED
|
@@ -14,15 +14,21 @@ const UI_OPS = [
|
|
|
14
14
|
"select",
|
|
15
15
|
"press",
|
|
16
16
|
"scroll",
|
|
17
|
-
"wait"
|
|
17
|
+
"wait",
|
|
18
|
+
"reload",
|
|
19
|
+
"navigate"
|
|
18
20
|
];
|
|
19
21
|
/** Operations that change the page, so each call needs approval. `snapshot`, `scroll` and `wait` only look. */
|
|
20
22
|
const MUTATING_OPS = [
|
|
21
23
|
"click",
|
|
22
24
|
"type",
|
|
23
25
|
"select",
|
|
24
|
-
"press"
|
|
26
|
+
"press",
|
|
27
|
+
"reload",
|
|
28
|
+
"navigate"
|
|
25
29
|
];
|
|
30
|
+
/** A place in the app to go to: a path on the page's own origin (`/demo`, `/notes?q=a#top`), never another site and never a `//host` address. */
|
|
31
|
+
const APP_PATH = /^\/(?![/\\])[^\0\\]{0,2000}$/u;
|
|
26
32
|
const MAX_TEXT_LENGTH = 1e4;
|
|
27
33
|
const MAX_WAIT_MS = 1e4;
|
|
28
34
|
const MAX_SNAPSHOT_NODES = 1e3;
|
|
@@ -142,6 +148,16 @@ function parseUiRequest(value) {
|
|
|
142
148
|
...timeoutMs === void 0 ? {} : { timeoutMs }
|
|
143
149
|
};
|
|
144
150
|
}
|
|
151
|
+
case "reload":
|
|
152
|
+
only([]);
|
|
153
|
+
return { op };
|
|
154
|
+
case "navigate":
|
|
155
|
+
only(["path"]);
|
|
156
|
+
if (typeof value.path !== "string" || !APP_PATH.test(value.path)) return invalid("path must be a path of this app, such as /demo (it starts with one /, and names no other site)");
|
|
157
|
+
return {
|
|
158
|
+
op,
|
|
159
|
+
path: value.path
|
|
160
|
+
};
|
|
145
161
|
default: return invalid(`unknown op ${String(op)}`);
|
|
146
162
|
}
|
|
147
163
|
}
|
|
@@ -194,4 +210,4 @@ function parseChannelToHost(value) {
|
|
|
194
210
|
return null;
|
|
195
211
|
}
|
|
196
212
|
//#endregion
|
|
197
|
-
export { DEFAULT_SNAPSHOT_NODES, MAX_SNAPSHOT_NODES, MAX_TEXT_LENGTH, MAX_WAIT_MS, MUTATING_OPS, UI_ERROR_CODES, UI_OPS, UiControlError, WINDOW_ID, parseChannelToHost, parseChannelToPage, parseUiRequest };
|
|
213
|
+
export { APP_PATH, DEFAULT_SNAPSHOT_NODES, MAX_SNAPSHOT_NODES, MAX_TEXT_LENGTH, MAX_WAIT_MS, MUTATING_OPS, UI_ERROR_CODES, UI_OPS, UiControlError, WINDOW_ID, parseChannelToHost, parseChannelToPage, parseUiRequest };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as NotFoundHandler, y as ErrorHandler } from "./context-DS3fYyG-.js";
|
|
1
|
+
import { B as NotFoundHandler, E as H, y as ErrorHandler } from "./context-DS3fYyG-.js";
|
|
2
2
|
import { t as ConnectionInfo } from "./connection-ZLEso7XL.js";
|
|
3
3
|
import { n as UpgradeWebSocket } from "./index-C-SklY6u.js";
|
|
4
4
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
@@ -77,6 +77,12 @@ declare class Router extends Service {
|
|
|
77
77
|
* are copied now: one the app registers after this call is not served (mount again, or finish the app first).
|
|
78
78
|
*/
|
|
79
79
|
route(path: string, app: MountableApp): () => void;
|
|
80
|
+
/**
|
|
81
|
+
* Run `middleware` around every request the router serves, whenever the plugin that asks was mounted: a middleware in a plain mount wraps only the routes mounted after it,
|
|
82
|
+
* which is the wrong tool for a plugin that must see every page (the door adds its script to each HTML response, the pages of a plugin that loaded earlier included). It goes
|
|
83
|
+
* outside all mounts, in the order wrappers were added, and goes away when the calling plugin unloads or when the returned function is called.
|
|
84
|
+
*/
|
|
85
|
+
wrap(middleware: H): () => void;
|
|
80
86
|
/**
|
|
81
87
|
* The mount's own copy of `app`'s routes as they are now (like the core's `app.route`), each handler wrapped so the router knows which
|
|
82
88
|
* mount's code a request is in. The core's `app.route` runs first so a sub-app's own `onError` still applies to its routes.
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
* lives with the page code and has no Node imports.
|
|
8
8
|
*/
|
|
9
9
|
/** The operations the driver performs on the page. */
|
|
10
|
-
declare const UI_OPS: readonly ["snapshot", "click", "type", "select", "press", "scroll", "wait"];
|
|
10
|
+
declare const UI_OPS: readonly ["snapshot", "click", "type", "select", "press", "scroll", "wait", "reload", "navigate"];
|
|
11
11
|
type UiOp = (typeof UI_OPS)[number];
|
|
12
12
|
/** Operations that change the page, so each call needs approval. `snapshot`, `scroll` and `wait` only look. */
|
|
13
13
|
declare const MUTATING_OPS: readonly UiOp[];
|
|
14
|
+
/** A place in the app to go to: a path on the page's own origin (`/demo`, `/notes?q=a#top`), never another site and never a `//host` address. */
|
|
15
|
+
declare const APP_PATH: RegExp;
|
|
14
16
|
declare const MAX_TEXT_LENGTH = 10000;
|
|
15
17
|
declare const MAX_WAIT_MS = 10000;
|
|
16
18
|
declare const MAX_SNAPSHOT_NODES = 1000;
|
|
@@ -77,6 +79,11 @@ type UiRequest = {
|
|
|
77
79
|
readonly name?: string;
|
|
78
80
|
readonly gone?: boolean;
|
|
79
81
|
readonly timeoutMs?: number;
|
|
82
|
+
} | {
|
|
83
|
+
readonly op: 'reload';
|
|
84
|
+
} | {
|
|
85
|
+
readonly op: 'navigate';
|
|
86
|
+
readonly path: string;
|
|
80
87
|
};
|
|
81
88
|
/** What the page did, and to which control (for the audit log and the approval prompt). */
|
|
82
89
|
interface UiActionResult {
|
|
@@ -141,4 +148,4 @@ interface AuditEntry {
|
|
|
141
148
|
readonly approval: 'asked' | 'none' | 'not-needed' | 'token';
|
|
142
149
|
}
|
|
143
150
|
//#endregion
|
|
144
|
-
export { parseUiRequest as S,
|
|
151
|
+
export { parseUiRequest as C, parseChannelToPage as S, UiRequest as _, DEFAULT_SNAPSHOT_NODES as a, WINDOW_ID as b, MAX_WAIT_MS as c, UI_OPS as d, UiActionResult as f, UiOp as g, UiNode as h, ChannelToPage as i, MUTATING_OPS as l, UiErrorCode as m, AuditEntry as n, MAX_SNAPSHOT_NODES as o, UiControlError as p, ChannelToHost as r, MAX_TEXT_LENGTH as s, APP_PATH as t, UI_ERROR_CODES as u, UiResult as v, parseChannelToHost as x, UiSnapshot as y };
|
|
@@ -5,7 +5,7 @@ import { r as streamSSE } from "./streaming-DHST3Scg.js";
|
|
|
5
5
|
import { loopbackAuthority, loopbackOnly } from "./loopback-only.js";
|
|
6
6
|
import "./helpers-CvTauQT5.js";
|
|
7
7
|
import { UiControlError, WINDOW_ID, parseChannelToHost } from "./door/protocol.js";
|
|
8
|
-
import "./src-
|
|
8
|
+
import "./src-dy3Fk9tA.js";
|
|
9
9
|
import "./middleware-CPQ8himN.js";
|
|
10
10
|
import { dirname, join } from "node:path";
|
|
11
11
|
import { closeSync, mkdirSync, openSync, readFileSync, renameSync, statSync, writeSync } from "node:fs";
|
|
@@ -26,6 +26,8 @@ var UiChannel = class {
|
|
|
26
26
|
nextId = 1;
|
|
27
27
|
clock = 0;
|
|
28
28
|
closed = false;
|
|
29
|
+
/** Callers waiting for a page to (re)connect: each is told once, by the next stream that attaches. */
|
|
30
|
+
attachWaiters = /* @__PURE__ */ new Set();
|
|
29
31
|
/**
|
|
30
32
|
* Register a page's stream. A stream already open for the same window is closed first, and its calls settle as `disconnected`.
|
|
31
33
|
* @returns the function to call when this stream ends.
|
|
@@ -40,6 +42,7 @@ var UiChannel = class {
|
|
|
40
42
|
lastActive: ++this.clock
|
|
41
43
|
};
|
|
42
44
|
this.connections.set(windowId, connection);
|
|
45
|
+
for (const waiter of [...this.attachWaiters]) waiter();
|
|
43
46
|
return () => {
|
|
44
47
|
if (this.connections.get(windowId) === connection) this.drop(connection, false);
|
|
45
48
|
};
|
|
@@ -110,6 +113,51 @@ var UiChannel = class {
|
|
|
110
113
|
}
|
|
111
114
|
});
|
|
112
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* A promise for the next time a page connects (a page that reloaded or moved opens a new stream). Ask for it BEFORE the call that makes the page leave, or a fast page is missed.
|
|
118
|
+
* @returns whether a page connected within `timeoutMs`.
|
|
119
|
+
*/
|
|
120
|
+
expectAttach(timeoutMs, count = 1) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
let left = count;
|
|
123
|
+
const done = (value) => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
this.attachWaiters.delete(waiter);
|
|
126
|
+
resolve(value);
|
|
127
|
+
};
|
|
128
|
+
const waiter = () => {
|
|
129
|
+
left -= 1;
|
|
130
|
+
if (left <= 0) done(true);
|
|
131
|
+
};
|
|
132
|
+
const timer = setTimeout(() => {
|
|
133
|
+
done(false);
|
|
134
|
+
}, timeoutMs);
|
|
135
|
+
this.attachWaiters.add(waiter);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ask every connected page to reload (the app just changed under them) and wait, up to `timeoutMs`, for them to come back. Best effort: a page that does not come back
|
|
140
|
+
* (it was closed, or it moved to a page without the door) is not an error here.
|
|
141
|
+
* @returns how many pages were asked.
|
|
142
|
+
*/
|
|
143
|
+
async reloadAll(timeoutMs = 8e3) {
|
|
144
|
+
const targets = this.byRecency();
|
|
145
|
+
if (this.closed || targets.length === 0) return 0;
|
|
146
|
+
const back = this.expectAttach(timeoutMs, targets.length);
|
|
147
|
+
for (const target of targets) {
|
|
148
|
+
const id = this.nextId;
|
|
149
|
+
this.nextId += 1;
|
|
150
|
+
try {
|
|
151
|
+
target.stream.send(JSON.stringify({
|
|
152
|
+
t: "call",
|
|
153
|
+
id,
|
|
154
|
+
request: { op: "reload" }
|
|
155
|
+
}));
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
158
|
+
await back;
|
|
159
|
+
return targets.length;
|
|
160
|
+
}
|
|
113
161
|
/** Close every page's stream and settle every pending call as `unloaded`. */
|
|
114
162
|
close() {
|
|
115
163
|
this.closed = true;
|
|
@@ -39,6 +39,7 @@ var Router = class extends Service {
|
|
|
39
39
|
/** Everything that changes after construction, kept in one object so the service's live state is in one place. */
|
|
40
40
|
state = {
|
|
41
41
|
mounts: /* @__PURE__ */ new Set(),
|
|
42
|
+
wrappers: /* @__PURE__ */ new Set(),
|
|
42
43
|
errorHandler: void 0,
|
|
43
44
|
notFoundHandler: void 0,
|
|
44
45
|
composed: void 0,
|
|
@@ -95,6 +96,20 @@ var Router = class extends Service {
|
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
99
|
+
* Run `middleware` around every request the router serves, whenever the plugin that asks was mounted: a middleware in a plain mount wraps only the routes mounted after it,
|
|
100
|
+
* which is the wrong tool for a plugin that must see every page (the door adds its script to each HTML response, the pages of a plugin that loaded earlier included). It goes
|
|
101
|
+
* outside all mounts, in the order wrappers were added, and goes away when the calling plugin unloads or when the returned function is called.
|
|
102
|
+
*/
|
|
103
|
+
wrap(middleware) {
|
|
104
|
+
this.state.wrappers.add(middleware);
|
|
105
|
+
this.state.composed = void 0;
|
|
106
|
+
const remove = () => {
|
|
107
|
+
if (this.state.wrappers.delete(middleware)) this.state.composed = void 0;
|
|
108
|
+
};
|
|
109
|
+
this.ctx.effect(() => remove, "acryl-router: wrapper");
|
|
110
|
+
return remove;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
98
113
|
* The mount's own copy of `app`'s routes as they are now (like the core's `app.route`), each handler wrapped so the router knows which
|
|
99
114
|
* mount's code a request is in. The core's `app.route` runs first so a sub-app's own `onError` still applies to its routes.
|
|
100
115
|
*/
|
|
@@ -251,6 +266,7 @@ var Router = class extends Service {
|
|
|
251
266
|
strict: this.strict,
|
|
252
267
|
router: new TrieRouter()
|
|
253
268
|
});
|
|
269
|
+
for (const wrapper of this.state.wrappers) root.use("*", wrapper);
|
|
254
270
|
for (const mount of this.state.mounts) root.route(mount.path, mount.app);
|
|
255
271
|
root.onError(this.state.errorHandler ?? this.logError);
|
|
256
272
|
if (this.state.notFoundHandler !== void 0) root.notFound(this.state.notFoundHandler);
|
package/package.json
CHANGED