acrylblends 1.0.4 → 1.0.6

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 (351) hide show
  1. package/docs/framework/agents/index.md +7 -5
  2. package/lib/accept-CNNHu4Cf.js +193 -0
  3. package/lib/{accepts-XBuZPVRI.js → accepts-Btq83xPd.js} +1 -1
  4. package/lib/accepts.js +1 -1
  5. package/lib/acryl-base.js +1 -331
  6. package/lib/adapter-CaZ6FZUU.js +37 -0
  7. package/lib/adapter.js +1 -36
  8. package/lib/{app-BM0rTCp7.js → app-CQcGWjrk.js} +4 -4
  9. package/lib/app-base-CpRMZL4v.js +332 -0
  10. package/lib/{audit-D5z9uwOP.d.ts → audit-GcLtvNLV.d.ts} +4 -2
  11. package/lib/{base-NSN_SNv-.js → base-D_6kfOr-.js} +1 -1
  12. package/lib/basic-auth-C0HTRpXh.js +20 -0
  13. package/lib/basic-auth-DFLflXNe.js +95 -0
  14. package/lib/basic-auth.js +1 -94
  15. package/lib/bearer-auth-Cwxt-vgS.js +94 -0
  16. package/lib/bearer-auth.js +1 -93
  17. package/lib/body-D7f4e7w_.js +94 -0
  18. package/lib/body-limit-WmIjWRLt.js +66 -0
  19. package/lib/body-limit.js +1 -65
  20. package/lib/buffer-CkEOJCcY.js +51 -0
  21. package/lib/bun.js +3 -3
  22. package/lib/cache-B0bHH7s5.js +169 -0
  23. package/lib/cache.js +1 -168
  24. package/lib/{client-DDLXwhBo.js → client-BrMFnMEd.js} +1 -1
  25. package/lib/{client-modules-CLvWOjbd.js → client-modules-drj4mjhj.js} +4 -4
  26. package/lib/client.js +1 -1
  27. package/lib/color-COa3DplO.js +37 -0
  28. package/lib/combine-TWuGdcbC.js +137 -0
  29. package/lib/combine.js +1 -136
  30. package/lib/{components-cEW3ovK0.js → components-B2ka9Ax1.js} +4 -4
  31. package/lib/{components-JXVm45kK.js → components-PVXwaOaC.js} +2 -2
  32. package/lib/compress-BzWqsHF1.js +79 -0
  33. package/lib/compress-C-9vK4B2.d.ts +11 -0
  34. package/lib/compress-DCyrrQ5D.js +11 -0
  35. package/lib/compress.d.ts +1 -1
  36. package/lib/compress.js +2 -78
  37. package/lib/concurrent-DrF3d2c-.js +30 -0
  38. package/lib/conninfo-BCROmdnf.d.ts +11 -0
  39. package/lib/conninfo-CWJwieh4.js +19 -0
  40. package/lib/constants-CBSEudrb.js +7 -0
  41. package/lib/{context-DDO0cfuP.js → context-CkOh7Ygv.js} +2 -2
  42. package/lib/context-storage-iuPoI_CR.js +52 -0
  43. package/lib/context-storage.js +1 -51
  44. package/lib/cookie-B1LlkzOe.js +125 -0
  45. package/lib/cookie-BHIduy2T.js +82 -0
  46. package/lib/cookie.js +1 -81
  47. package/lib/cordis/db-sqlite.d.ts +1 -1
  48. package/lib/cordis/door.d.ts +99 -5
  49. package/lib/cordis/door.js +562 -20
  50. package/lib/cordis/inspect.js +4 -4
  51. package/lib/cordis/kv-db.d.ts +1 -1
  52. package/lib/cordis/kv-db.js +1 -1
  53. package/lib/cordis/kv-json.d.ts +1 -1
  54. package/lib/cordis/kv-json.js +1 -1
  55. package/lib/cordis/node-server.d.ts +1 -1
  56. package/lib/cordis/node-server.js +3 -116
  57. package/lib/cordis/router-http.js +2 -2
  58. package/lib/cordis/router.js +4 -4
  59. package/lib/cordis/state.js +7 -7
  60. package/lib/{core-CaY8XDZ6.js → core-BOu1jYua.js} +1 -1
  61. package/lib/cors-CbAQ-8qU.js +106 -0
  62. package/lib/cors.js +1 -105
  63. package/lib/crypto-iVP-N-tS.js +34 -0
  64. package/lib/csrf-DqqOZtgy.js +101 -0
  65. package/lib/csrf.js +1 -100
  66. package/lib/css-C5CPVMvM.js +134 -0
  67. package/lib/css.js +2 -133
  68. package/lib/db/sqlite.d.ts +1 -25
  69. package/lib/dev-CLgu-JRe.js +50 -0
  70. package/lib/dev.js +1 -49
  71. package/lib/dom-YjND9Jt3.js +74 -0
  72. package/lib/door/host.d.ts +2 -2
  73. package/lib/door/host.js +1 -1
  74. package/lib/door/page.d.ts +3 -1
  75. package/lib/door/page.js +2 -2
  76. package/lib/door/protocol.d.ts +1 -1
  77. package/lib/door/protocol.js +1 -212
  78. package/lib/encode-BhbNLrjp.js +33 -0
  79. package/lib/factory-CLdSf-xS.js +30 -0
  80. package/lib/factory.js +1 -29
  81. package/lib/handler-C0wZ0KwO.js +12 -0
  82. package/lib/helpers-BEjcdbgw.js +5 -0
  83. package/lib/{hooks-w72iLeFR.js → hooks-CfC07ri0.js} +1 -1
  84. package/lib/html-CgNa89xw.js +31 -0
  85. package/lib/html-DGExBFNg.js +103 -0
  86. package/lib/html.d.ts +2 -5
  87. package/lib/html.js +2 -30
  88. package/lib/http-exception-MjWkT89V.js +54 -0
  89. package/lib/http-exception.js +1 -53
  90. package/lib/index-dAzQsJG9.d.ts +6 -0
  91. package/lib/index.js +3 -3
  92. package/lib/ip-restriction-jKtKCRM_.js +166 -0
  93. package/lib/ip-restriction.js +1 -165
  94. package/lib/ipaddr-DCoIo528.js +238 -0
  95. package/lib/jsx/dom/client.js +1 -1
  96. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  97. package/lib/jsx/dom/jsx-runtime.js +2 -1
  98. package/lib/jsx/dom/server.js +3 -3
  99. package/lib/jsx/dom.js +5 -73
  100. package/lib/jsx/jsx-dev-runtime.d.ts +2 -5
  101. package/lib/jsx/jsx-dev-runtime.js +2 -13
  102. package/lib/jsx/jsx-runtime.d.ts +2 -2
  103. package/lib/jsx/jsx-runtime.js +4 -4
  104. package/lib/jsx/streaming.js +1 -1
  105. package/lib/jsx-CUQgttxU.js +47 -0
  106. package/lib/jsx-dev-runtime-B4WTyq8r.js +14 -0
  107. package/lib/{jsx-dev-runtime-CWLRA2F3.js → jsx-dev-runtime-BLfA-EhI.js} +2 -2
  108. package/lib/jsx-dev-runtime-CGt4kNYr.d.ts +6 -0
  109. package/lib/jsx-renderer-CfifM4hT.js +99 -0
  110. package/lib/jsx-renderer.js +1 -98
  111. package/lib/jsx-runtime-COCRWBJ9.js +2 -0
  112. package/lib/jsx.js +5 -46
  113. package/lib/{jwk-LkzeoaU8.js → jwk-CDPpYQpD.js} +3 -3
  114. package/lib/jwk.js +1 -1
  115. package/lib/{jwt-BGJBw5AF.js → jwt-BXoIA2fQ.js} +2 -2
  116. package/lib/{jwt-CskdhEW9.js → jwt-lyS4Q3mT.js} +3 -3
  117. package/lib/jwt.js +2 -2
  118. package/lib/kv/db.d.ts +1 -10
  119. package/lib/kv/db.js +1 -56
  120. package/lib/kv/json.d.ts +1 -9
  121. package/lib/kv/json.js +1 -223
  122. package/lib/{language-D9a4zqvn.js → language-DeGRtLJt.js} +2 -2
  123. package/lib/language.js +1 -1
  124. package/lib/{linear-router-C3FPib6h.js → linear-router-CmZn_JLV.js} +2 -2
  125. package/lib/logger-Ckxu8diz.js +54 -0
  126. package/lib/logger.js +1 -53
  127. package/lib/loopback-only-BbasoF7R.js +90 -0
  128. package/lib/loopback-only.js +1 -89
  129. package/lib/method-not-allowed-TO2_hnfl.js +82 -0
  130. package/lib/method-not-allowed.js +1 -81
  131. package/lib/method-override-DnZKfJbV.js +103 -0
  132. package/lib/method-override.js +1 -102
  133. package/lib/middleware-BfG_fp8P.js +12 -0
  134. package/lib/mime-BnScXkQn.js +77 -0
  135. package/lib/mount-QkK_3NtJ.js +67 -0
  136. package/lib/mount.js +1 -66
  137. package/lib/node-server/conninfo.d.ts +1 -10
  138. package/lib/node-server/conninfo.js +1 -18
  139. package/lib/node-server/register.js +25 -2
  140. package/lib/node-server/serve-static.d.ts +1 -17
  141. package/lib/node-server/serve-static.js +1 -1
  142. package/lib/node-server.d.ts +2 -2
  143. package/lib/node-server.js +4 -4
  144. package/lib/open-BqK4D2Wn.d.ts +11 -0
  145. package/lib/open-But9E2rL.js +57 -0
  146. package/lib/open-CULVJgOc.d.ts +12 -0
  147. package/lib/open-Dp4sxspc.js +224 -0
  148. package/lib/open-T7kgbO2O.d.ts +27 -0
  149. package/lib/{pattern-router-HZxjDGEQ.js → pattern-router-CJBecJBd.js} +1 -1
  150. package/lib/powered-by-CXQNjNIX.js +24 -0
  151. package/lib/powered-by.js +1 -23
  152. package/lib/pretty-json-CeC1yvYt.js +39 -0
  153. package/lib/pretty-json.js +1 -38
  154. package/lib/{protocol-C37gPePD.d.ts → protocol-CR31M7K_.d.ts} +2 -0
  155. package/lib/protocol-Dt0AZBOz.js +213 -0
  156. package/lib/proxy-C2w_mH7k.js +121 -0
  157. package/lib/proxy.js +1 -120
  158. package/lib/quick.js +4 -4
  159. package/lib/{reg-exp-router-DC1R3qoY.js → reg-exp-router-C_WV84eV.js} +2 -2
  160. package/lib/request-DvMgGtlW.js +323 -0
  161. package/lib/request.js +1 -322
  162. package/lib/route-D8m-a2I1.js +126 -0
  163. package/lib/route.js +1 -125
  164. package/lib/router/linear-router.js +1 -1
  165. package/lib/router/pattern-router.js +1 -1
  166. package/lib/router/reg-exp-router.js +1 -1
  167. package/lib/router/smart-router.js +1 -1
  168. package/lib/router/trie-router.js +1 -1
  169. package/lib/router-Be_o6nFu.js +35 -0
  170. package/lib/router.js +1 -34
  171. package/lib/schema-validator-EK3cUUCt.js +22 -0
  172. package/lib/schema-validator.js +1 -21
  173. package/lib/{secure-headers-mP3j6Hm6.js → secure-headers-C5ZOcXqk.js} +1 -1
  174. package/lib/secure-headers.js +1 -1
  175. package/lib/serve-static-B7sqnftW.d.ts +18 -0
  176. package/lib/{serve-static-3uo_9rjF.js → serve-static-C1Nkj_xB.js} +2 -2
  177. package/lib/{serve-static-Cp5adzGo.js → serve-static-EiJf1zkO.js} +4 -4
  178. package/lib/serve-static.js +1 -1
  179. package/lib/{smart-router-C5CZ5XSM.js → smart-router-BjxkDQCE.js} +1 -1
  180. package/lib/{snapshot-text-BLz8nlSc.js → snapshot-text-CtQm3n8n.js} +23 -14
  181. package/lib/{src-dy3Fk9tA.js → src-BTBm4OPD.js} +6 -6
  182. package/lib/src-LX0E4ec4.js +117 -0
  183. package/lib/{ssg-BwjcA-Ge.js → ssg-BEl7MHqg.js} +5 -5
  184. package/lib/ssg.js +1 -1
  185. package/lib/stream-CzM_SHkq.js +88 -0
  186. package/lib/{streaming-DHST3Scg.js → streaming-BlDaph8U.js} +3 -3
  187. package/lib/streaming.js +1 -1
  188. package/lib/{test-websocket-D8WWqE5G.js → test-websocket-BLeEvybj.js} +2 -2
  189. package/lib/testing-BQx5T_UX.js +17 -0
  190. package/lib/testing.js +1 -16
  191. package/lib/timeout-FeqZ9A-0.js +42 -0
  192. package/lib/timeout.js +1 -41
  193. package/lib/tiny.js +2 -2
  194. package/lib/trailing-slash-DwTx-1sk.js +85 -0
  195. package/lib/trailing-slash.js +1 -84
  196. package/lib/{trie-router-CfFbapzL.js → trie-router-JydLKUa2.js} +2 -2
  197. package/lib/url-BPh4d6j-.js +197 -0
  198. package/lib/utils/accept.js +1 -192
  199. package/lib/utils/basic-auth.js +1 -19
  200. package/lib/utils/body.js +1 -93
  201. package/lib/utils/buffer.js +1 -50
  202. package/lib/utils/color.js +1 -36
  203. package/lib/utils/compress.d.ts +1 -10
  204. package/lib/utils/compress.js +1 -10
  205. package/lib/utils/concurrent.js +1 -29
  206. package/lib/utils/constants.js +1 -6
  207. package/lib/utils/cookie.js +1 -124
  208. package/lib/utils/crypto.js +1 -33
  209. package/lib/utils/encode.js +1 -32
  210. package/lib/utils/handler.js +1 -11
  211. package/lib/utils/html.js +1 -102
  212. package/lib/utils/ipaddr.js +1 -237
  213. package/lib/utils/jwt.js +1 -1
  214. package/lib/utils/mime.js +1 -76
  215. package/lib/utils/stream.js +1 -87
  216. package/lib/utils/url.js +1 -196
  217. package/lib/{validator-D84mq-dO.js → validator-Btiyt5VJ.js} +3 -3
  218. package/lib/validator.js +1 -1
  219. package/lib/websocket-DsyBIKVA.js +56 -0
  220. package/lib/ws.js +1 -55
  221. package/package.json +1 -1
  222. package/plugins/acryl-door/src/cli/acryl.ts +36 -5
  223. package/plugins/acryl-door/src/host/channel.ts +14 -6
  224. package/plugins/acryl-door/src/host/chat-tools.ts +2 -0
  225. package/plugins/acryl-door/src/host/door.ts +97 -12
  226. package/plugins/acryl-door/src/host/page-routes.ts +3 -1
  227. package/plugins/acryl-door/src/host/shell.ts +147 -0
  228. package/plugins/acryl-door/src/host/shim.ts +39 -3
  229. package/plugins/acryl-door/src/host/verbs.ts +39 -2
  230. package/plugins/acryl-door/src/index.ts +22 -3
  231. package/plugins/acryl-door/src/page/channel.ts +3 -1
  232. package/plugins/acryl-door/src/page/index.ts +2 -1
  233. package/plugins/acryl-door/src/page/protocol.ts +2 -0
  234. package/plugins/acryl-door/src/shell/protocol.ts +130 -0
  235. package/plugins/acryl-node-server/src/register.ts +29 -2
  236. package/plugins/agent/model.ts +6 -1
  237. package/plugins/agent/tools.ts +12 -2
  238. package/plugins/agent-panel/client/describe.ts +44 -0
  239. package/plugins/agent-panel/client/main.ts +4 -1
  240. package/plugins/agent-panel/headless.ts +29 -8
  241. package/plugins/agent-panel/index.ts +22 -10
  242. package/plugins/agent-panel/launch.ts +67 -0
  243. package/plugins/desktop/attach.ts +72 -0
  244. package/plugins/desktop/door-shell.ts +379 -0
  245. package/plugins/desktop/main.ts +82 -15
  246. package/plugins/extensions/index.ts +3 -4
  247. package/plugins/extensions/stage.ts +33 -5
  248. package/plugins/openai/index.ts +1 -1
  249. package/templates/agent/AGENTS.md +4 -0
  250. package/templates/agent/blend.yaml +4 -1
  251. package/templates/agent/src/app.ts +2 -0
  252. package/templates/agent/src/routes.ts +1 -1
  253. package/templates/agent/template.json +1 -0
  254. package/templates/agent/test/builder.test.ts +26 -21
  255. package/lib/helpers-CvTauQT5.js +0 -5
  256. package/lib/middleware-CPQ8himN.js +0 -12
  257. package/plugins/acryl-router/src/connection.d.ts +0 -25
  258. package/plugins/acryl-router/src/core/app-base.d.ts +0 -215
  259. package/plugins/acryl-router/src/core/app.d.ts +0 -19
  260. package/plugins/acryl-router/src/core/client/client.d.ts +0 -4
  261. package/plugins/acryl-router/src/core/client/fetch-result-please.d.ts +0 -35
  262. package/plugins/acryl-router/src/core/client/index.d.ts +0 -7
  263. package/plugins/acryl-router/src/core/client/types.d.ts +0 -229
  264. package/plugins/acryl-router/src/core/client/utils.d.ts +0 -18
  265. package/plugins/acryl-router/src/core/compose.d.ts +0 -14
  266. package/plugins/acryl-router/src/core/context.d.ts +0 -443
  267. package/plugins/acryl-router/src/core/helper/accepts/accepts.d.ts +0 -30
  268. package/plugins/acryl-router/src/core/helper/accepts/index.d.ts +0 -5
  269. package/plugins/acryl-router/src/core/helper/conninfo/index.d.ts +0 -5
  270. package/plugins/acryl-router/src/core/helper/conninfo/types.d.ts +0 -36
  271. package/plugins/acryl-router/src/core/helper/cookie/index.d.ts +0 -25
  272. package/plugins/acryl-router/src/core/helper/css/common.d.ts +0 -59
  273. package/plugins/acryl-router/src/core/helper/css/index.d.ts +0 -80
  274. package/plugins/acryl-router/src/core/helper/dev/index.d.ts +0 -20
  275. package/plugins/acryl-router/src/core/helper/factory/index.d.ts +0 -82
  276. package/plugins/acryl-router/src/core/helper/html/index.d.ts +0 -8
  277. package/plugins/acryl-router/src/core/helper/proxy/index.d.ts +0 -73
  278. package/plugins/acryl-router/src/core/helper/route/index.d.ts +0 -75
  279. package/plugins/acryl-router/src/core/helper/ssg/index.d.ts +0 -7
  280. package/plugins/acryl-router/src/core/helper/ssg/middleware.d.ts +0 -44
  281. package/plugins/acryl-router/src/core/helper/ssg/plugins.d.ts +0 -27
  282. package/plugins/acryl-router/src/core/helper/ssg/ssg.d.ts +0 -91
  283. package/plugins/acryl-router/src/core/helper/ssg/utils.d.ts +0 -16
  284. package/plugins/acryl-router/src/core/helper/streaming/index.d.ts +0 -8
  285. package/plugins/acryl-router/src/core/helper/streaming/sse.d.ts +0 -13
  286. package/plugins/acryl-router/src/core/helper/streaming/stream.d.ts +0 -3
  287. package/plugins/acryl-router/src/core/helper/streaming/text.d.ts +0 -3
  288. package/plugins/acryl-router/src/core/helper/streaming/utils.d.ts +0 -1
  289. package/plugins/acryl-router/src/core/helper/testing/index.d.ts +0 -13
  290. package/plugins/acryl-router/src/core/helper/websocket/index.d.ts +0 -70
  291. package/plugins/acryl-router/src/core/http-exception.d.ts +0 -58
  292. package/plugins/acryl-router/src/core/index.d.ts +0 -37
  293. package/plugins/acryl-router/src/core/jsx/base.d.ts +0 -61
  294. package/plugins/acryl-router/src/core/jsx/children.d.ts +0 -9
  295. package/plugins/acryl-router/src/core/jsx/components.d.ts +0 -15
  296. package/plugins/acryl-router/src/core/jsx/constants.d.ts +0 -6
  297. package/plugins/acryl-router/src/core/jsx/context.d.ts +0 -51
  298. package/plugins/acryl-router/src/core/jsx/dom/components.d.ts +0 -10
  299. package/plugins/acryl-router/src/core/jsx/dom/context.d.ts +0 -3
  300. package/plugins/acryl-router/src/core/jsx/dom/css.d.ts +0 -83
  301. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.d.ts +0 -39
  302. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.d.ts +0 -16
  303. package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.d.ts +0 -8
  304. package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.d.ts +0 -7
  305. package/plugins/acryl-router/src/core/jsx/dom/render.d.ts +0 -73
  306. package/plugins/acryl-router/src/core/jsx/dom/utils.d.ts +0 -1
  307. package/plugins/acryl-router/src/core/jsx/hooks/index.d.ts +0 -47
  308. package/plugins/acryl-router/src/core/jsx/index.d.ts +0 -76
  309. package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.d.ts +0 -6
  310. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.d.ts +0 -13
  311. package/plugins/acryl-router/src/core/jsx/intrinsic-elements.d.ts +0 -735
  312. package/plugins/acryl-router/src/core/jsx/streaming.d.ts +0 -36
  313. package/plugins/acryl-router/src/core/jsx/types.d.ts +0 -36
  314. package/plugins/acryl-router/src/core/jsx/utils.d.ts +0 -4
  315. package/plugins/acryl-router/src/core/request/constants.d.ts +0 -1
  316. package/plugins/acryl-router/src/core/request.d.ts +0 -306
  317. package/plugins/acryl-router/src/core/router/reg-exp-router/index.d.ts +0 -6
  318. package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.d.ts +0 -7
  319. package/plugins/acryl-router/src/core/router/reg-exp-router/node.d.ts +0 -13
  320. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.d.ts +0 -17
  321. package/plugins/acryl-router/src/core/router/reg-exp-router/router.d.ts +0 -11
  322. package/plugins/acryl-router/src/core/router/reg-exp-router/trie.d.ts +0 -8
  323. package/plugins/acryl-router/src/core/router/smart-router/index.d.ts +0 -5
  324. package/plugins/acryl-router/src/core/router/smart-router/router.d.ts +0 -11
  325. package/plugins/acryl-router/src/core/router/trie-router/index.d.ts +0 -5
  326. package/plugins/acryl-router/src/core/router/trie-router/node.d.ts +0 -6
  327. package/plugins/acryl-router/src/core/router/trie-router/router.d.ts +0 -7
  328. package/plugins/acryl-router/src/core/router/utils.d.ts +0 -1
  329. package/plugins/acryl-router/src/core/router.d.ts +0 -97
  330. package/plugins/acryl-router/src/core/types.d.ts +0 -573
  331. package/plugins/acryl-router/src/core/utils/accept.d.ts +0 -6
  332. package/plugins/acryl-router/src/core/utils/body.d.ts +0 -79
  333. package/plugins/acryl-router/src/core/utils/buffer.d.ts +0 -17
  334. package/plugins/acryl-router/src/core/utils/color.d.ts +0 -21
  335. package/plugins/acryl-router/src/core/utils/concurrent.d.ts +0 -11
  336. package/plugins/acryl-router/src/core/utils/constants.d.ts +0 -4
  337. package/plugins/acryl-router/src/core/utils/cookie.d.ts +0 -41
  338. package/plugins/acryl-router/src/core/utils/crypto.d.ts +0 -15
  339. package/plugins/acryl-router/src/core/utils/handler.d.ts +0 -6
  340. package/plugins/acryl-router/src/core/utils/headers.d.ts +0 -8
  341. package/plugins/acryl-router/src/core/utils/html.d.ts +0 -44
  342. package/plugins/acryl-router/src/core/utils/http-status.d.ts +0 -32
  343. package/plugins/acryl-router/src/core/utils/mime.d.ts +0 -70
  344. package/plugins/acryl-router/src/core/utils/stream.d.ts +0 -31
  345. package/plugins/acryl-router/src/core/utils/types.d.ts +0 -128
  346. package/plugins/acryl-router/src/core/utils/url.d.ts +0 -44
  347. package/plugins/acryl-router/src/core/validator/index.d.ts +0 -7
  348. package/plugins/acryl-router/src/core/validator/utils.d.ts +0 -29
  349. package/plugins/acryl-router/src/core/validator/validator.d.ts +0 -16
  350. package/plugins/acryl-router/src/helpers/index.d.ts +0 -17
  351. package/plugins/acryl-router/src/helpers/schema-validator.d.ts +0 -54
@@ -1,13 +1,280 @@
1
- import { t as Acryl } from "../app-BM0rTCp7.js";
2
- import { n as clientModules } from "../client-modules-CLvWOjbd.js";
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-BLz8nlSc.js";
5
- import "../src-dy3Fk9tA.js";
1
+ import { t as Acryl } from "../app-CQcGWjrk.js";
2
+ import { r as streamSSE } from "../streaming-BlDaph8U.js";
3
+ import "../helpers-BEjcdbgw.js";
4
+ import { n as clientModules } from "../client-modules-drj4mjhj.js";
5
+ import { a as MAX_WAIT_MS, l as UiControlError, p as parseUiRequest, r as MAX_SNAPSHOT_NODES } from "../protocol-Dt0AZBOz.js";
6
+ 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-CtQm3n8n.js";
7
+ import "../src-BTBm4OPD.js";
6
8
  import { isAbsolute, join, relative, resolve } from "node:path";
7
9
  import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
8
10
  import z from "@deepseek-ai/schemastery";
9
11
  import { randomBytes } from "node:crypto";
10
12
  import { spawnSync } from "node:child_process";
13
+ //#region ../../plugins/acryl-door/src/shell/protocol.ts
14
+ var ShellError = class extends Error {
15
+ code;
16
+ constructor(code, message) {
17
+ super(message);
18
+ this.code = code;
19
+ this.name = "ShellError";
20
+ }
21
+ };
22
+ /** A path of the app, as `act navigate` takes one: one leading `/`, and no other site. */
23
+ const APP_PATH = /^\/(?![/\\])[^\s\\]*$/u;
24
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
25
+ function only(value, allowed) {
26
+ const extra = Object.keys(value).filter((key) => key !== "op" && !allowed.includes(key));
27
+ if (extra.length > 0) throw new ShellError("invalid", `unknown field ${extra.join(", ")}`);
28
+ }
29
+ function windowId(value) {
30
+ if (typeof value.id !== "number" || !Number.isSafeInteger(value.id) || value.id < 1) throw new ShellError("invalid", "id must be the number `window list` prints for the window");
31
+ return value.id;
32
+ }
33
+ function size(value, key) {
34
+ const number = value[key];
35
+ if (typeof number !== "number" || !Number.isInteger(number) || number < 200 || number > 1e4) throw new ShellError("invalid", `${key} must be a whole number of pixels from 200 to 10000`);
36
+ return number;
37
+ }
38
+ /** @throws ShellError `invalid`. */
39
+ function parseShellRequest(op, input) {
40
+ const value = isRecord(input) ? input : {};
41
+ switch (op) {
42
+ case "window-list":
43
+ only(value, []);
44
+ return { op };
45
+ case "window-open":
46
+ only(value, ["path"]);
47
+ if (value.path === void 0) return { op };
48
+ if (typeof value.path !== "string" || !APP_PATH.test(value.path)) throw new ShellError("invalid", "path must be a path of this app, such as /settings (it starts with one /, and names no other site)");
49
+ return {
50
+ op,
51
+ path: value.path
52
+ };
53
+ case "window-focus":
54
+ only(value, ["id"]);
55
+ return {
56
+ op,
57
+ id: windowId(value)
58
+ };
59
+ case "window-close":
60
+ only(value, ["id", "force"]);
61
+ if (value.force !== void 0 && typeof value.force !== "boolean") throw new ShellError("invalid", "force must be true or false");
62
+ return {
63
+ op,
64
+ id: windowId(value),
65
+ ...value.force === true ? { force: true } : {}
66
+ };
67
+ case "window-resize":
68
+ only(value, [
69
+ "id",
70
+ "width",
71
+ "height"
72
+ ]);
73
+ return {
74
+ op,
75
+ id: windowId(value),
76
+ width: size(value, "width"),
77
+ height: size(value, "height")
78
+ };
79
+ case "menu-list":
80
+ only(value, []);
81
+ return { op };
82
+ case "menu-click":
83
+ only(value, ["path", "force"]);
84
+ if (typeof value.path !== "string" || value.path.trim() === "" || value.path.length > 300) throw new ShellError("invalid", "path is the labels from the top of the menu joined with >, e.g. \"File>New Note\" (`menu list` prints them)");
85
+ if (value.force !== void 0 && typeof value.force !== "boolean") throw new ShellError("invalid", "force must be true or false");
86
+ return {
87
+ op,
88
+ path: value.path,
89
+ ...value.force === true ? { force: true } : {}
90
+ };
91
+ }
92
+ }
93
+ /** @param value - one parsed POST body from the shell; null when it is not a well-formed answer. */
94
+ function parseShellAnswer(value) {
95
+ if (!isRecord(value) || typeof value.id !== "number" || !Number.isSafeInteger(value.id)) return null;
96
+ if (value.t === "result" && isRecord(value.value)) return {
97
+ t: "result",
98
+ id: value.id,
99
+ value: value.value
100
+ };
101
+ if (value.t === "error" && typeof value.code === "string" && typeof value.message === "string") return {
102
+ t: "error",
103
+ id: value.id,
104
+ code: value.code.slice(0, 40),
105
+ message: value.message.slice(0, 500)
106
+ };
107
+ return null;
108
+ }
109
+ /** Whatever code the shell used, as one the door knows (an unknown code is `failed`). */
110
+ function shellErrorCode(code) {
111
+ return [
112
+ "invalid",
113
+ "no-such-window",
114
+ "last-window",
115
+ "no-such-menu-item",
116
+ "disabled",
117
+ "quits-app"
118
+ ].find((known) => known === code) ?? "failed";
119
+ }
120
+ //#endregion
121
+ //#region ../../plugins/acryl-door/src/host/shell.ts
122
+ /**
123
+ * The desktop shell's channel: the Electron main process of `npm run desktop`, which reaches the door as a page does (an event stream down, a POST up) and answers calls about
124
+ * the app's windows and menu. One shell at a time: a new one replaces the old (the dev server restarted and the shell came back). It needs this run's secret, so no page can open it.
125
+ */
126
+ const CALL_TIMEOUT_MS = 1e4;
127
+ /** What a verb says when no desktop shell is attached: one line a model can act on. */
128
+ const NO_DESKTOP = "no desktop window is attached; run npm run desktop";
129
+ var ShellChannel = class {
130
+ current;
131
+ pending = /* @__PURE__ */ new Map();
132
+ nextId = 1;
133
+ closed = false;
134
+ attachWaiters = /* @__PURE__ */ new Set();
135
+ get connected() {
136
+ return this.current !== void 0;
137
+ }
138
+ /** @returns the function to call when this stream ends. */
139
+ attach(stream) {
140
+ const previous = this.current;
141
+ const connection = { stream };
142
+ this.current = connection;
143
+ if (previous !== void 0) {
144
+ this.failAll(new ShellError("disconnected", "the desktop shell reconnected before it answered"));
145
+ previous.stream.close();
146
+ }
147
+ for (const waiter of [...this.attachWaiters]) waiter();
148
+ return () => {
149
+ if (this.current !== connection) return;
150
+ this.current = void 0;
151
+ this.failAll(new ShellError("disconnected", "the desktop shell went away before it answered"));
152
+ };
153
+ }
154
+ /** A promise for the shell connecting (it reconnects after the dev server restarted). */
155
+ waitForShell(timeoutMs) {
156
+ if (this.current !== void 0) return Promise.resolve(true);
157
+ return new Promise((resolve) => {
158
+ const done = (value) => {
159
+ clearTimeout(timer);
160
+ this.attachWaiters.delete(waiter);
161
+ resolve(value);
162
+ };
163
+ const waiter = () => {
164
+ done(true);
165
+ };
166
+ const timer = setTimeout(() => {
167
+ done(false);
168
+ }, timeoutMs);
169
+ this.attachWaiters.add(waiter);
170
+ });
171
+ }
172
+ receive(body) {
173
+ const answer = parseShellAnswer(body);
174
+ if (answer === null) return;
175
+ if (answer.t === "result") this.settle(answer.id, answer.value);
176
+ else this.settle(answer.id, void 0, new ShellError(shellErrorCode(answer.code), answer.message));
177
+ }
178
+ /** @throws ShellError `no-desktop` when no shell is attached, else what the shell refused with. */
179
+ call(request, options = {}) {
180
+ if (this.closed) return Promise.reject(new ShellError("unloaded", "the door was unloaded"));
181
+ const target = this.current;
182
+ if (target === void 0) return Promise.reject(new ShellError("no-desktop", NO_DESKTOP));
183
+ const signal = options.signal;
184
+ if (signal?.aborted === true) return Promise.reject(new ShellError("aborted", "the call was cancelled"));
185
+ const id = this.nextId;
186
+ this.nextId += 1;
187
+ return new Promise((resolve, reject) => {
188
+ const timer = setTimeout(() => {
189
+ this.settle(id, void 0, new ShellError("timeout", "the desktop shell did not answer in time"));
190
+ }, options.timeoutMs ?? CALL_TIMEOUT_MS);
191
+ const onAbort = () => {
192
+ this.settle(id, void 0, new ShellError("aborted", "the call was cancelled"));
193
+ };
194
+ signal?.addEventListener("abort", onAbort, { once: true });
195
+ this.pending.set(id, {
196
+ resolve,
197
+ reject,
198
+ timer,
199
+ cleanup: () => {
200
+ signal?.removeEventListener("abort", onAbort);
201
+ }
202
+ });
203
+ try {
204
+ target.stream.send(JSON.stringify({
205
+ t: "call",
206
+ id,
207
+ request
208
+ }));
209
+ } catch {
210
+ this.settle(id, void 0, new ShellError("disconnected", "the desktop shell could not be reached"));
211
+ }
212
+ });
213
+ }
214
+ close() {
215
+ this.closed = true;
216
+ this.failAll(new ShellError("unloaded", "the door was unloaded"));
217
+ this.current?.stream.close();
218
+ this.current = void 0;
219
+ }
220
+ failAll(failure) {
221
+ for (const id of [...this.pending.keys()]) this.settle(id, void 0, failure);
222
+ }
223
+ settle(id, value, failure) {
224
+ const entry = this.pending.get(id);
225
+ if (entry === void 0) return;
226
+ this.pending.delete(id);
227
+ clearTimeout(entry.timer);
228
+ entry.cleanup();
229
+ if (failure !== void 0) entry.reject(failure);
230
+ else entry.resolve(value);
231
+ }
232
+ };
233
+ /** How often a `ping` is written, so the shell knows a silent stream died and reconnects. */
234
+ const SHELL_HEARTBEAT_MS = 15e3;
235
+ /** `GET <prefix>/shell/events` is the shell's stream and `POST <prefix>/shell/answer` carries its answers: both need this run's secret. */
236
+ function shellRoutes(prefix, secret, channel, heartbeatMs = SHELL_HEARTBEAT_MS) {
237
+ return new Acryl().get(`${prefix}/shell/events`, operatorOnly(secret), (c) => {
238
+ const signal = c.req.raw.signal;
239
+ return streamSSE(c, async (stream) => {
240
+ let end = () => {};
241
+ const ended = new Promise((resolve) => {
242
+ end = resolve;
243
+ });
244
+ stream.onAbort(end);
245
+ signal.addEventListener("abort", end, { once: true });
246
+ const write = (event, data) => {
247
+ stream.writeSSE({
248
+ event,
249
+ data
250
+ }).catch(end);
251
+ };
252
+ const detach = channel.attach({
253
+ send: (data) => {
254
+ write("call", data);
255
+ },
256
+ close: end
257
+ });
258
+ const beat = setInterval(() => {
259
+ write("ping", "");
260
+ }, heartbeatMs);
261
+ try {
262
+ write("ready", "");
263
+ await ended;
264
+ } finally {
265
+ clearInterval(beat);
266
+ signal.removeEventListener("abort", end);
267
+ detach();
268
+ }
269
+ });
270
+ }).post(`${prefix}/shell/answer`, operatorOnly(secret), jsonBody, async (c) => {
271
+ const body = await readJson(c);
272
+ if (body === INVALID_JSON || typeof body !== "object" || body === null) return c.json(error("invalid request body"), 400);
273
+ channel.receive(body);
274
+ return c.json({ ok: true });
275
+ });
276
+ }
277
+ //#endregion
11
278
  //#region ../../plugins/acryl-door/src/host/verbs.ts
12
279
  /**
13
280
  * The one verb list (spec 049 slice 13). Every face of the door reads it: the operator client (`acryl <verb>`, its `verbs` and `brief`),
@@ -23,7 +290,14 @@ const VERBS = [
23
290
  "plugin-remove",
24
291
  "plugin-reload",
25
292
  "tool-list",
26
- "tool-call"
293
+ "tool-call",
294
+ "window-list",
295
+ "window-open",
296
+ "window-focus",
297
+ "window-close",
298
+ "window-resize",
299
+ "menu-list",
300
+ "menu-click"
27
301
  ];
28
302
  const none = {
29
303
  type: "object",
@@ -37,7 +311,7 @@ const VERB_SPECS = {
37
311
  arguments: none
38
312
  },
39
313
  "look": {
40
- usage: "look [--cursor <n>] [--max-nodes <n>]: the page as text, with refs",
314
+ usage: "look [--window <n>] [--cursor <n>] [--max-nodes <n>]: the page as text, with refs",
41
315
  description: "The open page as text: its roles, names, values and visible text, each with a ref to act on. A long page comes in parts (cursor).",
42
316
  arguments: {
43
317
  type: "object",
@@ -51,6 +325,11 @@ const VERB_SPECS = {
51
325
  type: "integer",
52
326
  minimum: 1,
53
327
  maximum: MAX_SNAPSHOT_NODES
328
+ },
329
+ window: {
330
+ type: "integer",
331
+ minimum: 1,
332
+ description: "a desktop window (the id window-list prints); default: the page used last"
54
333
  }
55
334
  },
56
335
  additionalProperties: false
@@ -134,6 +413,11 @@ const VERB_SPECS = {
134
413
  type: "integer",
135
414
  minimum: 0,
136
415
  maximum: MAX_WAIT_MS
416
+ },
417
+ window: {
418
+ type: "integer",
419
+ minimum: 1,
420
+ description: "a desktop window (the id window-list prints); default: the page used last"
137
421
  }
138
422
  },
139
423
  required: ["op"],
@@ -190,6 +474,109 @@ const VERB_SPECS = {
190
474
  required: ["tool"],
191
475
  additionalProperties: false
192
476
  }
477
+ },
478
+ "window-list": {
479
+ usage: "window list: the windows of the desktop app",
480
+ description: "The windows of the desktop app: id, title, path, size, and which one is focused. Needs the desktop app (npm run desktop).",
481
+ arguments: none
482
+ },
483
+ "window-open": {
484
+ usage: "window open [--path <path of the app>]: open a new window",
485
+ description: "Open a new window of the desktop app on a path of the app (default: its start page). Answers with the new window's id; look and act take it as window.",
486
+ arguments: {
487
+ type: "object",
488
+ properties: { path: {
489
+ type: "string",
490
+ description: "a path of this app, e.g. /settings"
491
+ } },
492
+ additionalProperties: false
493
+ }
494
+ },
495
+ "window-focus": {
496
+ usage: "window focus --id <n>: bring a window to the front",
497
+ description: "Bring a window of the desktop app to the front. Look and act without a window then drive it.",
498
+ arguments: {
499
+ type: "object",
500
+ properties: { id: {
501
+ type: "integer",
502
+ minimum: 1,
503
+ description: "the id window-list prints"
504
+ } },
505
+ required: ["id"],
506
+ additionalProperties: false
507
+ }
508
+ },
509
+ "window-close": {
510
+ usage: "window close --id <n> [--force]: close a window",
511
+ description: "Close a window of the desktop app. The last window cannot be closed without force, because that ends the desktop app.",
512
+ arguments: {
513
+ type: "object",
514
+ properties: {
515
+ id: {
516
+ type: "integer",
517
+ minimum: 1
518
+ },
519
+ force: {
520
+ type: "boolean",
521
+ description: "allow closing the last window, which ends the desktop app"
522
+ }
523
+ },
524
+ required: ["id"],
525
+ additionalProperties: false
526
+ }
527
+ },
528
+ "window-resize": {
529
+ usage: "window resize --id <n> --width <px> --height <px>",
530
+ description: "Resize a window of the desktop app.",
531
+ arguments: {
532
+ type: "object",
533
+ properties: {
534
+ id: {
535
+ type: "integer",
536
+ minimum: 1
537
+ },
538
+ width: {
539
+ type: "integer",
540
+ minimum: 200,
541
+ maximum: 1e4
542
+ },
543
+ height: {
544
+ type: "integer",
545
+ minimum: 200,
546
+ maximum: 1e4
547
+ }
548
+ },
549
+ required: [
550
+ "id",
551
+ "width",
552
+ "height"
553
+ ],
554
+ additionalProperties: false
555
+ }
556
+ },
557
+ "menu-list": {
558
+ usage: "menu list: the application menu",
559
+ description: "The desktop app's application menu as paths such as File>New Note, with the disabled items marked.",
560
+ arguments: none
561
+ },
562
+ "menu-click": {
563
+ usage: "menu click --path \"File>New Note\" [--force]: press a menu item",
564
+ description: "Press an item of the desktop app's application menu by its path (labels joined with >, as menu-list prints them). A disabled item is refused, and so is one that ends the app (Quit, Close Window on the last window) unless force is set.",
565
+ arguments: {
566
+ type: "object",
567
+ properties: {
568
+ path: {
569
+ type: "string",
570
+ description: "e.g. File>New Note"
571
+ },
572
+ force: {
573
+ type: "boolean",
574
+ description: "allow an item that ends the desktop app"
575
+ }
576
+ },
577
+ required: ["path"],
578
+ additionalProperties: false
579
+ }
193
580
  }
194
581
  };
195
582
  /** A verb's name in the `tools` registry the built-in chat reads: prefixed, so it never meets an app tool's name. */
@@ -209,6 +596,12 @@ var DoorError = class extends Error {
209
596
  this.name = "DoorError";
210
597
  }
211
598
  };
599
+ /** A shell answer as text an agent reads: windows and menu items one to a line. */
600
+ function renderShell(result) {
601
+ if ("windows" in result) return result.windows.length === 0 ? "no windows are open" : result.windows.map((window) => `${String(window.id)} ${JSON.stringify(window.title)} ${window.path} ${String(window.width)}x${String(window.height)}${window.focused ? " focused" : ""}`).join("\n");
602
+ if ("items" in result) return result.items.length === 0 ? "the app has no menu" : result.items.map((item) => `${item.path}${item.enabled ? "" : " (disabled)"}${item.ends === true ? " (ends the app; needs force)" : ""}`).join("\n");
603
+ return result.detail;
604
+ }
212
605
  /** The files an agent should read first, when the app has them. */
213
606
  const SELF_KNOWLEDGE = [
214
607
  "AGENTS.md",
@@ -260,6 +653,13 @@ var Door = class {
260
653
  };
261
654
  }
262
655
  case "tool-call": return this.callTool(input);
656
+ case "window-list":
657
+ case "window-open":
658
+ case "window-focus":
659
+ case "window-close":
660
+ case "window-resize":
661
+ case "menu-list":
662
+ case "menu-click": return this.desktop(verb, input, signal);
263
663
  default: throw new DoorError("unknown-verb", `unknown verb ${JSON.stringify(verb)}; the verbs are ${VERBS.join(", ")}`);
264
664
  }
265
665
  }
@@ -289,20 +689,22 @@ var Door = class {
289
689
  };
290
690
  }
291
691
  look(input, signal) {
692
+ const { window, rest } = this.windowOf(input);
292
693
  const request = this.parse({
293
- ...input,
694
+ ...rest,
294
695
  op: "snapshot"
295
696
  });
296
- return this.callPage("look", request, signal);
697
+ return this.callPage("look", request, signal, window);
297
698
  }
298
699
  async act(input, signal) {
299
700
  if (input.op === "snapshot") throw new DoorError("invalid", "use look to see the page");
300
- const request = this.parse(input);
301
- if (request.op !== "reload" && request.op !== "navigate") return await this.callPage(`act ${String(input.op)}`, request, signal);
701
+ const { window, rest } = this.windowOf(input);
702
+ const request = this.parse(rest);
703
+ if (request.op !== "reload" && request.op !== "navigate") return await this.callPage(`act ${String(input.op)}`, request, signal, window);
302
704
  const back = this.deps.channel.expectAttach(this.deps.pageBackMs ?? PAGE_BACK_MS);
303
705
  let answer;
304
706
  try {
305
- answer = await this.callPage(`act ${request.op}`, request, signal);
707
+ answer = await this.callPage(`act ${request.op}`, request, signal, window);
306
708
  } catch (cause) {
307
709
  if (!(cause instanceof UiControlError && cause.code === "disconnected")) throw cause;
308
710
  answer = {
@@ -311,9 +713,95 @@ var Door = class {
311
713
  };
312
714
  }
313
715
  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?`);
716
+ const landed = request.op === "navigate" ? await this.statusAt(request.path) : void 0;
314
717
  return {
315
718
  ...answer,
316
- detail: `${answer.detail ?? "done"}; the page is back: look again`
719
+ detail: `${answer.detail ?? "done"}${landed === void 0 ? "" : `; ${landed}`}; the page is back: look again`
720
+ };
721
+ }
722
+ /**
723
+ * 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.
724
+ * 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.
725
+ */
726
+ async statusAt(path) {
727
+ const base = this.deps.app.url;
728
+ if (base === void 0) return void 0;
729
+ try {
730
+ const response = await fetch(new URL(path, base), {
731
+ headers: { accept: "text/html" },
732
+ signal: AbortSignal.timeout(3e3)
733
+ });
734
+ await response.body?.cancel();
735
+ return response.ok ? `${response.status} at ${path}` : `${response.status} at ${path}: the app serves no page there, look at what is shown and navigate to a path it does`;
736
+ } catch {
737
+ return;
738
+ }
739
+ }
740
+ /**
741
+ * The windows and the menu of the desktop app, through the shell. Every call is audited with the window it was about; a call with no shell attached answers one line that says what to run;
742
+ * closing the last window is refused unless the call says `force` (it ends the desktop app, and the session an agent works in with it).
743
+ */
744
+ async desktop(verb, input, signal) {
745
+ const shell = this.deps.shell;
746
+ let request;
747
+ try {
748
+ request = parseShellRequest(verb, input);
749
+ } catch (cause) {
750
+ throw new DoorError("invalid", cause instanceof Error ? cause.message : "invalid request");
751
+ }
752
+ const windowId = "id" in request ? request.id : void 0;
753
+ const record = (outcome, detail, windowOf) => {
754
+ const id = windowOf ?? windowId;
755
+ this.deps.audit.record({
756
+ at: (/* @__PURE__ */ new Date()).toISOString(),
757
+ tool: verb,
758
+ ...request.op === "menu-click" ? { target: {
759
+ role: "menuitem",
760
+ name: request.path
761
+ } } : {},
762
+ ...id === void 0 ? {} : { window: id },
763
+ outcome,
764
+ ...detail === void 0 ? {} : { detail },
765
+ approval: "token"
766
+ });
767
+ };
768
+ try {
769
+ if (shell === void 0 || !shell.connected) throw new ShellError("no-desktop", NO_DESKTOP);
770
+ if (request.op === "window-close" && request.force !== true) {
771
+ const { windows } = await shell.call({ op: "window-list" }, { signal });
772
+ if (windows.length === 1 && windows[0]?.id === request.id) throw new ShellError("last-window", `window ${String(request.id)} is the app's last window: closing it ends the desktop app. Open another window first, or pass force to end it on purpose`);
773
+ }
774
+ const result = await shell.call(request, { signal });
775
+ record("ok", void 0, "id" in result ? result.id : void 0);
776
+ return {
777
+ value: result,
778
+ text: renderShell(result)
779
+ };
780
+ } catch (cause) {
781
+ if (!(cause instanceof ShellError)) throw cause;
782
+ record([
783
+ "invalid",
784
+ "no-such-window",
785
+ "last-window",
786
+ "no-such-menu-item",
787
+ "disabled",
788
+ "quits-app",
789
+ "no-desktop"
790
+ ].includes(cause.code) ? "refused" : "failed", cause.code);
791
+ throw new DoorError(cause.code, cause.message);
792
+ }
793
+ }
794
+ /** `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. */
795
+ windowOf(input) {
796
+ const { window, ...rest } = input;
797
+ if (window === void 0) return {
798
+ window: void 0,
799
+ rest
800
+ };
801
+ if (typeof window !== "number" || !Number.isSafeInteger(window) || window < 1) throw new DoorError("invalid", "window must be the number `window list` prints for the window");
802
+ return {
803
+ window,
804
+ rest
317
805
  };
318
806
  }
319
807
  parse(input) {
@@ -324,11 +812,12 @@ var Door = class {
324
812
  }
325
813
  }
326
814
  /** Run one call on the page and record it. The audit holds which control was touched and how it ended, never what was typed. */
327
- async callPage(verb, request, signal) {
815
+ async callPage(verb, request, signal, window) {
328
816
  const approval = "token";
329
817
  try {
330
818
  const value = await this.deps.channel.call(request, {
331
819
  signal,
820
+ ...window === void 0 ? {} : { window },
332
821
  ...request.op === "wait" ? { timeoutMs: (request.timeoutMs ?? 3e3) + 5e3 } : {}
333
822
  });
334
823
  const target = "target" in value ? value.target : void 0;
@@ -336,6 +825,7 @@ var Door = class {
336
825
  at: (/* @__PURE__ */ new Date()).toISOString(),
337
826
  tool: verb,
338
827
  ...target === void 0 ? {} : { target },
828
+ ...window === void 0 ? {} : { window },
339
829
  outcome: "ok",
340
830
  approval
341
831
  });
@@ -354,6 +844,7 @@ var Door = class {
354
844
  this.deps.audit.record({
355
845
  at: (/* @__PURE__ */ new Date()).toISOString(),
356
846
  tool: verb,
847
+ ...window === void 0 ? {} : { window },
357
848
  outcome: refused ? "refused" : "failed",
358
849
  detail: code,
359
850
  approval
@@ -426,6 +917,7 @@ function registerChatTools(tools, door) {
426
917
  try {
427
918
  for (const verb of VERBS) removers.push(tools.register(chatToolName(verb), {
428
919
  description: VERB_SPECS[verb].description,
920
+ parameters: VERB_SPECS[verb].arguments,
429
921
  run: async (input) => {
430
922
  try {
431
923
  return (await door.run(verb, input ?? {}, running.signal)).text;
@@ -715,11 +1207,41 @@ function windowsShim(root, target) {
715
1207
  ].map(cmdWord).join(" ");
716
1208
  return `@echo off\r\nsetlocal\r\nrem This app's door (acryl-door): reaches only the app running from this folder. Rewritten each time the app starts.\r\n${target.electron ? "set ELECTRON_RUN_AS_NODE=1\r\n" : ""}${words} %*\r\n`;
717
1209
  }
718
- function writeAtomically(file, content, mode) {
1210
+ /** The error codes with which Windows refuses to replace a file that a running process (a `cmd.exe` executing the batch file) has open. */
1211
+ const LOCKED = /* @__PURE__ */ new Set([
1212
+ "EPERM",
1213
+ "EBUSY",
1214
+ "EACCES"
1215
+ ]);
1216
+ const realFs = {
1217
+ rename: renameSync,
1218
+ pause: (ms) => {
1219
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1220
+ }
1221
+ };
1222
+ /**
1223
+ * Write `content` to `file` so a reader never sees half of it. A shim that already holds this content is left alone: a restart of the app (the dev server restarts on a change)
1224
+ * rewrites the same text, and on Windows replacing a batch file that an agent's `acryl` call is running fails with EPERM, which stopped the app from starting. A file that
1225
+ * is held while its content differs is retried for about a second, and then reported: the old shim stays (it still works), and the app starts.
1226
+ */
1227
+ function writeAtomically(file, content, mode, fs = realFs) {
1228
+ try {
1229
+ if (readFileSync(file, "utf8") === content) return;
1230
+ } catch {}
719
1231
  const temporary = `${file}.${String(process.pid)}.tmp`;
720
1232
  writeFileSync(temporary, content, { mode });
721
1233
  if (process.platform !== "win32") chmodSync(temporary, mode);
722
- renameSync(temporary, file);
1234
+ for (let attempt = 0;; attempt += 1) try {
1235
+ fs.rename(temporary, file);
1236
+ return;
1237
+ } catch (cause) {
1238
+ const code = cause.code;
1239
+ if (code === void 0 || !LOCKED.has(code) || attempt >= 10) {
1240
+ rmSync(temporary, { force: true });
1241
+ throw cause;
1242
+ }
1243
+ fs.pause(25 * (attempt + 1));
1244
+ }
723
1245
  }
724
1246
  /** Write `.acryl/bin/acryl` and `.acryl/bin/acryl.cmd` for the app at `root`. @returns the folder they are in. */
725
1247
  function writeShims(root, target) {
@@ -779,6 +1301,11 @@ function loopbackUrl(url) {
779
1301
  if (parsed.hostname === "0.0.0.0" || parsed.hostname === "[::]") parsed.hostname = "127.0.0.1";
780
1302
  return parsed.origin;
781
1303
  }
1304
+ const escapeHtml = (text) => text.replace(/[&<>"']/gu, (char) => `&#${char.charCodeAt(0)};`);
1305
+ /** The page of a path the app does not serve, development only: what is missing, a way back, and the door's script. */
1306
+ function notFoundPage(path, scriptTag) {
1307
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>404 Not Found</title></head><body><main><h1>404 Not Found</h1><p>This app has no page at <code>${escapeHtml(path)}</code>.</p><p><a href="/">Back to the app</a></p></main>${scriptTag}</body></html>`;
1308
+ }
782
1309
  function apply(ctx, rawConfig = {}) {
783
1310
  const config = Config(rawConfig);
784
1311
  if (process.env.ACRYL_DOOR === "off" || process.env.NODE_ENV === "production" && config.allowInProduction !== true) return;
@@ -790,8 +1317,10 @@ function apply(ctx, rawConfig = {}) {
790
1317
  };
791
1318
  const secret = newSecret();
792
1319
  const channel = new UiChannel();
1320
+ const shell = new ShellChannel();
793
1321
  const door = new Door({
794
1322
  channel,
1323
+ shell,
795
1324
  audit: new AuditLog(auditPath(stateDir(app.root))),
796
1325
  app: {
797
1326
  name: app.name,
@@ -806,11 +1335,20 @@ function apply(ctx, rawConfig = {}) {
806
1335
  ctx.effect(() => () => {
807
1336
  channel.close();
808
1337
  }, "acryl-door: page channel");
1338
+ ctx.effect(() => () => {
1339
+ shell.close();
1340
+ }, "acryl-door: desktop shell channel");
809
1341
  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
1342
  const tag = `<script type="module" src="${DOOR_PREFIX}/page.js"><\/script>`;
811
1343
  const addScript = async (c, next) => {
812
1344
  await next();
813
- if (!(c.res.headers.get("content-type") ?? "").includes("text/html")) return;
1345
+ if (!(c.res.headers.get("content-type") ?? "").includes("text/html")) {
1346
+ if (c.res.status === 404 && (c.req.header("accept") ?? "").includes("text/html") && !(c.res.headers.get("content-type") ?? "").includes("json")) c.res = new Response(notFoundPage(c.req.path, tag), {
1347
+ status: 404,
1348
+ headers: { "content-type": "text/html; charset=utf-8" }
1349
+ });
1350
+ return;
1351
+ }
814
1352
  const html = await c.res.text();
815
1353
  const headers = new Headers(c.res.headers);
816
1354
  headers.delete("content-length");
@@ -823,18 +1361,22 @@ function apply(ctx, rawConfig = {}) {
823
1361
  const router = ctx.router;
824
1362
  if (typeof router.wrap === "function") router.wrap(addScript);
825
1363
  else ctx.router.route("/", new Acryl().use("*", addScript));
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)));
1364
+ ctx.router.route("/", privateApi(DOOR_PREFIX, pageModule, pageChannelRoutes(channel, { prefix: DOOR_PREFIX }), shellRoutes(DOOR_PREFIX, secret, shell), operatorRoutes(DOOR_PREFIX, secret, door, reportError), mcpRoutes(DOOR_PREFIX, secret, door, reportError)));
827
1365
  ctx.inject(["tools"], (inner) => {
828
1366
  inner.effect(() => registerChatTools(inner.get("tools"), door), "acryl-door: the verbs in the tools registry");
829
1367
  });
830
1368
  ctx.effect(function* () {
831
1369
  yield ctx.reflect.provide("door", door);
832
1370
  writeDoorFile(app.root, loopbackUrl(server.url), secret);
833
- writeShims(app.root, shimTarget());
1371
+ try {
1372
+ writeShims(app.root, shimTarget());
1373
+ } catch (cause) {
1374
+ reportError("write .acryl/bin", cause);
1375
+ }
834
1376
  yield () => {
835
1377
  removeDoorFile(app.root, secret);
836
1378
  };
837
1379
  }, "acryl-door: the door service, .acryl/door.json and .acryl/bin/acryl");
838
1380
  }
839
1381
  //#endregion
840
- export { Config, DOOR_PREFIX, Door, DoorError, STATE_DIR, VERBS, VERB_SPECS, apply, chatToolName, doorFilePath, inject, mcpTools, name, readDoorFile };
1382
+ export { Config, DOOR_PREFIX, Door, DoorError, STATE_DIR, VERBS, VERB_SPECS, apply, chatToolName, doorFilePath, inject, mcpTools, name, notFoundPage, readDoorFile };