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.
- package/docs/framework/agents/index.md +7 -5
- package/lib/accept-CNNHu4Cf.js +193 -0
- package/lib/{accepts-XBuZPVRI.js → accepts-Btq83xPd.js} +1 -1
- package/lib/accepts.js +1 -1
- package/lib/acryl-base.js +1 -331
- package/lib/adapter-CaZ6FZUU.js +37 -0
- package/lib/adapter.js +1 -36
- package/lib/{app-BM0rTCp7.js → app-CQcGWjrk.js} +4 -4
- package/lib/app-base-CpRMZL4v.js +332 -0
- package/lib/{audit-D5z9uwOP.d.ts → audit-GcLtvNLV.d.ts} +4 -2
- package/lib/{base-NSN_SNv-.js → base-D_6kfOr-.js} +1 -1
- package/lib/basic-auth-C0HTRpXh.js +20 -0
- package/lib/basic-auth-DFLflXNe.js +95 -0
- package/lib/basic-auth.js +1 -94
- package/lib/bearer-auth-Cwxt-vgS.js +94 -0
- package/lib/bearer-auth.js +1 -93
- package/lib/body-D7f4e7w_.js +94 -0
- package/lib/body-limit-WmIjWRLt.js +66 -0
- package/lib/body-limit.js +1 -65
- package/lib/buffer-CkEOJCcY.js +51 -0
- package/lib/bun.js +3 -3
- package/lib/cache-B0bHH7s5.js +169 -0
- package/lib/cache.js +1 -168
- package/lib/{client-DDLXwhBo.js → client-BrMFnMEd.js} +1 -1
- package/lib/{client-modules-CLvWOjbd.js → client-modules-drj4mjhj.js} +4 -4
- package/lib/client.js +1 -1
- package/lib/color-COa3DplO.js +37 -0
- package/lib/combine-TWuGdcbC.js +137 -0
- package/lib/combine.js +1 -136
- package/lib/{components-cEW3ovK0.js → components-B2ka9Ax1.js} +4 -4
- package/lib/{components-JXVm45kK.js → components-PVXwaOaC.js} +2 -2
- package/lib/compress-BzWqsHF1.js +79 -0
- package/lib/compress-C-9vK4B2.d.ts +11 -0
- package/lib/compress-DCyrrQ5D.js +11 -0
- package/lib/compress.d.ts +1 -1
- package/lib/compress.js +2 -78
- package/lib/concurrent-DrF3d2c-.js +30 -0
- package/lib/conninfo-BCROmdnf.d.ts +11 -0
- package/lib/conninfo-CWJwieh4.js +19 -0
- package/lib/constants-CBSEudrb.js +7 -0
- package/lib/{context-DDO0cfuP.js → context-CkOh7Ygv.js} +2 -2
- package/lib/context-storage-iuPoI_CR.js +52 -0
- package/lib/context-storage.js +1 -51
- package/lib/cookie-B1LlkzOe.js +125 -0
- package/lib/cookie-BHIduy2T.js +82 -0
- package/lib/cookie.js +1 -81
- package/lib/cordis/db-sqlite.d.ts +1 -1
- package/lib/cordis/door.d.ts +99 -5
- package/lib/cordis/door.js +562 -20
- package/lib/cordis/inspect.js +4 -4
- package/lib/cordis/kv-db.d.ts +1 -1
- package/lib/cordis/kv-db.js +1 -1
- package/lib/cordis/kv-json.d.ts +1 -1
- package/lib/cordis/kv-json.js +1 -1
- package/lib/cordis/node-server.d.ts +1 -1
- package/lib/cordis/node-server.js +3 -116
- package/lib/cordis/router-http.js +2 -2
- package/lib/cordis/router.js +4 -4
- package/lib/cordis/state.js +7 -7
- package/lib/{core-CaY8XDZ6.js → core-BOu1jYua.js} +1 -1
- package/lib/cors-CbAQ-8qU.js +106 -0
- package/lib/cors.js +1 -105
- package/lib/crypto-iVP-N-tS.js +34 -0
- package/lib/csrf-DqqOZtgy.js +101 -0
- package/lib/csrf.js +1 -100
- package/lib/css-C5CPVMvM.js +134 -0
- package/lib/css.js +2 -133
- package/lib/db/sqlite.d.ts +1 -25
- package/lib/dev-CLgu-JRe.js +50 -0
- package/lib/dev.js +1 -49
- package/lib/dom-YjND9Jt3.js +74 -0
- package/lib/door/host.d.ts +2 -2
- package/lib/door/host.js +1 -1
- package/lib/door/page.d.ts +3 -1
- package/lib/door/page.js +2 -2
- package/lib/door/protocol.d.ts +1 -1
- package/lib/door/protocol.js +1 -212
- package/lib/encode-BhbNLrjp.js +33 -0
- package/lib/factory-CLdSf-xS.js +30 -0
- package/lib/factory.js +1 -29
- package/lib/handler-C0wZ0KwO.js +12 -0
- package/lib/helpers-BEjcdbgw.js +5 -0
- package/lib/{hooks-w72iLeFR.js → hooks-CfC07ri0.js} +1 -1
- package/lib/html-CgNa89xw.js +31 -0
- package/lib/html-DGExBFNg.js +103 -0
- package/lib/html.d.ts +2 -5
- package/lib/html.js +2 -30
- package/lib/http-exception-MjWkT89V.js +54 -0
- package/lib/http-exception.js +1 -53
- package/lib/index-dAzQsJG9.d.ts +6 -0
- package/lib/index.js +3 -3
- package/lib/ip-restriction-jKtKCRM_.js +166 -0
- package/lib/ip-restriction.js +1 -165
- package/lib/ipaddr-DCoIo528.js +238 -0
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +2 -1
- package/lib/jsx/dom/server.js +3 -3
- package/lib/jsx/dom.js +5 -73
- package/lib/jsx/jsx-dev-runtime.d.ts +2 -5
- package/lib/jsx/jsx-dev-runtime.js +2 -13
- package/lib/jsx/jsx-runtime.d.ts +2 -2
- package/lib/jsx/jsx-runtime.js +4 -4
- package/lib/jsx/streaming.js +1 -1
- package/lib/jsx-CUQgttxU.js +47 -0
- package/lib/jsx-dev-runtime-B4WTyq8r.js +14 -0
- package/lib/{jsx-dev-runtime-CWLRA2F3.js → jsx-dev-runtime-BLfA-EhI.js} +2 -2
- package/lib/jsx-dev-runtime-CGt4kNYr.d.ts +6 -0
- package/lib/jsx-renderer-CfifM4hT.js +99 -0
- package/lib/jsx-renderer.js +1 -98
- package/lib/jsx-runtime-COCRWBJ9.js +2 -0
- package/lib/jsx.js +5 -46
- package/lib/{jwk-LkzeoaU8.js → jwk-CDPpYQpD.js} +3 -3
- package/lib/jwk.js +1 -1
- package/lib/{jwt-BGJBw5AF.js → jwt-BXoIA2fQ.js} +2 -2
- package/lib/{jwt-CskdhEW9.js → jwt-lyS4Q3mT.js} +3 -3
- package/lib/jwt.js +2 -2
- package/lib/kv/db.d.ts +1 -10
- package/lib/kv/db.js +1 -56
- package/lib/kv/json.d.ts +1 -9
- package/lib/kv/json.js +1 -223
- package/lib/{language-D9a4zqvn.js → language-DeGRtLJt.js} +2 -2
- package/lib/language.js +1 -1
- package/lib/{linear-router-C3FPib6h.js → linear-router-CmZn_JLV.js} +2 -2
- package/lib/logger-Ckxu8diz.js +54 -0
- package/lib/logger.js +1 -53
- package/lib/loopback-only-BbasoF7R.js +90 -0
- package/lib/loopback-only.js +1 -89
- package/lib/method-not-allowed-TO2_hnfl.js +82 -0
- package/lib/method-not-allowed.js +1 -81
- package/lib/method-override-DnZKfJbV.js +103 -0
- package/lib/method-override.js +1 -102
- package/lib/middleware-BfG_fp8P.js +12 -0
- package/lib/mime-BnScXkQn.js +77 -0
- package/lib/mount-QkK_3NtJ.js +67 -0
- package/lib/mount.js +1 -66
- package/lib/node-server/conninfo.d.ts +1 -10
- package/lib/node-server/conninfo.js +1 -18
- package/lib/node-server/register.js +25 -2
- package/lib/node-server/serve-static.d.ts +1 -17
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +2 -2
- package/lib/node-server.js +4 -4
- package/lib/open-BqK4D2Wn.d.ts +11 -0
- package/lib/open-But9E2rL.js +57 -0
- package/lib/open-CULVJgOc.d.ts +12 -0
- package/lib/open-Dp4sxspc.js +224 -0
- package/lib/open-T7kgbO2O.d.ts +27 -0
- package/lib/{pattern-router-HZxjDGEQ.js → pattern-router-CJBecJBd.js} +1 -1
- package/lib/powered-by-CXQNjNIX.js +24 -0
- package/lib/powered-by.js +1 -23
- package/lib/pretty-json-CeC1yvYt.js +39 -0
- package/lib/pretty-json.js +1 -38
- package/lib/{protocol-C37gPePD.d.ts → protocol-CR31M7K_.d.ts} +2 -0
- package/lib/protocol-Dt0AZBOz.js +213 -0
- package/lib/proxy-C2w_mH7k.js +121 -0
- package/lib/proxy.js +1 -120
- package/lib/quick.js +4 -4
- package/lib/{reg-exp-router-DC1R3qoY.js → reg-exp-router-C_WV84eV.js} +2 -2
- package/lib/request-DvMgGtlW.js +323 -0
- package/lib/request.js +1 -322
- package/lib/route-D8m-a2I1.js +126 -0
- package/lib/route.js +1 -125
- package/lib/router/linear-router.js +1 -1
- package/lib/router/pattern-router.js +1 -1
- package/lib/router/reg-exp-router.js +1 -1
- package/lib/router/smart-router.js +1 -1
- package/lib/router/trie-router.js +1 -1
- package/lib/router-Be_o6nFu.js +35 -0
- package/lib/router.js +1 -34
- package/lib/schema-validator-EK3cUUCt.js +22 -0
- package/lib/schema-validator.js +1 -21
- package/lib/{secure-headers-mP3j6Hm6.js → secure-headers-C5ZOcXqk.js} +1 -1
- package/lib/secure-headers.js +1 -1
- package/lib/serve-static-B7sqnftW.d.ts +18 -0
- package/lib/{serve-static-3uo_9rjF.js → serve-static-C1Nkj_xB.js} +2 -2
- package/lib/{serve-static-Cp5adzGo.js → serve-static-EiJf1zkO.js} +4 -4
- package/lib/serve-static.js +1 -1
- package/lib/{smart-router-C5CZ5XSM.js → smart-router-BjxkDQCE.js} +1 -1
- package/lib/{snapshot-text-BLz8nlSc.js → snapshot-text-CtQm3n8n.js} +23 -14
- package/lib/{src-dy3Fk9tA.js → src-BTBm4OPD.js} +6 -6
- package/lib/src-LX0E4ec4.js +117 -0
- package/lib/{ssg-BwjcA-Ge.js → ssg-BEl7MHqg.js} +5 -5
- package/lib/ssg.js +1 -1
- package/lib/stream-CzM_SHkq.js +88 -0
- package/lib/{streaming-DHST3Scg.js → streaming-BlDaph8U.js} +3 -3
- package/lib/streaming.js +1 -1
- package/lib/{test-websocket-D8WWqE5G.js → test-websocket-BLeEvybj.js} +2 -2
- package/lib/testing-BQx5T_UX.js +17 -0
- package/lib/testing.js +1 -16
- package/lib/timeout-FeqZ9A-0.js +42 -0
- package/lib/timeout.js +1 -41
- package/lib/tiny.js +2 -2
- package/lib/trailing-slash-DwTx-1sk.js +85 -0
- package/lib/trailing-slash.js +1 -84
- package/lib/{trie-router-CfFbapzL.js → trie-router-JydLKUa2.js} +2 -2
- package/lib/url-BPh4d6j-.js +197 -0
- package/lib/utils/accept.js +1 -192
- package/lib/utils/basic-auth.js +1 -19
- package/lib/utils/body.js +1 -93
- package/lib/utils/buffer.js +1 -50
- package/lib/utils/color.js +1 -36
- package/lib/utils/compress.d.ts +1 -10
- package/lib/utils/compress.js +1 -10
- package/lib/utils/concurrent.js +1 -29
- package/lib/utils/constants.js +1 -6
- package/lib/utils/cookie.js +1 -124
- package/lib/utils/crypto.js +1 -33
- package/lib/utils/encode.js +1 -32
- package/lib/utils/handler.js +1 -11
- package/lib/utils/html.js +1 -102
- package/lib/utils/ipaddr.js +1 -237
- package/lib/utils/jwt.js +1 -1
- package/lib/utils/mime.js +1 -76
- package/lib/utils/stream.js +1 -87
- package/lib/utils/url.js +1 -196
- package/lib/{validator-D84mq-dO.js → validator-Btiyt5VJ.js} +3 -3
- package/lib/validator.js +1 -1
- package/lib/websocket-DsyBIKVA.js +56 -0
- package/lib/ws.js +1 -55
- package/package.json +1 -1
- package/plugins/acryl-door/src/cli/acryl.ts +36 -5
- package/plugins/acryl-door/src/host/channel.ts +14 -6
- package/plugins/acryl-door/src/host/chat-tools.ts +2 -0
- package/plugins/acryl-door/src/host/door.ts +97 -12
- package/plugins/acryl-door/src/host/page-routes.ts +3 -1
- package/plugins/acryl-door/src/host/shell.ts +147 -0
- package/plugins/acryl-door/src/host/shim.ts +39 -3
- package/plugins/acryl-door/src/host/verbs.ts +39 -2
- package/plugins/acryl-door/src/index.ts +22 -3
- package/plugins/acryl-door/src/page/channel.ts +3 -1
- package/plugins/acryl-door/src/page/index.ts +2 -1
- package/plugins/acryl-door/src/page/protocol.ts +2 -0
- package/plugins/acryl-door/src/shell/protocol.ts +130 -0
- package/plugins/acryl-node-server/src/register.ts +29 -2
- package/plugins/agent/model.ts +6 -1
- package/plugins/agent/tools.ts +12 -2
- package/plugins/agent-panel/client/describe.ts +44 -0
- package/plugins/agent-panel/client/main.ts +4 -1
- package/plugins/agent-panel/headless.ts +29 -8
- package/plugins/agent-panel/index.ts +22 -10
- package/plugins/agent-panel/launch.ts +67 -0
- package/plugins/desktop/attach.ts +72 -0
- package/plugins/desktop/door-shell.ts +379 -0
- package/plugins/desktop/main.ts +82 -15
- package/plugins/extensions/index.ts +3 -4
- package/plugins/extensions/stage.ts +33 -5
- package/plugins/openai/index.ts +1 -1
- package/templates/agent/AGENTS.md +4 -0
- package/templates/agent/blend.yaml +4 -1
- package/templates/agent/src/app.ts +2 -0
- package/templates/agent/src/routes.ts +1 -1
- package/templates/agent/template.json +1 -0
- package/templates/agent/test/builder.test.ts +26 -21
- package/lib/helpers-CvTauQT5.js +0 -5
- package/lib/middleware-CPQ8himN.js +0 -12
- package/plugins/acryl-router/src/connection.d.ts +0 -25
- package/plugins/acryl-router/src/core/app-base.d.ts +0 -215
- package/plugins/acryl-router/src/core/app.d.ts +0 -19
- package/plugins/acryl-router/src/core/client/client.d.ts +0 -4
- package/plugins/acryl-router/src/core/client/fetch-result-please.d.ts +0 -35
- package/plugins/acryl-router/src/core/client/index.d.ts +0 -7
- package/plugins/acryl-router/src/core/client/types.d.ts +0 -229
- package/plugins/acryl-router/src/core/client/utils.d.ts +0 -18
- package/plugins/acryl-router/src/core/compose.d.ts +0 -14
- package/plugins/acryl-router/src/core/context.d.ts +0 -443
- package/plugins/acryl-router/src/core/helper/accepts/accepts.d.ts +0 -30
- package/plugins/acryl-router/src/core/helper/accepts/index.d.ts +0 -5
- package/plugins/acryl-router/src/core/helper/conninfo/index.d.ts +0 -5
- package/plugins/acryl-router/src/core/helper/conninfo/types.d.ts +0 -36
- package/plugins/acryl-router/src/core/helper/cookie/index.d.ts +0 -25
- package/plugins/acryl-router/src/core/helper/css/common.d.ts +0 -59
- package/plugins/acryl-router/src/core/helper/css/index.d.ts +0 -80
- package/plugins/acryl-router/src/core/helper/dev/index.d.ts +0 -20
- package/plugins/acryl-router/src/core/helper/factory/index.d.ts +0 -82
- package/plugins/acryl-router/src/core/helper/html/index.d.ts +0 -8
- package/plugins/acryl-router/src/core/helper/proxy/index.d.ts +0 -73
- package/plugins/acryl-router/src/core/helper/route/index.d.ts +0 -75
- package/plugins/acryl-router/src/core/helper/ssg/index.d.ts +0 -7
- package/plugins/acryl-router/src/core/helper/ssg/middleware.d.ts +0 -44
- package/plugins/acryl-router/src/core/helper/ssg/plugins.d.ts +0 -27
- package/plugins/acryl-router/src/core/helper/ssg/ssg.d.ts +0 -91
- package/plugins/acryl-router/src/core/helper/ssg/utils.d.ts +0 -16
- package/plugins/acryl-router/src/core/helper/streaming/index.d.ts +0 -8
- package/plugins/acryl-router/src/core/helper/streaming/sse.d.ts +0 -13
- package/plugins/acryl-router/src/core/helper/streaming/stream.d.ts +0 -3
- package/plugins/acryl-router/src/core/helper/streaming/text.d.ts +0 -3
- package/plugins/acryl-router/src/core/helper/streaming/utils.d.ts +0 -1
- package/plugins/acryl-router/src/core/helper/testing/index.d.ts +0 -13
- package/plugins/acryl-router/src/core/helper/websocket/index.d.ts +0 -70
- package/plugins/acryl-router/src/core/http-exception.d.ts +0 -58
- package/plugins/acryl-router/src/core/index.d.ts +0 -37
- package/plugins/acryl-router/src/core/jsx/base.d.ts +0 -61
- package/plugins/acryl-router/src/core/jsx/children.d.ts +0 -9
- package/plugins/acryl-router/src/core/jsx/components.d.ts +0 -15
- package/plugins/acryl-router/src/core/jsx/constants.d.ts +0 -6
- package/plugins/acryl-router/src/core/jsx/context.d.ts +0 -51
- package/plugins/acryl-router/src/core/jsx/dom/components.d.ts +0 -10
- package/plugins/acryl-router/src/core/jsx/dom/context.d.ts +0 -3
- package/plugins/acryl-router/src/core/jsx/dom/css.d.ts +0 -83
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.d.ts +0 -39
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.d.ts +0 -16
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.d.ts +0 -8
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.d.ts +0 -7
- package/plugins/acryl-router/src/core/jsx/dom/render.d.ts +0 -73
- package/plugins/acryl-router/src/core/jsx/dom/utils.d.ts +0 -1
- package/plugins/acryl-router/src/core/jsx/hooks/index.d.ts +0 -47
- package/plugins/acryl-router/src/core/jsx/index.d.ts +0 -76
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.d.ts +0 -6
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.d.ts +0 -13
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.d.ts +0 -735
- package/plugins/acryl-router/src/core/jsx/streaming.d.ts +0 -36
- package/plugins/acryl-router/src/core/jsx/types.d.ts +0 -36
- package/plugins/acryl-router/src/core/jsx/utils.d.ts +0 -4
- package/plugins/acryl-router/src/core/request/constants.d.ts +0 -1
- package/plugins/acryl-router/src/core/request.d.ts +0 -306
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.d.ts +0 -6
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.d.ts +0 -7
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.d.ts +0 -13
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.d.ts +0 -17
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.d.ts +0 -11
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.d.ts +0 -8
- package/plugins/acryl-router/src/core/router/smart-router/index.d.ts +0 -5
- package/plugins/acryl-router/src/core/router/smart-router/router.d.ts +0 -11
- package/plugins/acryl-router/src/core/router/trie-router/index.d.ts +0 -5
- package/plugins/acryl-router/src/core/router/trie-router/node.d.ts +0 -6
- package/plugins/acryl-router/src/core/router/trie-router/router.d.ts +0 -7
- package/plugins/acryl-router/src/core/router/utils.d.ts +0 -1
- package/plugins/acryl-router/src/core/router.d.ts +0 -97
- package/plugins/acryl-router/src/core/types.d.ts +0 -573
- package/plugins/acryl-router/src/core/utils/accept.d.ts +0 -6
- package/plugins/acryl-router/src/core/utils/body.d.ts +0 -79
- package/plugins/acryl-router/src/core/utils/buffer.d.ts +0 -17
- package/plugins/acryl-router/src/core/utils/color.d.ts +0 -21
- package/plugins/acryl-router/src/core/utils/concurrent.d.ts +0 -11
- package/plugins/acryl-router/src/core/utils/constants.d.ts +0 -4
- package/plugins/acryl-router/src/core/utils/cookie.d.ts +0 -41
- package/plugins/acryl-router/src/core/utils/crypto.d.ts +0 -15
- package/plugins/acryl-router/src/core/utils/handler.d.ts +0 -6
- package/plugins/acryl-router/src/core/utils/headers.d.ts +0 -8
- package/plugins/acryl-router/src/core/utils/html.d.ts +0 -44
- package/plugins/acryl-router/src/core/utils/http-status.d.ts +0 -32
- package/plugins/acryl-router/src/core/utils/mime.d.ts +0 -70
- package/plugins/acryl-router/src/core/utils/stream.d.ts +0 -31
- package/plugins/acryl-router/src/core/utils/types.d.ts +0 -128
- package/plugins/acryl-router/src/core/utils/url.d.ts +0 -44
- package/plugins/acryl-router/src/core/validator/index.d.ts +0 -7
- package/plugins/acryl-router/src/core/validator/utils.d.ts +0 -29
- package/plugins/acryl-router/src/core/validator/validator.d.ts +0 -16
- package/plugins/acryl-router/src/helpers/index.d.ts +0 -17
- package/plugins/acryl-router/src/helpers/schema-validator.d.ts +0 -54
package/lib/cordis/door.js
CHANGED
|
@@ -1,13 +1,280 @@
|
|
|
1
|
-
import { t as Acryl } from "../app-
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import "../
|
|
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
|
-
...
|
|
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
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"))
|
|
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
|
-
|
|
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 };
|