acrylblends 0.8.1 → 0.9.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 (235) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +83 -7
  3. package/dist/index.js.map +1 -1
  4. package/lib/audit-BXnYsLge.d.ts +58 -0
  5. package/lib/cell-ChU41DZD.d.ts +32 -0
  6. package/lib/client-modules-CLvWOjbd.js +267 -0
  7. package/lib/connection-ZLEso7XL.d.ts +25 -0
  8. package/lib/conninfo.d.ts +1 -23
  9. package/lib/cordis/db-sqlite.d.ts +56 -0
  10. package/lib/cordis/db-sqlite.js +58 -0
  11. package/lib/cordis/db.d.ts +8 -0
  12. package/lib/cordis/db.js +1 -0
  13. package/lib/cordis/door.d.ts +159 -0
  14. package/lib/cordis/door.js +757 -0
  15. package/lib/cordis/inspect.d.ts +77 -0
  16. package/lib/cordis/inspect.js +149 -0
  17. package/lib/cordis/kv-db.d.ts +16 -0
  18. package/lib/cordis/kv-db.js +18 -0
  19. package/lib/cordis/kv-json.d.ts +16 -0
  20. package/lib/cordis/kv-json.js +44 -0
  21. package/lib/cordis/kv.d.ts +8 -0
  22. package/lib/cordis/kv.js +1 -0
  23. package/lib/cordis/node-server.d.ts +45 -0
  24. package/lib/cordis/node-server.js +93 -0
  25. package/lib/cordis/router-http.d.ts +56 -0
  26. package/lib/cordis/router-http.js +117 -0
  27. package/lib/cordis/router.d.ts +8 -0
  28. package/lib/cordis/router.js +7 -0
  29. package/lib/cordis/state/react.d.ts +6 -0
  30. package/lib/cordis/state/react.js +11 -0
  31. package/lib/cordis/state.d.ts +33 -0
  32. package/lib/cordis/state.js +163 -0
  33. package/lib/core-CaY8XDZ6.js +19 -0
  34. package/lib/database-DBHdwefi.d.ts +44 -0
  35. package/lib/db/conformance.d.ts +11 -0
  36. package/lib/db/conformance.js +267 -0
  37. package/lib/db/sqlite.d.ts +1 -1
  38. package/lib/db/sqlite.js +1 -196
  39. package/lib/db.d.ts +2 -1
  40. package/lib/{src-0hI7M7cN.js → deprecated-BP4fPt38.js} +0 -17
  41. package/lib/door/host.d.ts +24 -0
  42. package/lib/door/host.js +2 -0
  43. package/lib/door/page.d.ts +105 -0
  44. package/lib/door/page.js +1030 -0
  45. package/lib/door/protocol.d.ts +2 -0
  46. package/lib/door/protocol.js +197 -0
  47. package/lib/error-t4N2js2I.d.ts +8 -0
  48. package/lib/etag-DaKMOjty.js +135 -0
  49. package/lib/etag.js +1 -134
  50. package/lib/helpers-CvTauQT5.js +5 -0
  51. package/lib/http-exception-kpGJkmpi.d.ts +57 -0
  52. package/lib/http-exception.d.ts +1 -56
  53. package/lib/{index-BLTYsyc1.d.ts → index-BnGgRKTv.d.ts} +3 -39
  54. package/lib/index-BqVQkTnu.d.ts +98 -0
  55. package/lib/index-Dmst2QCQ.d.ts +44 -0
  56. package/lib/index.d.ts +1 -1
  57. package/lib/index.js +2 -1
  58. package/lib/jwk-LkzeoaU8.js +117 -0
  59. package/lib/jwk.js +1 -116
  60. package/lib/jwt-CskdhEW9.js +115 -0
  61. package/lib/jwt.js +2 -114
  62. package/lib/kv/conformance.d.ts +13 -0
  63. package/lib/kv/conformance.js +301 -0
  64. package/lib/kv/db.d.ts +2 -2
  65. package/lib/kv/json.d.ts +1 -1
  66. package/lib/kv-9f-AlRKN.d.ts +39 -0
  67. package/lib/kv.d.ts +2 -1
  68. package/lib/language-D9a4zqvn.js +198 -0
  69. package/lib/language.js +1 -197
  70. package/lib/middleware-CPQ8himN.js +12 -0
  71. package/lib/node-server/serve-static.d.ts +0 -1
  72. package/lib/node-server/serve-static.js +1 -1
  73. package/lib/node-server.d.ts +1 -7
  74. package/lib/node-server.js +3 -1586
  75. package/lib/open-CMhnJODn.js +197 -0
  76. package/lib/protocol-BizVt4J6.d.ts +144 -0
  77. package/lib/request-id-CKVvHOO1.js +34 -0
  78. package/lib/request-id.js +1 -33
  79. package/lib/secure-headers-mP3j6Hm6.js +163 -0
  80. package/lib/secure-headers.js +1 -162
  81. package/lib/{serve-static-B849HlX2.js → serve-static-3uo_9rjF.js} +1 -4
  82. package/lib/server-DvgMGtzo.js +1323 -0
  83. package/lib/snapshot-text-D3NspDyT.js +338 -0
  84. package/lib/src-CwABFr6r.js +328 -0
  85. package/lib/timeout.d.ts +1 -1
  86. package/lib/timing-Cky7C1Jf.js +183 -0
  87. package/lib/timing.js +1 -182
  88. package/package.json +89 -1
  89. package/plugins/acryl-db/plugin.json +6 -1
  90. package/plugins/acryl-db-sqlite/plugin.json +4 -0
  91. package/plugins/acryl-db-sqlite/src/facade/node.ts +1 -1
  92. package/plugins/acryl-db-sqlite/src/index.ts +2 -2
  93. package/plugins/acryl-db-sqlite/src/open.ts +2 -2
  94. package/plugins/acryl-door/plugin.json +4 -1
  95. package/plugins/acryl-door/src/host/http.ts +2 -2
  96. package/plugins/acryl-door/src/host/mcp-routes.ts +1 -1
  97. package/plugins/acryl-door/src/host/operator-routes.ts +1 -1
  98. package/plugins/acryl-door/src/host/page-routes.ts +3 -3
  99. package/plugins/acryl-door/src/index.ts +2 -2
  100. package/plugins/acryl-inspect/plugin.json +4 -1
  101. package/plugins/acryl-inspect/src/service.ts +2 -2
  102. package/plugins/acryl-kv/plugin.json +6 -1
  103. package/plugins/acryl-kv-db/plugin.json +4 -0
  104. package/plugins/acryl-kv-db/src/index.ts +1 -1
  105. package/plugins/acryl-kv-db/src/open.ts +3 -3
  106. package/plugins/acryl-kv-json/plugin.json +4 -0
  107. package/plugins/acryl-kv-json/src/index.ts +1 -1
  108. package/plugins/acryl-kv-json/src/open.ts +2 -2
  109. package/plugins/acryl-node-server/plugin.json +5 -1
  110. package/plugins/acryl-node-server/src/adapter/conninfo.ts +1 -1
  111. package/plugins/acryl-node-server/src/adapter/early-hints.ts +1 -1
  112. package/plugins/acryl-node-server/src/adapter/serve-static.ts +2 -2
  113. package/plugins/acryl-node-server/src/adapter/websocket.ts +2 -2
  114. package/plugins/acryl-node-server/src/client-modules.ts +2 -2
  115. package/plugins/acryl-node-server/src/index.ts +1 -1
  116. package/plugins/acryl-router/plugin.json +11 -1
  117. package/plugins/acryl-router/src/connection.d.ts +25 -0
  118. package/plugins/acryl-router/src/core/app-base.d.ts +215 -0
  119. package/plugins/acryl-router/src/core/app.d.ts +19 -0
  120. package/plugins/acryl-router/src/core/client/client.d.ts +4 -0
  121. package/plugins/acryl-router/src/core/client/fetch-result-please.d.ts +35 -0
  122. package/plugins/acryl-router/src/core/client/index.d.ts +7 -0
  123. package/plugins/acryl-router/src/core/client/types.d.ts +229 -0
  124. package/plugins/acryl-router/src/core/client/utils.d.ts +18 -0
  125. package/plugins/acryl-router/src/core/compose.d.ts +14 -0
  126. package/plugins/acryl-router/src/core/context.d.ts +443 -0
  127. package/plugins/acryl-router/src/core/helper/accepts/accepts.d.ts +30 -0
  128. package/plugins/acryl-router/src/core/helper/accepts/index.d.ts +5 -0
  129. package/plugins/acryl-router/src/core/helper/conninfo/index.d.ts +5 -0
  130. package/plugins/acryl-router/src/core/helper/conninfo/types.d.ts +36 -0
  131. package/plugins/acryl-router/src/core/helper/cookie/index.d.ts +25 -0
  132. package/plugins/acryl-router/src/core/helper/css/common.d.ts +59 -0
  133. package/plugins/acryl-router/src/core/helper/css/index.d.ts +80 -0
  134. package/plugins/acryl-router/src/core/helper/dev/index.d.ts +20 -0
  135. package/plugins/acryl-router/src/core/helper/factory/index.d.ts +82 -0
  136. package/plugins/acryl-router/src/core/helper/html/index.d.ts +8 -0
  137. package/plugins/acryl-router/src/core/helper/proxy/index.d.ts +73 -0
  138. package/plugins/acryl-router/src/core/helper/route/index.d.ts +75 -0
  139. package/plugins/acryl-router/src/core/helper/ssg/index.d.ts +7 -0
  140. package/plugins/acryl-router/src/core/helper/ssg/middleware.d.ts +44 -0
  141. package/plugins/acryl-router/src/core/helper/ssg/plugins.d.ts +27 -0
  142. package/plugins/acryl-router/src/core/helper/ssg/ssg.d.ts +91 -0
  143. package/plugins/acryl-router/src/core/helper/ssg/utils.d.ts +16 -0
  144. package/plugins/acryl-router/src/core/helper/streaming/index.d.ts +8 -0
  145. package/plugins/acryl-router/src/core/helper/streaming/sse.d.ts +13 -0
  146. package/plugins/acryl-router/src/core/helper/streaming/stream.d.ts +3 -0
  147. package/plugins/acryl-router/src/core/helper/streaming/text.d.ts +3 -0
  148. package/plugins/acryl-router/src/core/helper/streaming/utils.d.ts +1 -0
  149. package/plugins/acryl-router/src/core/helper/testing/index.d.ts +13 -0
  150. package/plugins/acryl-router/src/core/helper/websocket/index.d.ts +70 -0
  151. package/plugins/acryl-router/src/core/http-exception.d.ts +58 -0
  152. package/plugins/acryl-router/src/core/index.d.ts +37 -0
  153. package/plugins/acryl-router/src/core/jsx/base.d.ts +61 -0
  154. package/plugins/acryl-router/src/core/jsx/children.d.ts +9 -0
  155. package/plugins/acryl-router/src/core/jsx/components.d.ts +15 -0
  156. package/plugins/acryl-router/src/core/jsx/constants.d.ts +6 -0
  157. package/plugins/acryl-router/src/core/jsx/context.d.ts +51 -0
  158. package/plugins/acryl-router/src/core/jsx/dom/components.d.ts +10 -0
  159. package/plugins/acryl-router/src/core/jsx/dom/context.d.ts +3 -0
  160. package/plugins/acryl-router/src/core/jsx/dom/css.d.ts +83 -0
  161. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.d.ts +39 -0
  162. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.d.ts +16 -0
  163. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.d.ts +8 -0
  164. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.d.ts +7 -0
  165. package/plugins/acryl-router/src/core/jsx/dom/render.d.ts +73 -0
  166. package/plugins/acryl-router/src/core/jsx/dom/utils.d.ts +1 -0
  167. package/plugins/acryl-router/src/core/jsx/hooks/index.d.ts +47 -0
  168. package/plugins/acryl-router/src/core/jsx/index.d.ts +76 -0
  169. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.d.ts +6 -0
  170. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.d.ts +13 -0
  171. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.d.ts +735 -0
  172. package/plugins/acryl-router/src/core/jsx/streaming.d.ts +36 -0
  173. package/plugins/acryl-router/src/core/jsx/types.d.ts +36 -0
  174. package/plugins/acryl-router/src/core/jsx/utils.d.ts +4 -0
  175. package/plugins/acryl-router/src/core/request/constants.d.ts +1 -0
  176. package/plugins/acryl-router/src/core/request.d.ts +306 -0
  177. package/plugins/acryl-router/src/core/router/reg-exp-router/index.d.ts +6 -0
  178. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.d.ts +7 -0
  179. package/plugins/acryl-router/src/core/router/reg-exp-router/node.d.ts +13 -0
  180. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.d.ts +17 -0
  181. package/plugins/acryl-router/src/core/router/reg-exp-router/router.d.ts +11 -0
  182. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.d.ts +8 -0
  183. package/plugins/acryl-router/src/core/router/smart-router/index.d.ts +5 -0
  184. package/plugins/acryl-router/src/core/router/smart-router/router.d.ts +11 -0
  185. package/plugins/acryl-router/src/core/router/trie-router/index.d.ts +5 -0
  186. package/plugins/acryl-router/src/core/router/trie-router/node.d.ts +6 -0
  187. package/plugins/acryl-router/src/core/router/trie-router/router.d.ts +7 -0
  188. package/plugins/acryl-router/src/core/router/utils.d.ts +1 -0
  189. package/plugins/acryl-router/src/core/router.d.ts +97 -0
  190. package/plugins/acryl-router/src/core/types.d.ts +573 -0
  191. package/plugins/acryl-router/src/core/utils/accept.d.ts +6 -0
  192. package/plugins/acryl-router/src/core/utils/body.d.ts +79 -0
  193. package/plugins/acryl-router/src/core/utils/buffer.d.ts +17 -0
  194. package/plugins/acryl-router/src/core/utils/color.d.ts +21 -0
  195. package/plugins/acryl-router/src/core/utils/concurrent.d.ts +11 -0
  196. package/plugins/acryl-router/src/core/utils/constants.d.ts +4 -0
  197. package/plugins/acryl-router/src/core/utils/cookie.d.ts +41 -0
  198. package/plugins/acryl-router/src/core/utils/crypto.d.ts +15 -0
  199. package/plugins/acryl-router/src/core/utils/handler.d.ts +6 -0
  200. package/plugins/acryl-router/src/core/utils/headers.d.ts +8 -0
  201. package/plugins/acryl-router/src/core/utils/html.d.ts +44 -0
  202. package/plugins/acryl-router/src/core/utils/http-status.d.ts +32 -0
  203. package/plugins/acryl-router/src/core/utils/mime.d.ts +70 -0
  204. package/plugins/acryl-router/src/core/utils/stream.d.ts +31 -0
  205. package/plugins/acryl-router/src/core/utils/types.d.ts +128 -0
  206. package/plugins/acryl-router/src/core/utils/url.d.ts +44 -0
  207. package/plugins/acryl-router/src/core/validator/index.d.ts +7 -0
  208. package/plugins/acryl-router/src/core/validator/utils.d.ts +29 -0
  209. package/plugins/acryl-router/src/core/validator/validator.d.ts +16 -0
  210. package/plugins/acryl-router/src/helpers/index.d.ts +17 -0
  211. package/plugins/acryl-router/src/helpers/schema-validator.d.ts +54 -0
  212. package/plugins/acryl-router-http/plugin.json +4 -1
  213. package/plugins/acryl-router-http/src/index.ts +1 -1
  214. package/plugins/acryl-state/plugin.json +5 -1
  215. package/plugins/acryl-state/src/service.ts +3 -3
  216. package/plugins/tsconfig.json +32 -20
  217. package/docs/framework/UPSTREAM.md +0 -3
  218. package/docs/framework/upstream.json +0 -9
  219. package/lib/index-D4FAsBZu.d.ts +0 -85
  220. package/plugins/acryl-db/upstream.json +0 -18
  221. package/plugins/acryl-db-sqlite/upstream.json +0 -21
  222. package/plugins/acryl-door/upstream.json +0 -28
  223. package/plugins/acryl-inspect/upstream.json +0 -18
  224. package/plugins/acryl-kv/upstream.json +0 -18
  225. package/plugins/acryl-kv-db/upstream.json +0 -21
  226. package/plugins/acryl-kv-json/upstream.json +0 -22
  227. package/plugins/acryl-node-server/upstream.json +0 -21
  228. package/plugins/acryl-router/upstream.json +0 -20
  229. package/plugins/acryl-router-http/upstream.json +0 -22
  230. package/plugins/acryl-state/NOTICE +0 -30
  231. package/plugins/acryl-state/upstream.json +0 -22
  232. package/plugins/desktop/upstream.json +0 -12
  233. package/plugins/desktop-web/upstream.json +0 -12
  234. package/templates/todo/upstream.json +0 -12
  235. /package/lib/{index-CTy4N7ng.d.ts → deprecated-CTy4N7ng.d.ts} +0 -0
@@ -0,0 +1,338 @@
1
+ import { t as Acryl } from "./app-BM0rTCp7.js";
2
+ import { bodyLimit } from "./body-limit.js";
3
+ import { methodNotAllowed } from "./method-not-allowed.js";
4
+ import { r as streamSSE } from "./streaming-DHST3Scg.js";
5
+ import { loopbackAuthority, loopbackOnly } from "./loopback-only.js";
6
+ import "./helpers-CvTauQT5.js";
7
+ import { UiControlError, WINDOW_ID, parseChannelToHost } from "./door/protocol.js";
8
+ import "./src-CwABFr6r.js";
9
+ import "./middleware-CPQ8himN.js";
10
+ import { dirname, join } from "node:path";
11
+ import { timingSafeEqual } from "node:crypto";
12
+ import { closeSync, mkdirSync, openSync, readFileSync, renameSync, statSync, writeSync } from "node:fs";
13
+ //#region ../../plugins/acryl-door/src/host/channel.ts
14
+ /**
15
+ * The Host's side of the page channel: the connected pages of the app, and calls that go to the current one (moved from
16
+ * `acryl-agent-control`, spec 041).
17
+ *
18
+ * The Host cannot reach into a page, so each page opens one event stream to the Host and answers calls by POST. With several pages open,
19
+ * the one the user focused last is the one the agent drives. A page that reconnects with the same window id replaces its old stream.
20
+ */
21
+ /** How long a call may take before it is given up (a wait may take up to 10 seconds itself). */
22
+ const DEFAULT_CALL_TIMEOUT_MS = 3e4;
23
+ var UiChannel = class {
24
+ connections = /* @__PURE__ */ new Map();
25
+ pending = /* @__PURE__ */ new Map();
26
+ nextId = 1;
27
+ clock = 0;
28
+ closed = false;
29
+ /**
30
+ * Register a page's stream. A stream already open for the same window is closed first, and its calls settle as `disconnected`.
31
+ * @returns the function to call when this stream ends.
32
+ */
33
+ attach(windowId, focused, stream) {
34
+ const previous = this.connections.get(windowId);
35
+ if (previous !== void 0) this.drop(previous, true);
36
+ const connection = {
37
+ stream,
38
+ windowId,
39
+ focused,
40
+ lastActive: ++this.clock
41
+ };
42
+ this.connections.set(windowId, connection);
43
+ return () => {
44
+ if (this.connections.get(windowId) === connection) this.drop(connection, false);
45
+ };
46
+ }
47
+ /** A message a page sent by POST. Only the window that was asked may answer a call, so another page cannot forge a result. */
48
+ receive(windowId, message) {
49
+ const connection = this.connections.get(windowId);
50
+ if (connection === void 0) return;
51
+ if (message.t === "focus") {
52
+ connection.focused = message.focused;
53
+ if (message.focused) connection.lastActive = ++this.clock;
54
+ return;
55
+ }
56
+ const entry = this.pending.get(message.id);
57
+ if (entry === void 0 || entry.connection !== connection) return;
58
+ if (message.t === "result") this.settle(message.id, message.value);
59
+ else this.settle(message.id, void 0, new UiControlError(message.code, message.message));
60
+ }
61
+ /** True when at least one page is connected. */
62
+ get connected() {
63
+ return this.connections.size > 0;
64
+ }
65
+ /** The windows currently connected, most recently active first (for diagnostics and tests). */
66
+ windows() {
67
+ return this.byRecency().map(({ windowId, focused }) => ({
68
+ windowId,
69
+ focused
70
+ }));
71
+ }
72
+ /**
73
+ * Ask the current page to do something.
74
+ * @throws UiControlError `no-window`, `timeout`, `aborted`, `unloaded`, `disconnected`, or whatever the page refused with.
75
+ */
76
+ call(request, options = {}) {
77
+ if (this.closed) return Promise.reject(new UiControlError("unloaded", "the door was unloaded"));
78
+ const target = this.byRecency()[0];
79
+ if (target === void 0) return Promise.reject(new UiControlError("no-window", "no page of the app is open to control"));
80
+ const signal = options.signal;
81
+ if (signal?.aborted === true) return Promise.reject(new UiControlError("aborted", "the call was cancelled"));
82
+ const id = this.nextId;
83
+ this.nextId += 1;
84
+ return new Promise((resolve, reject) => {
85
+ const timer = setTimeout(() => {
86
+ this.settle(id, void 0, new UiControlError("timeout", "the page did not answer in time"));
87
+ }, options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS);
88
+ const onAbort = () => {
89
+ this.settle(id, void 0, new UiControlError("aborted", "the call was cancelled"));
90
+ };
91
+ signal?.addEventListener("abort", onAbort, { once: true });
92
+ this.pending.set(id, {
93
+ connection: target,
94
+ resolve,
95
+ reject,
96
+ timer,
97
+ cleanup: () => {
98
+ signal?.removeEventListener("abort", onAbort);
99
+ }
100
+ });
101
+ const frame = {
102
+ t: "call",
103
+ id,
104
+ request
105
+ };
106
+ try {
107
+ target.stream.send(JSON.stringify(frame));
108
+ } catch {
109
+ this.settle(id, void 0, new UiControlError("disconnected", "the page could not be reached"));
110
+ }
111
+ });
112
+ }
113
+ /** Close every page's stream and settle every pending call as `unloaded`. */
114
+ close() {
115
+ this.closed = true;
116
+ for (const id of [...this.pending.keys()]) this.settle(id, void 0, new UiControlError("unloaded", "the door was unloaded"));
117
+ for (const connection of [...this.connections.values()]) connection.stream.close();
118
+ this.connections.clear();
119
+ }
120
+ byRecency() {
121
+ return [...this.connections.values()].sort((a, b) => b.lastActive - a.lastActive);
122
+ }
123
+ /** Forget a connection; its calls in flight settle as `disconnected` at once rather than waiting for their timeout. */
124
+ drop(connection, closeStream) {
125
+ this.connections.delete(connection.windowId);
126
+ for (const [id, entry] of [...this.pending]) if (entry.connection === connection) this.settle(id, void 0, new UiControlError("disconnected", "the page went away before it answered"));
127
+ if (closeStream) connection.stream.close();
128
+ }
129
+ settle(id, value, failure) {
130
+ const entry = this.pending.get(id);
131
+ if (entry === void 0) return;
132
+ this.pending.delete(id);
133
+ clearTimeout(entry.timer);
134
+ entry.cleanup();
135
+ if (failure !== void 0) entry.reject(failure);
136
+ else if (value !== void 0) entry.resolve(value);
137
+ }
138
+ };
139
+ //#endregion
140
+ //#region ../../plugins/acryl-door/src/host/audit.ts
141
+ /**
142
+ * The audit log: one JSON line per door call, append-only, in the app's own `.acryl/audit/` (moved from `acryl-agent-control`, spec 041).
143
+ *
144
+ * It is the record of what the agent did to the app. It never holds text the agent typed or values it read,
145
+ * only which control was touched and how the call ended.
146
+ */
147
+ const MAX_LOG_BYTES = 5 * 1024 * 1024;
148
+ /** The audit log below a folder the caller owns (the app's `.acryl/` here; ACRYL's app home for the DSH face). */
149
+ function auditPath(folder) {
150
+ return join(folder, "audit", "ui-control.jsonl");
151
+ }
152
+ var AuditLog = class {
153
+ path;
154
+ constructor(path) {
155
+ this.path = path;
156
+ }
157
+ /** Append one line; a failing disk never breaks the tool call. */
158
+ record(entry) {
159
+ try {
160
+ mkdirSync(dirname(this.path), { recursive: true });
161
+ this.rotateIfLarge();
162
+ const fd = openSync(this.path, "a", 384);
163
+ try {
164
+ writeSync(fd, `${JSON.stringify(entry)}\n`);
165
+ } finally {
166
+ closeSync(fd);
167
+ }
168
+ } catch {}
169
+ }
170
+ /** The most recent entries, oldest first. */
171
+ recent(limit = 100) {
172
+ try {
173
+ return readFileSync(this.path, "utf8").split("\n").filter((line) => line !== "").slice(-limit).flatMap((line) => {
174
+ try {
175
+ return [JSON.parse(line)];
176
+ } catch {
177
+ return [];
178
+ }
179
+ });
180
+ } catch {
181
+ return [];
182
+ }
183
+ }
184
+ rotateIfLarge() {
185
+ try {
186
+ if (statSync(this.path).size >= MAX_LOG_BYTES) renameSync(this.path, `${this.path}.1`);
187
+ } catch {}
188
+ }
189
+ };
190
+ //#endregion
191
+ //#region ../../plugins/acryl-door/src/host/http.ts
192
+ /**
193
+ * What every door route shares (moved from `acryl-agent-control`, spec 041). Two kinds of caller reach it, each with its own guard: the
194
+ * app's own page (same origin, on a loopback address), and an operator on this machine (`.acryl/bin/acryl`, an agent's shell) presenting
195
+ * this run's secret.
196
+ */
197
+ /** Every door route lives below this path. */
198
+ const DOOR_PREFIX = "/api/acryl-door";
199
+ /** The cap on a JSON request body. */
200
+ const MAX_BODY_BYTES = 64 * 1024;
201
+ /** The JSON body of a refusal. */
202
+ function error(message) {
203
+ return { error: message };
204
+ }
205
+ /** The app's own page, on a loopback address, and nothing else: another origin, another port and a script on the machine are refused. */
206
+ const ownPage = loopbackOnly({ callers: "own-page" });
207
+ /** A constant-time comparison of a `Bearer` header with the secret. */
208
+ function tokenMatches(header, secret) {
209
+ const presented = header?.startsWith("Bearer ") === true ? header.slice(7) : "";
210
+ const a = Buffer.from(presented);
211
+ const b = Buffer.from(secret);
212
+ return a.length === b.length && timingSafeEqual(a, b);
213
+ }
214
+ /**
215
+ * Only an operator on this machine: a loopback peer addressing this server's own port (`loopbackAuthority`), presenting this run's
216
+ * secret. Anything else is 403 `forbidden`, which the client reads as "refused: the app restarted and the secret rotated?".
217
+ */
218
+ function operatorOnly(secret) {
219
+ return async (c, next) => {
220
+ if (loopbackAuthority(c) === void 0 || !tokenMatches(c.req.header("authorization"), secret)) return c.json(error("forbidden"), 403);
221
+ await next();
222
+ };
223
+ }
224
+ /** The headers every private answer carries, set after the handler so a refusal carries them too. */
225
+ const privateHeaders = async (c, next) => {
226
+ await next();
227
+ c.header("cache-control", "no-store");
228
+ c.header("x-content-type-options", "nosniff");
229
+ };
230
+ /**
231
+ * The door's routes as one app: each part's routes, every answer with the private headers, and a known path asked with another method
232
+ * answered 405 with its `Allow` before any guard runs. The middleware is scoped to the prefix, so it never wraps another plugin's routes.
233
+ */
234
+ function privateApi(prefix, ...routes) {
235
+ const app = new Acryl();
236
+ app.use(`${prefix}/*`, privateHeaders, methodNotAllowed({
237
+ app,
238
+ onMethodNotAllowed: (c, allowed) => c.json(error("method not allowed"), 405, { allow: allowed.join(", ") })
239
+ }));
240
+ for (const part of routes) app.route("/", part);
241
+ return app;
242
+ }
243
+ /** Refuse a body over {@link MAX_BODY_BYTES} with 413 before the handler reads it. */
244
+ const jsonBody = bodyLimit({
245
+ maxSize: MAX_BODY_BYTES,
246
+ onError: (c) => c.json(error("request body is too large"), 413)
247
+ });
248
+ /** Returned by {@link readJson} for a body that is not JSON. */
249
+ const INVALID_JSON = Symbol("invalid JSON");
250
+ /** The parsed body, or {@link INVALID_JSON}. */
251
+ async function readJson(c) {
252
+ try {
253
+ return await c.req.json();
254
+ } catch {
255
+ return INVALID_JSON;
256
+ }
257
+ }
258
+ //#endregion
259
+ //#region ../../plugins/acryl-door/src/host/page-routes.ts
260
+ /**
261
+ * The page channel on the router: an event stream down to the page and POST up, so it needs no WebSocket upgrade on any adapter.
262
+ *
263
+ * `GET <prefix>/channel?window=<id>&focused=0|1` is the page's stream. The Host writes each call as `event: call` and a `ping` every
264
+ * heartbeat, so a page that hears nothing for a while knows the stream died and reconnects. `POST <prefix>/channel/answer` carries
265
+ * `{ window, message }` (a result, an error or a focus change). Both are for the app's own page only (same origin, loopback).
266
+ */
267
+ /** How often the Host writes a `ping`; the page gives up on a stream that is silent for three of these. */
268
+ const HEARTBEAT_MS = 15e3;
269
+ function pageChannelRoutes(channel, options) {
270
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
271
+ const page = options.hostnames === void 0 ? ownPage : loopbackOnly({
272
+ callers: "own-page",
273
+ hostnames: options.hostnames
274
+ });
275
+ return new Acryl().get(`${options.prefix}/channel`, page, (c) => {
276
+ const windowId = c.req.query("window");
277
+ if (windowId === void 0 || !WINDOW_ID.test(windowId)) return c.json(error("a window id is required"), 400);
278
+ const focused = c.req.query("focused") === "1";
279
+ const signal = c.req.raw.signal;
280
+ return streamSSE(c, async (stream) => {
281
+ let end = () => {};
282
+ const ended = new Promise((resolve) => {
283
+ end = resolve;
284
+ });
285
+ stream.onAbort(end);
286
+ signal.addEventListener("abort", end, { once: true });
287
+ const write = (event, data) => {
288
+ stream.writeSSE({
289
+ event,
290
+ data
291
+ }).catch(end);
292
+ };
293
+ const detach = channel.attach(windowId, focused, {
294
+ send: (data) => {
295
+ write("call", data);
296
+ },
297
+ close: end
298
+ });
299
+ const beat = setInterval(() => {
300
+ write("ping", "");
301
+ }, heartbeatMs);
302
+ try {
303
+ write("ready", "");
304
+ await ended;
305
+ } finally {
306
+ clearInterval(beat);
307
+ signal.removeEventListener("abort", end);
308
+ detach();
309
+ }
310
+ });
311
+ }).post(`${options.prefix}/channel/answer`, page, jsonBody, async (c) => {
312
+ const body = await readJson(c);
313
+ if (body === INVALID_JSON || typeof body !== "object" || body === null) return c.json(error("invalid request body"), 400);
314
+ const { window: windowId, message } = body;
315
+ const parsed = parseChannelToHost(message);
316
+ if (typeof windowId !== "string" || !WINDOW_ID.test(windowId) || parsed === null) return c.json(error("invalid message"), 400);
317
+ channel.receive(windowId, parsed);
318
+ return c.body(null, 204);
319
+ });
320
+ }
321
+ //#endregion
322
+ //#region ../../plugins/acryl-door/src/page/snapshot-text.ts
323
+ /**
324
+ * The snapshot as compact text for an agent: one line per element, indented by depth.
325
+ * @param more - how to ask for the rest when the page was cut, given the cursor to continue from.
326
+ */
327
+ function renderSnapshot(snapshot, more = (cursor) => `call ui_snapshot with cursor ${String(cursor)}`) {
328
+ const lines = snapshot.nodes.map((node) => {
329
+ const level = node.level === void 0 ? "" : ` level=${String(node.level)}`;
330
+ const states = node.states.length === 0 ? "" : ` [${node.states.join(", ")}]`;
331
+ const value = node.value === void 0 ? "" : ` value=${JSON.stringify(node.value)}`;
332
+ return `${" ".repeat(Math.min(node.depth, 8))}- ${node.role}${node.name === "" ? "" : ` ${JSON.stringify(node.name)}`}${level}${states}${value} [ref=${node.ref}]`;
333
+ });
334
+ const rest = snapshot.nextCursor === void 0 ? "" : `\n(${String(snapshot.total - snapshot.nextCursor)} more elements; ${more(snapshot.nextCursor)})`;
335
+ return `Page: ${snapshot.title || "(untitled)"}\nSnapshot ${String(snapshot.generation)}, ${String(snapshot.nodes.length)} of ${String(snapshot.total)} elements:\n${lines.join("\n")}${rest}`;
336
+ }
337
+ //#endregion
338
+ export { INVALID_JSON as a, operatorOnly as c, AuditLog as d, auditPath as f, DOOR_PREFIX as i, privateApi as l, HEARTBEAT_MS as n, error as o, UiChannel as p, pageChannelRoutes as r, jsonBody as s, renderSnapshot as t, readJson as u };
@@ -0,0 +1,328 @@
1
+ import { HTTPException } from "./http-exception.js";
2
+ import { mergePath } from "./utils/url.js";
3
+ import { COMPOSED_HANDLER } from "./utils/constants.js";
4
+ import { t as TrieRouter } from "./trie-router-CfFbapzL.js";
5
+ import { t as Acryl } from "./app-BM0rTCp7.js";
6
+ import "./core-CaY8XDZ6.js";
7
+ import "./deprecated-BP4fPt38.js";
8
+ import "./connection-C7xgP1hP.js";
9
+ import { Service } from "@deepseek-ai/cordis";
10
+ import z from "@deepseek-ai/schemastery";
11
+ //#region ../../plugins/acryl-router/src/service.ts
12
+ /**
13
+ * The ACRYL router service (`ctx.router`): plugin-owned `Acryl` mounts on Web-standard Request and Response.
14
+ *
15
+ * A plugin builds an ordinary `Acryl` and mounts it with `ctx.router.route(path, app)` from its own context, so the mount disappears when that
16
+ * plugin unloads. `fetch(request)` runs a request in process: Web and Desktop serve it behind HTTP through an adapter, the terminal UI calls
17
+ * it directly, so one handler serves every surface without opening a socket. Disposal refuses new requests, aborts in-flight ones, ends any
18
+ * response body still streaming and waits for all of it to settle.
19
+ */
20
+ const DEFAULT_DISPOSE_GRACE_MS = 2e3;
21
+ /** Every field has a default, so a caller passes only what it changes: `ctx.plugin(plugin, { ... })` takes the partial shape. */
22
+ const Config = z.object({
23
+ trailingSlash: z.union(["strict", "ignore"]).default("strict").description("Whether `/a` and `/a/` are different paths."),
24
+ disposeGraceMs: z.number().min(0).default(DEFAULT_DISPOSE_GRACE_MS).description("How long disposal waits for aborted requests, their response bodies and their waitUntil tasks before it gives up on them.")
25
+ });
26
+ /** Stable Cordis plugin name, equal to the package name and the Loader row id. */
27
+ const name = "acryl-router";
28
+ /** The core's `app.route` with a structural app: at run time it reads only the routes and the app's own error handler. */
29
+ const copyRoutes = (app) => new Acryl().route("/", app);
30
+ /** Wait for `tasks`, including any added while waiting (a stream may register more after the handler returned). */
31
+ async function drain(tasks) {
32
+ let seen = 0;
33
+ while (seen < tasks.length) {
34
+ const batch = tasks.slice(seen);
35
+ seen = tasks.length;
36
+ await Promise.allSettled(batch);
37
+ }
38
+ }
39
+ var Router = class extends Service {
40
+ /** Everything that changes after construction, kept in one object so the service's live state is in one place. */
41
+ state = {
42
+ mounts: /* @__PURE__ */ new Set(),
43
+ errorHandler: void 0,
44
+ notFoundHandler: void 0,
45
+ composed: void 0,
46
+ closing: false
47
+ };
48
+ flights = /* @__PURE__ */ new Set();
49
+ /** The flight of each request the router dispatched, keyed by the request its handlers see as `c.req.raw`. */
50
+ flightOf = /* @__PURE__ */ new WeakMap();
51
+ strict;
52
+ graceMs;
53
+ logError;
54
+ constructor(ctx, config = {}) {
55
+ super(ctx, "router");
56
+ const { trailingSlash = "strict", disposeGraceMs = DEFAULT_DISPOSE_GRACE_MS } = config;
57
+ this.strict = trailingSlash === "strict";
58
+ this.graceMs = disposeGraceMs;
59
+ this.logError = (error, c) => {
60
+ if (error instanceof HTTPException) return error.getResponse();
61
+ if (c.req.raw.signal.aborted) return new Response("Client Closed Request", { status: 499 });
62
+ ctx.logger.error(`acryl-router: handler failed: ${error instanceof Error ? error.message : String(error)}`);
63
+ return c.text("Internal Server Error", 500);
64
+ };
65
+ ctx.effect(() => async () => {
66
+ this.state.closing = true;
67
+ await this.end([...this.flights], "disposal");
68
+ }, "acryl-router: abort and settle in-flight requests");
69
+ }
70
+ /** Registrations currently held (mounts, and the router-wide error and not-found handlers); a leak check for reload tests. */
71
+ get size() {
72
+ return this.state.mounts.size + (this.state.errorHandler === void 0 ? 0 : 1) + (this.state.notFoundHandler === void 0 ? 0 : 1);
73
+ }
74
+ /**
75
+ * Serve `app` under `path` (`'/'` for its own paths as they are). The mount belongs to the calling plugin and goes away when it unloads;
76
+ * the returned function removes it sooner. Mounts are composed in the order they were made. Like the core's own `app.route`, the routes
77
+ * are copied now: one the app registers after this call is not served (mount again, or finish the app first).
78
+ */
79
+ route(path, app) {
80
+ const mount = this.snapshot(path, app);
81
+ this.state.mounts.add(mount);
82
+ this.state.composed = void 0;
83
+ const remove = () => {
84
+ if (!this.state.mounts.delete(mount)) return Promise.resolve();
85
+ this.state.composed = void 0;
86
+ return this.end([...this.flights].filter((flight) => (flight.running.get(mount) ?? 0) > 0 || flight.producer === mount), `removing ${path}`);
87
+ };
88
+ const served = new Set(app.routes.map((route) => `${route.method} ${mergePath(path, route.path)}`));
89
+ const owner = this.ctx;
90
+ owner.effect(function* () {
91
+ for (const route of served) yield owner.effect(() => () => {}, `acryl-router: serves ${route}`);
92
+ yield remove;
93
+ }, `acryl-router: route ${path}`);
94
+ return () => {
95
+ remove();
96
+ };
97
+ }
98
+ /**
99
+ * 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
100
+ * 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.
101
+ */
102
+ snapshot(path, app) {
103
+ const copy = copyRoutes(app);
104
+ const tagged = new Acryl();
105
+ const mount = {
106
+ path,
107
+ app: tagged
108
+ };
109
+ for (const route of copy.routes) {
110
+ const inner = route.handler;
111
+ const handler = async (c, next) => {
112
+ const flight = this.flightOf.get(c.req.raw);
113
+ if (flight === void 0) return inner(c, next);
114
+ let calledNext = false;
115
+ flight.running.set(mount, (flight.running.get(mount) ?? 0) + 1);
116
+ try {
117
+ const result = await inner(c, () => {
118
+ calledNext = true;
119
+ return next();
120
+ });
121
+ if (!calledNext && result instanceof Response) flight.producer = mount;
122
+ return result;
123
+ } finally {
124
+ flight.running.set(mount, (flight.running.get(mount) ?? 1) - 1);
125
+ }
126
+ };
127
+ handler[COMPOSED_HANDLER] = inner;
128
+ tagged.on(route.method, route.path, handler);
129
+ const registered = tagged.routes.at(-1);
130
+ if (registered !== void 0) registered.basePath = route.basePath;
131
+ }
132
+ return mount;
133
+ }
134
+ /** Abort `flights`, cut their bodies short and wait for them to settle, at most `disposeGraceMs`. */
135
+ async end(flights, why) {
136
+ if (flights.length === 0) return;
137
+ for (const flight of flights) {
138
+ flight.controller.abort();
139
+ flight.endBody();
140
+ }
141
+ let timer;
142
+ const gaveUp = new Promise((resolve) => {
143
+ timer = setTimeout(() => {
144
+ resolve("gave-up");
145
+ }, this.graceMs);
146
+ });
147
+ const outcome = await Promise.race([Promise.allSettled(flights.map((flight) => flight.settled)).then(() => "settled"), gaveUp]);
148
+ clearTimeout(timer);
149
+ if (outcome === "gave-up") this.ctx.logger.warn(`acryl-router: ${String(flights.length)} request(s) did not finish ${String(this.graceMs)} ms after abort (${why}); continued without them`);
150
+ }
151
+ /** Turn errors no mounted app handled into responses. One handler per router; the calling plugin owns it. */
152
+ onError(handler) {
153
+ if (this.state.errorHandler !== void 0) throw new Error("router: an error handler is already registered");
154
+ this.state.errorHandler = handler;
155
+ this.state.composed = void 0;
156
+ const remove = () => {
157
+ if (this.state.errorHandler !== handler) return;
158
+ this.state.errorHandler = void 0;
159
+ this.state.composed = void 0;
160
+ };
161
+ this.ctx.effect(() => remove, "acryl-router: onError");
162
+ return remove;
163
+ }
164
+ /** Answer requests no route claimed. One handler per router; the calling plugin owns it. */
165
+ notFound(handler) {
166
+ if (this.state.notFoundHandler !== void 0) throw new Error("router: a not-found handler is already registered");
167
+ this.state.notFoundHandler = handler;
168
+ this.state.composed = void 0;
169
+ const remove = () => {
170
+ if (this.state.notFoundHandler !== handler) return;
171
+ this.state.notFoundHandler = void 0;
172
+ this.state.composed = void 0;
173
+ };
174
+ this.ctx.effect(() => remove, "acryl-router: notFound");
175
+ return remove;
176
+ }
177
+ /**
178
+ * Call the router the way a test calls an app: a path or URL string, a `URL` or a `Request`, with the usual init.
179
+ * A relative path is resolved against `http://localhost`.
180
+ */
181
+ request(input, init, connection) {
182
+ const request = input instanceof Request ? input : new Request(new URL(String(input), "http://localhost"), init);
183
+ return this.fetch(request, connection);
184
+ }
185
+ /**
186
+ * Run a request in process. Never throws: failures become a 500, a disposed router answers 503. `connection` describes the transport
187
+ * (an HTTP adapter passes the socket's peer address and local port); an in-process caller omits it. Handlers read it with
188
+ * `connectionOf(c)`, abort with `c.req.raw.signal` and keep the request in flight with `c.executionCtx.waitUntil(task)`.
189
+ */
190
+ fetch(request, connection = {}) {
191
+ if (this.state.closing) return Promise.resolve(new Response("Service Unavailable", { status: 503 }));
192
+ const controller = new AbortController();
193
+ const forward = () => controller.abort();
194
+ if (request.signal.aborted) controller.abort();
195
+ else request.signal.addEventListener("abort", forward, { once: true });
196
+ const tasks = [];
197
+ const executionCtx = {
198
+ waitUntil: (task) => {
199
+ tasks.push(task);
200
+ },
201
+ passThroughOnException: () => {},
202
+ props: {}
203
+ };
204
+ const env = { connection };
205
+ let bodyDone = Promise.resolve();
206
+ let endBody = () => {};
207
+ let resolveSettled;
208
+ const flight = {
209
+ controller,
210
+ running: /* @__PURE__ */ new Map(),
211
+ producer: void 0,
212
+ endBody: () => {
213
+ endBody();
214
+ },
215
+ settled: new Promise((resolve) => {
216
+ resolveSettled = resolve;
217
+ })
218
+ };
219
+ const inner = new Request(request, { signal: controller.signal });
220
+ this.flightOf.set(inner, flight);
221
+ const response = this.dispatch(inner, env, executionCtx).then((raw) => {
222
+ const tracked = this.track(raw);
223
+ bodyDone = tracked.done;
224
+ endBody = tracked.end;
225
+ if (controller.signal.aborted) tracked.end();
226
+ return tracked.response;
227
+ });
228
+ const settled = response.then(async () => {
229
+ await bodyDone;
230
+ await drain(tasks);
231
+ });
232
+ resolveSettled(settled);
233
+ this.flights.add(flight);
234
+ settled.finally(() => {
235
+ request.signal.removeEventListener("abort", forward);
236
+ this.flights.delete(flight);
237
+ });
238
+ return response;
239
+ }
240
+ async dispatch(request, env, executionCtx) {
241
+ try {
242
+ return await this.app().fetch(request, env, executionCtx);
243
+ } catch (error) {
244
+ this.ctx.logger.error(`acryl-router: request failed: ${error instanceof Error ? error.message : String(error)}`);
245
+ return new Response("Internal Server Error", { status: 500 });
246
+ }
247
+ }
248
+ app() {
249
+ if (this.state.composed !== void 0) return this.state.composed;
250
+ const root = new Acryl({
251
+ strict: this.strict,
252
+ router: new TrieRouter()
253
+ });
254
+ for (const mount of this.state.mounts) root.route(mount.path, mount.app);
255
+ root.onError(this.state.errorHandler ?? this.logError);
256
+ if (this.state.notFoundHandler !== void 0) root.notFound(this.state.notFoundHandler);
257
+ this.state.composed = root;
258
+ return root;
259
+ }
260
+ /**
261
+ * Hand the caller a body the router can end. Reading it passes the handler's chunks through; disposal cancels the handler's stream (which runs
262
+ * the stream helpers' own abort cleanup) and closes the caller's side, so a stream never outlives the plugin that served it.
263
+ */
264
+ track(response) {
265
+ const source = response.body;
266
+ if (source === null) return {
267
+ response,
268
+ done: Promise.resolve(),
269
+ end: () => {}
270
+ };
271
+ const reader = source.getReader();
272
+ let finish;
273
+ const done = new Promise((resolve) => {
274
+ finish = resolve;
275
+ });
276
+ let ended = false;
277
+ let sink;
278
+ const end = () => {
279
+ if (ended) return;
280
+ ended = true;
281
+ finish();
282
+ };
283
+ const endEarly = () => {
284
+ if (ended) return;
285
+ reader.cancel(/* @__PURE__ */ new Error("acryl-router: disposed")).catch(() => {});
286
+ try {
287
+ sink?.close();
288
+ } catch {}
289
+ end();
290
+ };
291
+ const body = new ReadableStream({
292
+ start: (controller) => {
293
+ sink = controller;
294
+ },
295
+ pull: async (controller) => {
296
+ try {
297
+ const chunk = await reader.read();
298
+ if (ended) return;
299
+ if (chunk.done) {
300
+ controller.close();
301
+ end();
302
+ } else controller.enqueue(chunk.value);
303
+ } catch (error) {
304
+ if (!ended) controller.error(error);
305
+ end();
306
+ }
307
+ },
308
+ cancel: async (reason) => {
309
+ end();
310
+ await reader.cancel(reason);
311
+ }
312
+ });
313
+ return {
314
+ response: new Response(body, {
315
+ status: response.status,
316
+ statusText: response.statusText,
317
+ headers: response.headers
318
+ }),
319
+ done,
320
+ end: endEarly
321
+ };
322
+ }
323
+ };
324
+ function apply(ctx, config = {}) {
325
+ new Router(ctx, config);
326
+ }
327
+ //#endregion
328
+ export { name as i, Router as n, apply as r, Config as t };
package/lib/timeout.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { L as MiddlewareHandler, t as Context } from "./context-DS3fYyG-.js";
2
- import { HTTPException } from "./http-exception.js";
2
+ import { t as HTTPException } from "./http-exception-kpGJkmpi.js";
3
3
 
4
4
  //#region ../../plugins/acryl-router/src/core/middleware/timeout/index.d.ts
5
5
  type HTTPExceptionFunction = (context: Context) => HTTPException;