openmausbot 0.1.98 → 0.1.99
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/dist/assets/{ActivityPanel-H6D3SD39.js → ActivityPanel-CgUgtumZ.js} +1 -1
- package/dist/assets/BotSettingsDialog-lJj6oiir.js +7 -0
- package/dist/assets/{CloudBackendPicker-iezpJFpF.js → CloudBackendPicker-C5-_arUz.js} +1 -1
- package/dist/assets/{ComputerPanel-DZPj-IWu.js → ComputerPanel-euJ2CSlH.js} +1 -1
- package/dist/assets/{DesktopViewer-utONms1p.js → DesktopViewer-XCln1Pn_.js} +1 -1
- package/dist/assets/InspectorPanel-DKYyQZlo.js +5 -0
- package/dist/assets/{KeyboardShortcutsModal-gk9mvHj_.js → KeyboardShortcutsModal-DkkZWZRI.js} +1 -1
- package/dist/assets/{LocalVmWorkspace-DLIvd2kC.js → LocalVmWorkspace-Cwt3_XSx.js} +1 -1
- package/dist/assets/{MacLocalControl-bJv8yiPB.js → MacLocalControl-B28RbdCS.js} +1 -1
- package/dist/assets/NewBotDialog-dMWDs5xm.js +2 -0
- package/dist/assets/RemoteAgentSettingsPanel-B5KWXnqH.js +1 -0
- package/dist/assets/RoutinesPage-DOosRYDK.js +1 -0
- package/dist/assets/{RoutinesSection-Dny9ra-c.js → RoutinesSection-uOfOTfkB.js} +1 -1
- package/dist/assets/{SettingsModal-gC6XJJmt.js → SettingsModal-BTGkFbj1.js} +3 -3
- package/dist/assets/{TeamMapPage-B6fuu4r_.js → TeamMapPage-DqNdJn96.js} +1 -1
- package/dist/assets/{TriggersPanel-j0qTP2CO.js → TriggersPanel-c6xkG9mi.js} +1 -1
- package/dist/assets/{VisibilitySection-DT0FqPvu.js → VisibilitySection-DD4ffIR5.js} +1 -1
- package/dist/assets/WebhooksPanel-Cz17K-_O.js +3 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-BsjcFX0-.js → abnfDiagram-O67JEVCF-CX6tDpkg.js} +1 -1
- package/dist/assets/{activity-BxOkayvD.js → activity-BAVWO0Zj.js} +1 -1
- package/dist/assets/{arc-CJNA94c6.js → arc-h45-9NFk.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-dz2ud7lP.js → architectureDiagram-NJMV4G6O-CgLFXbg6.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-ChBEALoR.js → blockDiagram-BEXU5L5S-DGDFFc9M.js} +1 -1
- package/dist/assets/{brain-CFZ7PEXc.js → brain-CeCdoTb2.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-KZNRKDQe.js → c4Diagram-YGBWAQC7-B9aklAUj.js} +1 -1
- package/dist/assets/channel-TsOXCiPg.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BLLwmCET.js → chunk-2Q5K7J3B-BC0KmWsM.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CQxyreBZ.js → chunk-3FUC2YCW-BbDFHsJ1.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-D6sFMW0u.js → chunk-5VM5RSS4-DtGbsjOM.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-BaZ1YgLF.js → chunk-GTNCS2PH-VMghMcEl.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CcalphmC.js → chunk-GWA4HPMP-BP_JyGWQ.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-CSk8R3ve.js → chunk-JWPE2WC7-CYFyzgAU.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-CTmLjA57.js → chunk-NETBCI7D-DBNa_kqm.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CM9Z77Ne.js → chunk-WEXAMYUT-BKEeM7CJ.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-B1fxpMS2.js → chunk-XXDRQBXY-C__m3ZXl.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DCCyoLpj.js → classDiagram-v2-NBCMYWYE-CBUrTSAX.js} +1 -1
- package/dist/assets/core-6lvx-ypA.js +12 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-KyImBKvE.js → cose-bilkent-JH36ORCC-GRfL-1Vb.js} +1 -1
- package/dist/assets/cpp-DddhVWhK.js +1 -0
- package/dist/assets/{cynefin-OW5HDTMX-fsZc6MGa.js → cynefin-OW5HDTMX-LopwGUjm.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-CGmHsShB.js → cynefinDiagram-VND7K2PF-DSB8ZRNS.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-Dah1h-CH.js → dagre-6A5THRUB-DL3Kgdfk.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-BIu71OyV.js → diagram-22UHCM2B-DIxN34BM.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-gxhUB_Mu.js → diagram-3UASUU5V-CJnSvXaI.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-CBhusUBn.js → diagram-ATOU4E4O-C-XF-lO8.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-BdPrc86x.js → diagram-CDSNMT55-Df2yZPi_.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Clp110R_.js → diagram-MLGK6HIB-BBs9oZFs.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-DJKfbugw.js → diagram-MPIPVDR6-CEbK11mM.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DnsltoBu.js → ebnfDiagram-ZINNZB2B-BNq6Yv3C.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-jWdq6gqH.js → elk-276RUBZZ-CFbQ2bGm.js} +1 -1
- package/dist/assets/engine-javascript-D5Xfc425.js +141 -0
- package/dist/assets/{erDiagram-OPXOYQCR-DArPVf8u.js → erDiagram-OPXOYQCR-Bz8vmjN4.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-D2HZ-2f7.js → flowDiagram-KWPJA3E3-cQUfEYrx.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP--o0KIxEo.js → ganttDiagram-FUAMR5RP-BY8n9tbI.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-CsoXr2D7.js → gitGraphDiagram-X574FWY7-KduSOMDN.js} +1 -1
- package/dist/assets/{image-plus-BbVjGHin.js → image-plus-DNIWU160.js} +1 -1
- package/dist/assets/{index-C3NqfdVU.js → index-C8xb62XS.js} +205 -205
- package/dist/assets/{index-BrPW5HDO.css → index-DbN_Hfjs.css} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-g-6lNp7r.js → infoDiagram-VRGFBTTK-xBKgUkpL.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-B1d6qVTV.js → ishikawaDiagram-OU5B5YK6-_ei09tAJ.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-BTPuiU6T.js → journeyDiagram-ZHPQQLJL-Fp49MkSp.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-bDDuRJvP.js → kanban-definition-PNTS6WVX-CY9lLm7V.js} +1 -1
- package/dist/assets/{linear-BVVKbEMy.js → linear-D2ai4eM2.js} +1 -1
- package/dist/assets/{mermaid.core-JJdoKyKB.js → mermaid.core-DP6qPqu1.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-B4aE3T0q.js → mindmap-definition-NLK3R4M7-g1ynEw_d.js} +1 -1
- package/dist/assets/{page-visible-BBo_Zm8g.js → page-visible-BZn8304W.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-DFKQWpR7.js → pegDiagram-GJSIUBJH-Bt4ObsZj.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-Iv7OKVJi.js → pieDiagram-5QR66LMP-CeLld8bY.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-U2YnfwBk.js → quadrantDiagram-O4NWA36T-D4WWZw3x.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-CuPy6RqM.js → railroadDiagram-XR7U4H2S-D95P5NqU.js} +1 -1
- package/dist/assets/{remote-desktop-panel-BNEj7tUx.js → remote-desktop-panel-DOIMmT4T.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-bMI5AK7T.js → requirementDiagram-PLB6GJNP-D12p3RhR.js} +1 -1
- package/dist/assets/ruby-Ct1eTYOr.js +1 -0
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BKcFE2WQ.js → sankeyDiagram-IPEJSGJF-Co26Dwcs.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Bn9q88kl.js → sequenceDiagram-PO4LG4MO-y5YxaDKt.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-Cvmdq9HD.js → sizeCapture-INFHLROL-D49hZ8U5.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-JJQYw3eQ.js → stateDiagram-v2-GCMORJYK-C0sylr-r.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-BkSCBEwp.js → swimlanes-2SLR337P-DmRWsDIs.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DzhbBHtd.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-DsaM4GHr.js → timeline-definition-EJHVYXUP-DVmHYs3w.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-3FOwLq7B.js → usecaseDiagram-POWQR4AR-BQ1q0VV6.js} +1 -1
- package/dist/assets/{user-DcZBnAuv.js → user-B9lohN4n.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-DyNjJsKt.js → vennDiagram-UO4OBE2U-C_cnfST0.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-Y4ZMcnBC.js → wardleyDiagram-VNRHLVJA-Bb3BSu12.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BXZpg955.js → xychartDiagram-PMCCYNJV-CcwEuuEi.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/drivers/agents-proxy.js +10 -9
- package/dist-server/index.js +1474 -807
- package/dist-server/openmausbot.js +248 -152
- package/dist-server/pair-cli.js +248 -152
- package/dist-server/server/activity.js +2 -0
- package/dist-server/server/checkpoints.js +55 -39
- package/dist-server/server/direct-apply.js +20 -0
- package/dist-server/server/drivers/agents-call.js +1 -1
- package/dist-server/server/drivers/agents-catalog.js +11 -8
- package/dist-server/server/drivers/codex.js +147 -24
- package/dist-server/server/drivers/device-auth.js +5 -4
- package/dist-server/server/drivers/openai-chat.js +5 -4
- package/dist-server/server/harness/registry.js +63 -45
- package/dist-server/server/index.js +203 -23
- package/dist-server/server/mcp-oauth.js +125 -47
- package/dist-server/server/message-file.js +95 -3
- package/dist-server/server/model-requests.js +55 -4
- package/dist-server/server/profile-requests.js +80 -4
- package/dist-server/server/request-auth.js +1 -0
- package/dist-server/server/routes/undo.js +17 -0
- package/dist-server/server/routine-requests.js +210 -6
- package/dist-server/server/setup-mode.js +1 -1
- package/dist-server/server/skill-learn.js +2 -2
- package/dist-server/server/skills.js +30 -0
- package/dist-server/server/system-prompt.js +1 -1
- package/dist-server/server/turn-continuation.js +13 -18
- package/dist-server/server/workspace-backup.js +6 -1
- package/dist-server/workspace-backup.worker.js +1 -1
- package/package.json +1 -1
- package/skills/create-verification-skill/SKILL.md +9 -8
- package/skills/create-verification-skill/manifest.json +1 -1
- package/dist/assets/BotSettingsDialog-Dqz_AiBZ.js +0 -7
- package/dist/assets/InspectorPanel-OVaBVrzC.js +0 -5
- package/dist/assets/NewBotDialog-1qGOAdUM.js +0 -2
- package/dist/assets/RemoteAgentSettingsPanel-DqKr0HkO.js +0 -1
- package/dist/assets/RoutinesPage-BM2D4qGi.js +0 -1
- package/dist/assets/WebhooksPanel--YQyCjKO.js +0 -3
- package/dist/assets/abap-BdImnpbu.js +0 -1
- package/dist/assets/actionscript-3-B3316cI-.js +0 -1
- package/dist/assets/ada-bCR0ucgS.js +0 -1
- package/dist/assets/ahk-CsyLZFj1.js +0 -1
- package/dist/assets/ahk2-8Zs4aa1G.js +0 -1
- package/dist/assets/andromeeda-C4gqWexZ.js +0 -1
- package/dist/assets/angular-html-DA-rfuFy.js +0 -1
- package/dist/assets/angular-ts-BrjP3tb8.js +0 -1
- package/dist/assets/apache-Pmp26Uib.js +0 -1
- package/dist/assets/apex-DhZFqWV2.js +0 -1
- package/dist/assets/apl-CORt7UWP.js +0 -1
- package/dist/assets/applescript-Co6uUVPk.js +0 -1
- package/dist/assets/ara-BRHolxvo.js +0 -1
- package/dist/assets/asciidoc-CSVQ5wI8.js +0 -1
- package/dist/assets/asm-D_Q5rh1f.js +0 -1
- package/dist/assets/astro-HNnZUWAn.js +0 -1
- package/dist/assets/aurora-x-D-2ljcwZ.js +0 -1
- package/dist/assets/awk-DMzUqQB5.js +0 -1
- package/dist/assets/ayu-dark-DYE7WIF3.js +0 -1
- package/dist/assets/ayu-light-BA47KaF1.js +0 -1
- package/dist/assets/ayu-mirage-32ctXXKs.js +0 -1
- package/dist/assets/ballerina-BFfxhgS-.js +0 -1
- package/dist/assets/bat-CickPsom.js +0 -1
- package/dist/assets/beancount-k_qm7-4y.js +0 -1
- package/dist/assets/berry-uYugtg8r.js +0 -1
- package/dist/assets/bibtex-CHM0blh-.js +0 -1
- package/dist/assets/bicep-Bmn6On1c.js +0 -1
- package/dist/assets/bird2-Bx8U0n9b.js +0 -1
- package/dist/assets/blade-2xfisSek.js +0 -1
- package/dist/assets/bsl-DlhNcFeZ.js +0 -1
- package/dist/assets/c3-Dp5svz6Z.js +0 -1
- package/dist/assets/cadence-Bv_4Rxtq.js +0 -1
- package/dist/assets/cairo-KRGpt6FW.js +0 -1
- package/dist/assets/catppuccin-frappe-CZL1YF0i.js +0 -1
- package/dist/assets/catppuccin-latte-DH-8KZSZ.js +0 -1
- package/dist/assets/catppuccin-macchiato-B7yYVSCf.js +0 -1
- package/dist/assets/catppuccin-mocha-Ct7hS0mc.js +0 -1
- package/dist/assets/channel-WhFyujhZ.js +0 -1
- package/dist/assets/chapel-DTp_pixX.js +0 -1
- package/dist/assets/clarity-Dn5IMItf.js +0 -1
- package/dist/assets/clojure-P80f7IUj.js +0 -1
- package/dist/assets/cmake-D1j8_8rp.js +0 -1
- package/dist/assets/cobol-nBiQ_Alo.js +0 -1
- package/dist/assets/codeowners-Bp6g37R7.js +0 -1
- package/dist/assets/codeql-DsOJ9woJ.js +0 -1
- package/dist/assets/coffee-Ch7k5sss.js +0 -1
- package/dist/assets/common-lisp-Cg-RD9OK.js +0 -1
- package/dist/assets/coq-C7JzOVbR.js +0 -1
- package/dist/assets/cpp-BMRokrvK.js +0 -1
- package/dist/assets/crystal-DGywbUpC.js +0 -1
- package/dist/assets/csv-fuZLfV_i.js +0 -1
- package/dist/assets/cue-D82EKSYY.js +0 -1
- package/dist/assets/cypher-COkxafJQ.js +0 -1
- package/dist/assets/d-85-TOEBH.js +0 -1
- package/dist/assets/dark-plus-C3mMm8J8.js +0 -1
- package/dist/assets/dax-CEL-wOlO.js +0 -1
- package/dist/assets/desktop-BmXAJ9_W.js +0 -1
- package/dist/assets/dotenv-Da5cRb03.js +0 -1
- package/dist/assets/dracula-BzJJZx-M.js +0 -1
- package/dist/assets/dracula-soft-BXkSAIEj.js +0 -1
- package/dist/assets/dream-maker-BtqSS_iP.js +0 -1
- package/dist/assets/edge-FbVlp4U3.js +0 -1
- package/dist/assets/elixir-CkH2-t6x.js +0 -1
- package/dist/assets/elm-DbKCFpqz.js +0 -1
- package/dist/assets/emacs-lisp-C_m_b--Z.js +0 -1
- package/dist/assets/erb-DXfck5VN.js +0 -1
- package/dist/assets/erlang-DsQrWhSR.js +0 -1
- package/dist/assets/everforest-dark-BgDCqdQA.js +0 -1
- package/dist/assets/everforest-light-C8M2exoo.js +0 -1
- package/dist/assets/fennel-BYunw83y.js +0 -1
- package/dist/assets/fluent-C4IJs8-o.js +0 -1
- package/dist/assets/fortran-fixed-form-CkoXwp7k.js +0 -1
- package/dist/assets/fortran-free-form-BxgE0vQu.js +0 -1
- package/dist/assets/fsharp-CXgrBDvD.js +0 -1
- package/dist/assets/gdresource-TyuKm33G.js +0 -1
- package/dist/assets/gdscript-DqcFQ5yU.js +0 -1
- package/dist/assets/gdshader-DkwncUOv.js +0 -1
- package/dist/assets/genie-D0YGMca9.js +0 -1
- package/dist/assets/gherkin-DyxjwDmM.js +0 -1
- package/dist/assets/git-commit-F4YmCXRG.js +0 -1
- package/dist/assets/git-rebase-r7XF79zn.js +0 -1
- package/dist/assets/github-dark-DHJKELXO.js +0 -1
- package/dist/assets/github-dark-dimmed-DH5Ifo-i.js +0 -1
- package/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +0 -1
- package/dist/assets/github-light-DAi9KRSo.js +0 -1
- package/dist/assets/github-light-high-contrast-BfjtVDDH.js +0 -1
- package/dist/assets/gleam-BspZqrRM.js +0 -1
- package/dist/assets/glimmer-js-ByusRIyA.js +0 -1
- package/dist/assets/glimmer-ts-BfAWNZQY.js +0 -1
- package/dist/assets/glsl-DplSGwfg.js +0 -1
- package/dist/assets/gn-n2N0HUVH.js +0 -1
- package/dist/assets/gnuplot-DdkO51Og.js +0 -1
- package/dist/assets/groovy-gcz8RCvz.js +0 -1
- package/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +0 -1
- package/dist/assets/gruvbox-dark-medium-GsRaNv29.js +0 -1
- package/dist/assets/gruvbox-dark-soft-CVdnzihN.js +0 -1
- package/dist/assets/gruvbox-light-hard-CH1njM8p.js +0 -1
- package/dist/assets/gruvbox-light-medium-DRw_LuNl.js +0 -1
- package/dist/assets/gruvbox-light-soft-hJgmCMqR.js +0 -1
- package/dist/assets/hack-BWmVpMyf.js +0 -1
- package/dist/assets/haml-D5jkg6IW.js +0 -1
- package/dist/assets/handlebars-BpdQsYii.js +0 -1
- package/dist/assets/haskell-Df6bDoY_.js +0 -1
- package/dist/assets/haxe-CfZj7gIn.js +0 -1
- package/dist/assets/hcl-BWvSN4gD.js +0 -1
- package/dist/assets/hjson-D5-asLiD.js +0 -1
- package/dist/assets/hlsl-D3lLCCz7.js +0 -1
- package/dist/assets/horizon-BUw7H-hv.js +0 -1
- package/dist/assets/horizon-bright-CUuTKBJd.js +0 -1
- package/dist/assets/houston-DnULxvSX.js +0 -1
- package/dist/assets/html-derivative-DlHx6ybY.js +0 -1
- package/dist/assets/http-jrhK8wxY.js +0 -1
- package/dist/assets/hurl-irOxFIW8.js +0 -1
- package/dist/assets/hxml-2-FPmUDs.js +0 -1
- package/dist/assets/hy-DFXneXwc.js +0 -1
- package/dist/assets/imba-DGztddWO.js +0 -1
- package/dist/assets/index-CJTp9JEp.js +0 -153
- package/dist/assets/ini-BEwlwnbL.js +0 -1
- package/dist/assets/jinja-f2NsQr07.js +0 -1
- package/dist/assets/jison-wvAkD_A8.js +0 -1
- package/dist/assets/jsonl-DcaNXYhu.js +0 -1
- package/dist/assets/jsonnet-DFQXde-d.js +0 -1
- package/dist/assets/jssm-C2t-YnRu.js +0 -1
- package/dist/assets/julia-5Bft2YPA.js +0 -1
- package/dist/assets/just-Cwhn7H3k.js +0 -1
- package/dist/assets/kanagawa-dragon-BuwD2xS4.js +0 -1
- package/dist/assets/kanagawa-lotus-C3DzagqV.js +0 -1
- package/dist/assets/kanagawa-wave-DFGoQZhC.js +0 -1
- package/dist/assets/kdl-DV7GczEv.js +0 -1
- package/dist/assets/kusto-wEQ09or8.js +0 -1
- package/dist/assets/laserwave-DUszq2jm.js +0 -1
- package/dist/assets/latex-D5pSuvFb.js +0 -1
- package/dist/assets/lean-BZvkOJ9d.js +0 -1
- package/dist/assets/light-plus-B7mTdjB0.js +0 -1
- package/dist/assets/liquid-C0sCDyMI.js +0 -1
- package/dist/assets/llvm-BZoOZj88.js +0 -1
- package/dist/assets/log-2UxHyX5q.js +0 -1
- package/dist/assets/logo-BtOb2qkB.js +0 -1
- package/dist/assets/luau-BnpPk5vE.js +0 -1
- package/dist/assets/marko-DjSrsDqO.js +0 -1
- package/dist/assets/material-theme-D5KoaKCx.js +0 -1
- package/dist/assets/material-theme-darker-BfHTSMKl.js +0 -1
- package/dist/assets/material-theme-lighter-B0m2ddpp.js +0 -1
- package/dist/assets/material-theme-ocean-CyktbL80.js +0 -1
- package/dist/assets/material-theme-palenight-Csfq5Kiy.js +0 -1
- package/dist/assets/matlab-D7o27uSR.js +0 -1
- package/dist/assets/mdc-D1_yUvq7.js +0 -1
- package/dist/assets/mermaid-CQcHuHx7.js +0 -1
- package/dist/assets/min-dark-CafNBF8u.js +0 -1
- package/dist/assets/min-light-CTRr51gU.js +0 -1
- package/dist/assets/mipsasm-CKIfxQSi.js +0 -1
- package/dist/assets/mojo-DJz3ZmWd.js +0 -1
- package/dist/assets/monokai-BRVnQi9A.js +0 -1
- package/dist/assets/moonbit-CHtswR0a.js +0 -1
- package/dist/assets/move-el3G9tDJ.js +0 -1
- package/dist/assets/narrat-DRg8JJMk.js +0 -1
- package/dist/assets/nextflow-C-mBbutL.js +0 -1
- package/dist/assets/nextflow-groovy-vE_lwT2v.js +0 -1
- package/dist/assets/nginx-BpAMiNFr.js +0 -1
- package/dist/assets/night-owl-C39BiMTA.js +0 -1
- package/dist/assets/night-owl-light-CMTm3GFP.js +0 -1
- package/dist/assets/nim-BIad80T-.js +0 -1
- package/dist/assets/nix-CwoSXNpI.js +0 -1
- package/dist/assets/nord-Ddv68eIx.js +0 -1
- package/dist/assets/nsis-BlV79W_Q.js +0 -1
- package/dist/assets/nushell-D3jzshHO.js +0 -1
- package/dist/assets/objective-c-DXmwc3jG.js +0 -1
- package/dist/assets/objective-cpp-CLxacb5B.js +0 -1
- package/dist/assets/ocaml-C0hk2d4L.js +0 -1
- package/dist/assets/odin-BBf5iR-q.js +0 -1
- package/dist/assets/one-dark-pro-DVMEJ2y_.js +0 -1
- package/dist/assets/one-light-C3Wv6jpd.js +0 -1
- package/dist/assets/openscad-C4EeE6gA.js +0 -1
- package/dist/assets/org-DM6o9KBp.js +0 -1
- package/dist/assets/pascal-D93ZcfNL.js +0 -1
- package/dist/assets/perl-B9cMNwum.js +0 -1
- package/dist/assets/pkl-u5AG7uiY.js +0 -1
- package/dist/assets/plastic-3e1v2bzS.js +0 -1
- package/dist/assets/plsql-ChMvpjG-.js +0 -1
- package/dist/assets/po-BTJTHyun.js +0 -1
- package/dist/assets/poimandres-CS3Unz2-.js +0 -1
- package/dist/assets/polar-C0HS_06l.js +0 -1
- package/dist/assets/postcss-CXtECtnM.js +0 -1
- package/dist/assets/powerquery-CEu0bR-o.js +0 -1
- package/dist/assets/prisma-Vru482bI.js +0 -1
- package/dist/assets/prolog-CbFg5uaA.js +0 -1
- package/dist/assets/pug-DKIMFp6K.js +0 -1
- package/dist/assets/puppet-BMWR74SV.js +0 -1
- package/dist/assets/purescript-CklMAg4u.js +0 -1
- package/dist/assets/qml-3beO22l8.js +0 -1
- package/dist/assets/qmldir-C8lEn-DE.js +0 -1
- package/dist/assets/qss-IeuSbFQv.js +0 -1
- package/dist/assets/racket-BqYA7rlc.js +0 -1
- package/dist/assets/raku-DXvB9xmW.js +0 -1
- package/dist/assets/razor-BjBPvh-w.js +0 -1
- package/dist/assets/rbs-CpoqiR4B.js +0 -1
- package/dist/assets/red-hvxz__6c.js +0 -1
- package/dist/assets/reg-C-SQnVFl.js +0 -1
- package/dist/assets/regexp-CDVJQ6XC.js +0 -1
- package/dist/assets/rel-C3B-1QV4.js +0 -1
- package/dist/assets/riscv-BM1_JUlF.js +0 -1
- package/dist/assets/ron-D8l8udqQ.js +0 -1
- package/dist/assets/rose-pine-5qJOZa0Y.js +0 -1
- package/dist/assets/rose-pine-dawn-zx0QlTCp.js +0 -1
- package/dist/assets/rose-pine-moon-C9pEdX9L.js +0 -1
- package/dist/assets/rosmsg-BJDFO7_C.js +0 -1
- package/dist/assets/rst-bs7f0vWN.js +0 -1
- package/dist/assets/ruby-C0TQ7zu5.js +0 -1
- package/dist/assets/sas-DEy46yEz.js +0 -1
- package/dist/assets/scala-CqE71os6.js +0 -1
- package/dist/assets/scheme-C98Dy4si.js +0 -1
- package/dist/assets/sdbl-DVxCFoDh.js +0 -1
- package/dist/assets/shaderlab-Dg9Lc6iA.js +0 -1
- package/dist/assets/shellsession-BADoaaVG.js +0 -1
- package/dist/assets/slack-dark-BthQWCQV.js +0 -1
- package/dist/assets/slack-ochin-DqwNpetd.js +0 -1
- package/dist/assets/smalltalk-BOQMe2GC.js +0 -1
- package/dist/assets/smithy-cds9vsN8.js +0 -1
- package/dist/assets/snazzy-light-Bw305WKR.js +0 -1
- package/dist/assets/solarized-dark-CpvCGNkr.js +0 -1
- package/dist/assets/solarized-light-Dlz6yCKv.js +0 -1
- package/dist/assets/solidity-DijEV5ha.js +0 -1
- package/dist/assets/soy-8wufbnw4.js +0 -1
- package/dist/assets/sparql-rVzFXLq3.js +0 -1
- package/dist/assets/splunk-BtCnVYZw.js +0 -1
- package/dist/assets/ssh-config-_ykCGR6B.js +0 -1
- package/dist/assets/stata-DI20mbqo.js +0 -1
- package/dist/assets/stylus-BEDo0Tqx.js +0 -1
- package/dist/assets/surrealql-Cjom0U5J.js +0 -1
- package/dist/assets/svelte-Cy7k_4gC.js +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CmTirbVT.js +0 -8
- package/dist/assets/synthwave-84-CbfX1IO0.js +0 -1
- package/dist/assets/system-verilog-0hqHdDBg.js +0 -1
- package/dist/assets/systemd-4A_iFExJ.js +0 -1
- package/dist/assets/talonscript-CkByrt1z.js +0 -1
- package/dist/assets/tasl-QIJgUcNo.js +0 -1
- package/dist/assets/tcl-dwOrl1Do.js +0 -1
- package/dist/assets/templ-DhtptRzy.js +0 -1
- package/dist/assets/terraform-BETggiCN.js +0 -1
- package/dist/assets/tex-D96PA37w.js +0 -1
- package/dist/assets/tokyo-night-hegEt444.js +0 -1
- package/dist/assets/ts-tags-D351s5mN.js +0 -1
- package/dist/assets/tsv-B_m7g4N7.js +0 -1
- package/dist/assets/turtle-BsS91CYL.js +0 -1
- package/dist/assets/twig-27uCiNez.js +0 -1
- package/dist/assets/typespec-CAFt9gP4.js +0 -1
- package/dist/assets/typst-BUadGCkm.js +0 -1
- package/dist/assets/v-BGw2Nkan.js +0 -1
- package/dist/assets/vala-CsfeWuGM.js +0 -1
- package/dist/assets/vb-Cu-pLBUe.js +0 -1
- package/dist/assets/verilog-nZwndyjY.js +0 -1
- package/dist/assets/vesper-DRje8inN.js +0 -1
- package/dist/assets/vhdl-CeAyd5Ju.js +0 -1
- package/dist/assets/viml-CJc9bBzg.js +0 -1
- package/dist/assets/vitesse-black-Bkuqu6BP.js +0 -1
- package/dist/assets/vitesse-dark-D0r3Knsf.js +0 -1
- package/dist/assets/vitesse-light-CVO1_9PV.js +0 -1
- package/dist/assets/vue-BqiEGhQt.js +0 -1
- package/dist/assets/vue-html-AaS7Mt5G.js +0 -1
- package/dist/assets/vue-vine-BoDAl6tE.js +0 -1
- package/dist/assets/vyper-CDx5xZoG.js +0 -1
- package/dist/assets/wasm-CG6Dc4jp.js +0 -1
- package/dist/assets/wenyan-BV7otONQ.js +0 -1
- package/dist/assets/wgsl-Dx-B1_4e.js +0 -1
- package/dist/assets/wikitext-BhOHFoWU.js +0 -1
- package/dist/assets/wit-5i3qLPDT.js +0 -1
- package/dist/assets/wolfram-lXgVvXCa.js +0 -1
- package/dist/assets/xsl-CtQFsRM5.js +0 -1
- package/dist/assets/zenscript-DVFEvuxE.js +0 -1
- package/dist/assets/zig-VOosw3JB.js +0 -1
- /package/dist/assets/{shellscript-Yzrsuije.js → bash-Yzrsuije.js} +0 -0
- /package/dist/assets/{docker-BcOcwvcX.js → dockerfile-BcOcwvcX.js} +0 -0
- /package/dist/assets/{make-CHLpvVh8.js → makefile-CHLpvVh8.js} +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// OAuth sign-in for URL MCP servers, the way MCP clients do it: discover
|
|
2
2
|
// the authorization server from the MCP server's 401, sign in as the app
|
|
3
3
|
// the person registered for this server or else register as a public
|
|
4
|
-
// client when the server allows it, run PKCE through the person's browser
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// client when the server allows it, run PKCE through the person's browser,
|
|
5
|
+
// then keep the tokens fresh. The browser comes back to a loopback listener
|
|
6
|
+
// on this machine; a browser on another computer (My Cloud, a hosted
|
|
7
|
+
// workspace) comes back to this server's own https address instead, and
|
|
8
|
+
// where it cannot, the person pastes the address the browser ended on.
|
|
7
9
|
// Engines never see this: they get an ordinary Authorization header.
|
|
8
10
|
//
|
|
9
11
|
// The loopback listener and callback checks follow
|
|
@@ -27,7 +29,10 @@ const DEFAULT_LIFETIME_MS = 5 * 60_000;
|
|
|
27
29
|
* that lapses mid-way. */
|
|
28
30
|
const REFRESH_MARGIN_MS = 2 * 60_000;
|
|
29
31
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
30
|
-
|
|
32
|
+
/** The callback path, on a loopback listener and on this server's own
|
|
33
|
+
* https address alike. */
|
|
34
|
+
export const MCP_OAUTH_CALLBACK_PATH = "/mcp-oauth/callback";
|
|
35
|
+
const CALLBACK_PATH = MCP_OAUTH_CALLBACK_PATH;
|
|
31
36
|
/** complete()'s answer for a flow that ended while its code was spent. */
|
|
32
37
|
const DISCARDED = "discarded";
|
|
33
38
|
export class McpSignInError extends Error {
|
|
@@ -53,6 +58,33 @@ function preferredPort(url) {
|
|
|
53
58
|
export function mcpOAuthRedirectUri(url) {
|
|
54
59
|
return `http://127.0.0.1:${preferredPort(url)}${CALLBACK_PATH}`;
|
|
55
60
|
}
|
|
61
|
+
/** An exact https origin (no path, no credentials), or null. */
|
|
62
|
+
function httpsOrigin(value) {
|
|
63
|
+
if (!value)
|
|
64
|
+
return null;
|
|
65
|
+
try {
|
|
66
|
+
const url = new URL(value);
|
|
67
|
+
return url.protocol === "https:" && !url.username && !url.password && value.replace(/\/+$/, "") === url.origin ? url.origin : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** The https address of this server that a browser on another computer
|
|
74
|
+
* comes back to after approving a sign-in, or null for the loopback
|
|
75
|
+
* callback. `browserOrigin` is the origin that browser proved it is using
|
|
76
|
+
* (its Origin header, equal to the request's own origin); `configured` are
|
|
77
|
+
* this server's public addresses (a Cloud home's, a hosted workspace's, a
|
|
78
|
+
* connected domain, OMB_PUBLIC_URL). The browser's own origin wins when it
|
|
79
|
+
* is one of them or none is set; otherwise the first configured one does,
|
|
80
|
+
* never a Host header nobody vouched for. */
|
|
81
|
+
export function mcpCallbackOrigin(browserOrigin, configured) {
|
|
82
|
+
const known = configured.map(httpsOrigin).filter((origin) => origin !== null);
|
|
83
|
+
const proven = httpsOrigin(browserOrigin);
|
|
84
|
+
if (proven && (known.length === 0 || known.includes(proven)))
|
|
85
|
+
return proven;
|
|
86
|
+
return known[0] ?? null;
|
|
87
|
+
}
|
|
56
88
|
/** RFC 8414 §2: an absent list means client_secret_basic. */
|
|
57
89
|
function tokenAuthMethod(meta, secret) {
|
|
58
90
|
if (!secret)
|
|
@@ -116,6 +148,9 @@ function tokensFrom(body, previous) {
|
|
|
116
148
|
...(typeof body.scope === "string" ? { scope: body.scope } : {}),
|
|
117
149
|
};
|
|
118
150
|
}
|
|
151
|
+
/** The authorization server answered a registration, and said no. */
|
|
152
|
+
class RegistrationRefused extends Error {
|
|
153
|
+
}
|
|
119
154
|
export class McpOAuthManager {
|
|
120
155
|
store;
|
|
121
156
|
lifetimeMs;
|
|
@@ -156,7 +191,7 @@ export class McpOAuthManager {
|
|
|
156
191
|
const { tokens: _dropped, ...rest } = this.store.get(name, url) ?? { url };
|
|
157
192
|
this.store.put(name, { ...rest, url, state: "needs-sign-in" });
|
|
158
193
|
}
|
|
159
|
-
start(name, url, wwwAuthenticate, owner = "loopback") {
|
|
194
|
+
start(name, url, wwwAuthenticate, owner = "loopback", browser = { remote: false }) {
|
|
160
195
|
const current = this.flows.get(name);
|
|
161
196
|
if (current?.status.phase === "waiting" && !this.isOwnerLive(current.owner)) {
|
|
162
197
|
this.finish(current, "cancelled", "Your session ended. Start again.");
|
|
@@ -174,7 +209,7 @@ export class McpOAuthManager {
|
|
|
174
209
|
}
|
|
175
210
|
return pending.promise;
|
|
176
211
|
}
|
|
177
|
-
const starting = { owner, url, cancelled: false, promise: undefined };
|
|
212
|
+
const starting = { owner, url, browser, cancelled: false, promise: undefined };
|
|
178
213
|
starting.promise = this.begin(name, url, starting, wwwAuthenticate).finally(() => this.starting.delete(name));
|
|
179
214
|
this.starting.set(name, starting);
|
|
180
215
|
return starting.promise;
|
|
@@ -225,6 +260,24 @@ export class McpOAuthManager {
|
|
|
225
260
|
await this.acceptCallback(flow, callback);
|
|
226
261
|
return { ...flow.status };
|
|
227
262
|
}
|
|
263
|
+
/** The browser came back to this server's own https address. `host` is
|
|
264
|
+
* the address it asked for (the Host header) and `search` the query it
|
|
265
|
+
* brought. The flow is the one that redirect URI and state belong to; it
|
|
266
|
+
* then passes the same one-shot checks as the listener and paste-back.
|
|
267
|
+
* The scheme is the redirect URI's: a proxy in front of this server may
|
|
268
|
+
* end TLS, so the request itself cannot say it was https. */
|
|
269
|
+
async publicCallback(host, search) {
|
|
270
|
+
const state = new URLSearchParams(search).get("state") ?? "";
|
|
271
|
+
let match;
|
|
272
|
+
for (const flow of this.flows.values()) {
|
|
273
|
+
const expected = new URL(flow.redirectUri);
|
|
274
|
+
if (!flow.server && expected.host === host?.trim().toLowerCase() && sameSecret(state, flow.state))
|
|
275
|
+
match = flow;
|
|
276
|
+
}
|
|
277
|
+
if (!match)
|
|
278
|
+
return { status: 400, text: "Invalid sign-in callback. Return to OpenMausBot and try again." };
|
|
279
|
+
return this.answer(match, new URL(`${new URL(match.redirectUri).origin}${CALLBACK_PATH}${search.startsWith("?") ? search : ""}`));
|
|
280
|
+
}
|
|
228
281
|
/** Revoke with the server when it offers that (best effort), then forget. */
|
|
229
282
|
async signOut(name, url) {
|
|
230
283
|
this.cancel(name);
|
|
@@ -342,7 +395,7 @@ export class McpOAuthManager {
|
|
|
342
395
|
});
|
|
343
396
|
const body = await response.json().catch(() => ({}));
|
|
344
397
|
if (!response.ok || typeof body.client_id !== "string" || !body.client_id) {
|
|
345
|
-
throw new
|
|
398
|
+
throw new RegistrationRefused("This server would not register OpenMausBot for sign-in. Try again later.");
|
|
346
399
|
}
|
|
347
400
|
return body.client_id;
|
|
348
401
|
}
|
|
@@ -354,6 +407,23 @@ export class McpOAuthManager {
|
|
|
354
407
|
if (!meta)
|
|
355
408
|
throw new McpOAuthError("not-oauth", "This server does not offer an OAuth sign-in. Add its token as a header instead.");
|
|
356
409
|
const registered = this.clients(name, url);
|
|
410
|
+
// A browser on another computer comes back to this server's own https
|
|
411
|
+
// address. Only for a client registered now, for that address: an app
|
|
412
|
+
// registered in advance keeps the loopback redirect it was given. A
|
|
413
|
+
// server that refuses the address gets the loopback callback, pasted.
|
|
414
|
+
if (!registered && starting.browser.remote && starting.browser.origin) {
|
|
415
|
+
const redirectUri = `${starting.browser.origin}${CALLBACK_PATH}`;
|
|
416
|
+
let clientId = null;
|
|
417
|
+
try {
|
|
418
|
+
clientId = await this.register(meta, redirectUri);
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (!(error instanceof RegistrationRefused))
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
if (clientId)
|
|
425
|
+
return { ...this.open(name, url, starting, meta, redirectUri, { id: clientId, method: "none" }, null, meta.scopes).status };
|
|
426
|
+
}
|
|
357
427
|
const server = createServer();
|
|
358
428
|
try {
|
|
359
429
|
await listen(server, preferredPort(url));
|
|
@@ -374,40 +444,46 @@ export class McpOAuthManager {
|
|
|
374
444
|
? { id: registered.clientId, ...(registered.clientSecret ? { secret: registered.clientSecret } : {}), method: tokenAuthMethod(meta, registered.clientSecret) }
|
|
375
445
|
: { id: await this.register(meta, redirectUri), method: "none" };
|
|
376
446
|
const scopes = registered?.scopes?.length ? registered.scopes : meta.scopes;
|
|
377
|
-
|
|
378
|
-
throw new McpSignInError("Sign-in cancelled because the session or server changed.", 409);
|
|
379
|
-
const state = randomBytes(32).toString("base64url");
|
|
380
|
-
const verifier = randomBytes(32).toString("base64url");
|
|
381
|
-
const authorize = new URL(meta.authorizationEndpoint);
|
|
382
|
-
authorize.searchParams.set("response_type", "code");
|
|
383
|
-
authorize.searchParams.set("client_id", client.id);
|
|
384
|
-
authorize.searchParams.set("redirect_uri", redirectUri);
|
|
385
|
-
authorize.searchParams.set("state", state);
|
|
386
|
-
authorize.searchParams.set("code_challenge", createHash("sha256").update(verifier).digest("base64url"));
|
|
387
|
-
authorize.searchParams.set("code_challenge_method", "S256");
|
|
388
|
-
authorize.searchParams.set("resource", meta.resource);
|
|
389
|
-
if (scopes?.length)
|
|
390
|
-
authorize.searchParams.set("scope", scopes.join(" "));
|
|
391
|
-
const status = {
|
|
392
|
-
phase: "waiting",
|
|
393
|
-
flowId: randomUUID(),
|
|
394
|
-
authorizationUrl: authorize.toString(),
|
|
395
|
-
expiresAt: new Date(Date.now() + this.lifetimeMs).toISOString(),
|
|
396
|
-
};
|
|
397
|
-
const flow = {
|
|
398
|
-
owner: starting.owner, name, url, status, state, verifier, redirectUri, client, meta, server, consumed: false,
|
|
399
|
-
expiry: setTimeout(() => this.finish(flow, "expired", "Sign-in expired. Start again."), this.lifetimeMs),
|
|
400
|
-
};
|
|
401
|
-
flow.expiry.unref();
|
|
447
|
+
const flow = this.open(name, url, starting, meta, redirectUri, client, server, scopes);
|
|
402
448
|
server.on("request", (request, response) => void this.callback(flow, request, response));
|
|
403
|
-
|
|
404
|
-
return { ...status };
|
|
449
|
+
return { ...flow.status };
|
|
405
450
|
}
|
|
406
451
|
catch (error) {
|
|
407
452
|
server.close();
|
|
408
453
|
throw error;
|
|
409
454
|
}
|
|
410
455
|
}
|
|
456
|
+
/** Make the authorize link and keep the flow waiting for its callback. */
|
|
457
|
+
open(name, url, starting, meta, redirectUri, client, server, scopes) {
|
|
458
|
+
if (starting.cancelled || !this.isOwnerLive(starting.owner))
|
|
459
|
+
throw new McpSignInError("Sign-in cancelled because the session or server changed.", 409);
|
|
460
|
+
const state = randomBytes(32).toString("base64url");
|
|
461
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
462
|
+
const authorize = new URL(meta.authorizationEndpoint);
|
|
463
|
+
authorize.searchParams.set("response_type", "code");
|
|
464
|
+
authorize.searchParams.set("client_id", client.id);
|
|
465
|
+
authorize.searchParams.set("redirect_uri", redirectUri);
|
|
466
|
+
authorize.searchParams.set("state", state);
|
|
467
|
+
authorize.searchParams.set("code_challenge", createHash("sha256").update(verifier).digest("base64url"));
|
|
468
|
+
authorize.searchParams.set("code_challenge_method", "S256");
|
|
469
|
+
authorize.searchParams.set("resource", meta.resource);
|
|
470
|
+
if (scopes?.length)
|
|
471
|
+
authorize.searchParams.set("scope", scopes.join(" "));
|
|
472
|
+
const status = {
|
|
473
|
+
phase: "waiting",
|
|
474
|
+
flowId: randomUUID(),
|
|
475
|
+
authorizationUrl: authorize.toString(),
|
|
476
|
+
expiresAt: new Date(Date.now() + this.lifetimeMs).toISOString(),
|
|
477
|
+
...(server && starting.browser.remote ? { pasteBack: true } : {}),
|
|
478
|
+
};
|
|
479
|
+
const flow = {
|
|
480
|
+
owner: starting.owner, name, url, status, state, verifier, redirectUri, client, meta, server, consumed: false,
|
|
481
|
+
expiry: setTimeout(() => this.finish(flow, "expired", "Sign-in expired. Start again."), this.lifetimeMs),
|
|
482
|
+
};
|
|
483
|
+
flow.expiry.unref();
|
|
484
|
+
this.flows.set(name, flow);
|
|
485
|
+
return flow;
|
|
486
|
+
}
|
|
411
487
|
async callback(flow, request, response) {
|
|
412
488
|
response.setHeader("Cache-Control", "no-store");
|
|
413
489
|
response.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
@@ -426,23 +502,25 @@ export class McpOAuthManager {
|
|
|
426
502
|
response.writeHead(400).end("Invalid sign-in callback. Return to OpenMausBot and try again.");
|
|
427
503
|
return;
|
|
428
504
|
}
|
|
505
|
+
const answer = request.method === "GET"
|
|
506
|
+
? await this.answer(flow, callback)
|
|
507
|
+
: { status: 400, text: "Invalid or expired sign-in. Return to OpenMausBot and try again." };
|
|
508
|
+
response.writeHead(answer.status).end(answer.text);
|
|
509
|
+
}
|
|
510
|
+
/** Spend a callback the browser brought back, and say how it went in
|
|
511
|
+
* words fit for a plain page: never the code or the server's own text. */
|
|
512
|
+
async answer(flow, callback) {
|
|
429
513
|
try {
|
|
430
|
-
if (request.method !== "GET")
|
|
431
|
-
throw new McpSignInError("Invalid sign-in callback.", 400);
|
|
432
514
|
await this.acceptCallback(flow, callback);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
else {
|
|
437
|
-
response.end("Signed in. You can close this tab and return to OpenMausBot.");
|
|
438
|
-
}
|
|
515
|
+
return flow.status.phase === "failed"
|
|
516
|
+
? { status: 400, text: `${flow.status.message} You can close this tab.` }
|
|
517
|
+
: { status: 200, text: "Signed in. You can close this tab and return to OpenMausBot." };
|
|
439
518
|
}
|
|
440
519
|
catch (error) {
|
|
441
|
-
|
|
442
|
-
.end("Invalid or expired sign-in. Return to OpenMausBot and try again.");
|
|
520
|
+
return { status: error instanceof McpSignInError ? error.status : 400, text: "Invalid or expired sign-in. Return to OpenMausBot and try again." };
|
|
443
521
|
}
|
|
444
522
|
}
|
|
445
|
-
/**
|
|
523
|
+
/** The listener, the public callback and authenticated paste-back all use the same one-shot checks. */
|
|
446
524
|
async acceptCallback(flow, callback) {
|
|
447
525
|
if (!this.isOwnerLive(flow.owner))
|
|
448
526
|
this.finish(flow, "cancelled", "Your session ended. Start again.");
|
|
@@ -519,8 +597,8 @@ export class McpOAuthManager {
|
|
|
519
597
|
if (flow.status.phase !== "waiting")
|
|
520
598
|
return;
|
|
521
599
|
clearTimeout(flow.expiry);
|
|
522
|
-
flow.server
|
|
523
|
-
flow.server
|
|
600
|
+
flow.server?.close();
|
|
601
|
+
flow.server?.closeIdleConnections();
|
|
524
602
|
flow.status = { ...flow.status, phase, authorizationUrl: null, ...(message ? { message } : {}) };
|
|
525
603
|
}
|
|
526
604
|
}
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// reader: the HTTP route supplies only roots derived from the exact bot
|
|
4
4
|
// message, and this helper keeps the eventual file handle inside one of them.
|
|
5
5
|
import { constants } from "node:fs";
|
|
6
|
-
import { open, realpath, stat } from "node:fs/promises";
|
|
7
|
-
import { basename, extname, isAbsolute, posix, relative, resolve, sep, win32 } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { lstat, open, readdir, realpath, stat } from "node:fs/promises";
|
|
7
|
+
import { basename, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
10
10
|
import { windowsPathDestinations } from "../shared/markdown-windows-paths.js";
|
|
11
11
|
export const MESSAGE_FILE_MAX_BYTES = 25 * 1024 * 1024;
|
|
@@ -392,6 +392,8 @@ export async function openMessageFile(href, roots) {
|
|
|
392
392
|
const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
393
393
|
handle = await open(canonicalBefore, constants.O_RDONLY | noFollow);
|
|
394
394
|
const opened = await handle.stat();
|
|
395
|
+
if (opened.isDirectory())
|
|
396
|
+
throw statusError(400, "the link points to a folder, not a file", "directory");
|
|
395
397
|
if (!opened.isFile())
|
|
396
398
|
throw statusError(400, "the link does not point to a regular file");
|
|
397
399
|
if (opened.size > MESSAGE_FILE_MAX_BYTES) {
|
|
@@ -422,6 +424,96 @@ export async function openMessageFile(href, roots) {
|
|
|
422
424
|
throw outsideWorkspace();
|
|
423
425
|
throw statusError(404, "the linked file is unavailable");
|
|
424
426
|
}
|
|
427
|
+
export const MESSAGE_FOLDER_MAX_ENTRIES = 200;
|
|
428
|
+
/**
|
|
429
|
+
* List the regular files directly inside a message-linked folder, under the
|
|
430
|
+
* same roots and canonical containment as openMessageFile. Subfolders,
|
|
431
|
+
* sockets and any entry that resolves outside the root are left out. Each
|
|
432
|
+
* entry is then opened by name through openMessageFile, which re-checks it.
|
|
433
|
+
*/
|
|
434
|
+
export async function listMessageFolder(href, roots) {
|
|
435
|
+
const { path: _path, ...listing } = await readMessageFolder(href, roots);
|
|
436
|
+
return listing;
|
|
437
|
+
}
|
|
438
|
+
/** Open one file of a listed folder by its plain name, with every file check. */
|
|
439
|
+
export async function openMessageFolderEntry(href, entry, roots) {
|
|
440
|
+
const folder = await readMessageFolder(href, roots);
|
|
441
|
+
if (!folder.entries.some((listed) => listed.name === entry)) {
|
|
442
|
+
throw statusError(404, "the linked file is unavailable");
|
|
443
|
+
}
|
|
444
|
+
return openMessageFile(pathToFileURL(join(folder.path, entry)).href, roots);
|
|
445
|
+
}
|
|
446
|
+
async function readMessageFolder(href, roots) {
|
|
447
|
+
const requested = referencedPath(href);
|
|
448
|
+
const canonicalRoots = (await Promise.all(roots.map(async (root) => {
|
|
449
|
+
try {
|
|
450
|
+
const canonical = await realpath(root);
|
|
451
|
+
return (await stat(canonical)).isDirectory() ? canonical : null;
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
}))).filter((root) => Boolean(root));
|
|
457
|
+
if (canonicalRoots.length === 0)
|
|
458
|
+
throw statusError(404, "the linked folder is unavailable");
|
|
459
|
+
const candidates = isAbsolute(requested)
|
|
460
|
+
? [resolve(requested)]
|
|
461
|
+
: canonicalRoots.map((root) => resolve(root, requested));
|
|
462
|
+
let sawOutsideRoot = false;
|
|
463
|
+
for (const candidate of new Set(candidates)) {
|
|
464
|
+
let folder;
|
|
465
|
+
try {
|
|
466
|
+
folder = await realpath(candidate);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const root = canonicalRoots.find((allowed) => containedBy(allowed, folder));
|
|
472
|
+
if (!root) {
|
|
473
|
+
sawOutsideRoot = true;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (!(await stat(folder)).isDirectory())
|
|
477
|
+
throw statusError(400, "the link does not point to a folder");
|
|
478
|
+
const names = (await readdir(folder)).sort((a, b) => a.localeCompare(b));
|
|
479
|
+
const entries = [];
|
|
480
|
+
let truncated = false;
|
|
481
|
+
for (const name of names) {
|
|
482
|
+
const child = join(folder, name);
|
|
483
|
+
try {
|
|
484
|
+
const own = await lstat(child);
|
|
485
|
+
if (!own.isFile() && !own.isSymbolicLink())
|
|
486
|
+
continue;
|
|
487
|
+
const target = await realpath(child);
|
|
488
|
+
if (!containedBy(root, target))
|
|
489
|
+
continue;
|
|
490
|
+
const info = await stat(target);
|
|
491
|
+
if (!info.isFile())
|
|
492
|
+
continue;
|
|
493
|
+
if (entries.length >= MESSAGE_FOLDER_MAX_ENTRIES) {
|
|
494
|
+
truncated = true;
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
entries.push({ name, bytes: info.size, mime: mimeFor(target) });
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return { name: basename(folder), entries, truncated, path: folder };
|
|
504
|
+
}
|
|
505
|
+
if (sawOutsideRoot)
|
|
506
|
+
throw outsideWorkspace();
|
|
507
|
+
throw statusError(404, "the linked folder is unavailable");
|
|
508
|
+
}
|
|
509
|
+
/** One plain file name inside a listed folder; never a path or a dot entry. */
|
|
510
|
+
export function messageFolderEntryName(value) {
|
|
511
|
+
if (typeof value !== "string" || !value || value.length > 255)
|
|
512
|
+
return null;
|
|
513
|
+
if (value === "." || value === ".." || /[\\/\0]/.test(value))
|
|
514
|
+
return null;
|
|
515
|
+
return value;
|
|
516
|
+
}
|
|
425
517
|
/** A safe attachment header with a readable ASCII fallback and UTF-8 name. */
|
|
426
518
|
export function messageFileDisposition(name) {
|
|
427
519
|
const safeName = safeDisplayName(name);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { harnessCapabilityLines } from "./harness-capabilities.js";
|
|
2
2
|
import { newId } from "./contracts.js";
|
|
3
|
+
import { UNDO_STALE } from "./direct-apply.js";
|
|
3
4
|
import { redactSecretsInText } from "./redact.js";
|
|
4
5
|
const MAX_REASON = 500;
|
|
5
6
|
const STALE = "This bot's default model changed after this card was prepared. Ask the bot to review it and propose again.";
|
|
@@ -128,10 +129,11 @@ export class ModelRequestService {
|
|
|
128
129
|
before,
|
|
129
130
|
};
|
|
130
131
|
const copy = modelCardCopy({ targetName, crossBot, before, selection, reason, capabilityLines });
|
|
131
|
-
const
|
|
132
|
+
const grant = submitted ? this.autoApply?.(args.botId, args.threadId, targetBotId) ?? null : null;
|
|
133
|
+
const automatic = grant !== null;
|
|
134
|
+
const persistence = this.canPersist?.(args.botId, args.threadId, !automatic);
|
|
132
135
|
if (persistence && !persistence.ok)
|
|
133
136
|
throw new ModelRequestError(persistence.error, persistence.status);
|
|
134
|
-
const automatic = submitted && this.autoApply?.(args.botId, args.threadId) === true;
|
|
135
137
|
const messageInput = {
|
|
136
138
|
role: "bot",
|
|
137
139
|
kind: "options",
|
|
@@ -152,10 +154,59 @@ export class ModelRequestService {
|
|
|
152
154
|
if (!automatic)
|
|
153
155
|
return proposal;
|
|
154
156
|
const result = this.resolve({ botId: args.botId, threadId: args.threadId, requestId, behavior: "allow" });
|
|
155
|
-
if (result.state === "applied")
|
|
156
|
-
|
|
157
|
+
if (result.state === "applied") {
|
|
158
|
+
if (!result.settlementPending)
|
|
159
|
+
this.recordAutoApplied(args.threadId, message.id);
|
|
160
|
+
return { ...proposal, result, appliedBy: grant };
|
|
161
|
+
}
|
|
157
162
|
throw new ModelRequestError(result.state === "invalid" ? result.error : "The model change could not be applied", result.state === "invalid" ? result.status : 409);
|
|
158
163
|
}
|
|
164
|
+
/** Marks a card whose change applied without a person. A failure here only
|
|
165
|
+
* loses the Undo. */
|
|
166
|
+
recordAutoApplied(threadId, messageId) {
|
|
167
|
+
try {
|
|
168
|
+
const card = this.store.messagesFor(threadId).find((candidate) => candidate.id === messageId)?.card;
|
|
169
|
+
if (card?.modelRequest)
|
|
170
|
+
this.store.patchMessage(threadId, messageId, { card: { ...card, autoApplied: true } });
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// The model change itself is durable; only its Undo is lost.
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Puts the previous default model back after a change that applied
|
|
177
|
+
* without a person, through the same checks a switch gets (validateModel:
|
|
178
|
+
* a pending approval-level change, or permissions the old engine cannot
|
|
179
|
+
* keep, refuse it). Refuses, applying nothing, once the default moved. */
|
|
180
|
+
undo(args) {
|
|
181
|
+
const message = this.store
|
|
182
|
+
.messagesFor(args.threadId)
|
|
183
|
+
.find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.modelRequest);
|
|
184
|
+
const card = message?.card;
|
|
185
|
+
const payload = card?.modelRequest;
|
|
186
|
+
if (!message || !card || !payload)
|
|
187
|
+
return { claimed: false };
|
|
188
|
+
if (card.undone)
|
|
189
|
+
return { claimed: true, state: "already_undone" };
|
|
190
|
+
const cannot = (error, status = 409) => ({ claimed: true, state: "invalid", error, status });
|
|
191
|
+
if (!card.autoApplied || card.answered !== "allow")
|
|
192
|
+
return cannot("Only a change that applied on its own can be undone here.");
|
|
193
|
+
if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
|
|
194
|
+
return cannot("This model change belongs to another conversation", 403);
|
|
195
|
+
}
|
|
196
|
+
const target = this.store.bot(payload.targetBotId);
|
|
197
|
+
if (!target || !sameSelection(target.modelSelection, payload.selection)) {
|
|
198
|
+
return { claimed: true, state: "invalid", error: UNDO_STALE, status: 409, stale: true };
|
|
199
|
+
}
|
|
200
|
+
if (this.validateModel) {
|
|
201
|
+
const refusal = this.validateModel(payload.before, target);
|
|
202
|
+
if (refusal)
|
|
203
|
+
return cannot(refusal);
|
|
204
|
+
}
|
|
205
|
+
if (!this.store.applyModelDefault(target.id, payload.before))
|
|
206
|
+
return cannot(NO_SUCH_BOT, 404);
|
|
207
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, undone: true } });
|
|
208
|
+
return { claimed: true, state: "undone", targetBotId: target.id };
|
|
209
|
+
}
|
|
159
210
|
/** Claims a model card even after it was settled, so a duplicate click
|
|
160
211
|
* never re-applies an already-applied change. */
|
|
161
212
|
resolve(args) {
|
|
@@ -11,6 +11,7 @@ import { PROFILE_REQUEST_FIELDS } from "../shared/profile-request.js";
|
|
|
11
11
|
import { parseBotProfilePatch } from "./bot-profile.js";
|
|
12
12
|
import { validateBotCwd } from "./bot-cwd.js";
|
|
13
13
|
import { newId } from "./contracts.js";
|
|
14
|
+
import { UNDO_STALE } from "./direct-apply.js";
|
|
14
15
|
import { profileRevision, profileSnapshot } from "./profile-revision.js";
|
|
15
16
|
import { recordProfileChange } from "./profile-versions.js";
|
|
16
17
|
import { redactSecretsInText } from "./redact.js";
|
|
@@ -205,6 +206,9 @@ export class ProfileRequestService {
|
|
|
205
206
|
const snapshot = profileSnapshot(target);
|
|
206
207
|
const before = {};
|
|
207
208
|
const finalChanges = {};
|
|
209
|
+
// Undo restores `before`, so it is offered only when the card holds the
|
|
210
|
+
// old values exactly (credential-shaped text is scrubbed from cards).
|
|
211
|
+
let exactBefore = true;
|
|
208
212
|
for (const field of PROFILE_REQUEST_FIELDS) {
|
|
209
213
|
if (field === "notifications" || field === "speakReplies") {
|
|
210
214
|
const value = changes[field];
|
|
@@ -218,6 +222,8 @@ export class ProfileRequestService {
|
|
|
218
222
|
if (value === undefined || value === snapshot[field])
|
|
219
223
|
continue;
|
|
220
224
|
before[field] = redactSecretsInText(snapshot[field]);
|
|
225
|
+
if (before[field] !== snapshot[field])
|
|
226
|
+
exactBefore = false;
|
|
221
227
|
finalChanges[field] = value;
|
|
222
228
|
}
|
|
223
229
|
}
|
|
@@ -240,11 +246,14 @@ export class ProfileRequestService {
|
|
|
240
246
|
expectedRevision: profileRevision(target),
|
|
241
247
|
};
|
|
242
248
|
const copy = profileCardCopy({ name: targetName, crossBot }, snapshot, before, finalChanges, reason);
|
|
243
|
-
const
|
|
249
|
+
const grant = submitted ? this.autoApply?.(args.botId, args.threadId, targetBotId) ?? null : null;
|
|
250
|
+
// A new working folder widens what the bot's tools read and write
|
|
251
|
+
// without asking, so below Full access it keeps today's card.
|
|
252
|
+
const automatic = grant === "full-access" || (grant === "self" && finalChanges.cwd === undefined);
|
|
253
|
+
const persistence = this.canPersist?.(args.botId, args.threadId, !automatic);
|
|
244
254
|
if (persistence && !persistence.ok) {
|
|
245
255
|
throw new ProfileRequestError(persistence.error, persistence.status);
|
|
246
256
|
}
|
|
247
|
-
const automatic = submitted && this.autoApply?.(args.botId, args.threadId) === true;
|
|
248
257
|
const messageInput = {
|
|
249
258
|
role: "bot",
|
|
250
259
|
kind: "options",
|
|
@@ -267,10 +276,77 @@ export class ProfileRequestService {
|
|
|
267
276
|
// The hidden receipt is persisted before the profile changes. The same
|
|
268
277
|
// validation and durable commit marker serve both automatic and human decisions.
|
|
269
278
|
const result = this.resolve({ botId: args.botId, threadId: args.threadId, requestId, behavior: "allow" });
|
|
270
|
-
if (result.state === "applied")
|
|
271
|
-
|
|
279
|
+
if (result.state === "applied") {
|
|
280
|
+
if (!result.settlementPending)
|
|
281
|
+
this.recordAutoApplied(args.threadId, message.id, targetBotId, exactBefore);
|
|
282
|
+
return { ...proposal, result, appliedBy: grant };
|
|
283
|
+
}
|
|
272
284
|
throw new ProfileRequestError(result.state === "invalid" ? result.error : "The profile change could not be applied", result.state === "invalid" ? result.status : 409);
|
|
273
285
|
}
|
|
286
|
+
/** Marks a card whose change applied without a person, with the revision
|
|
287
|
+
* its Undo checks. A failure here only loses the Undo. */
|
|
288
|
+
recordAutoApplied(threadId, messageId, targetBotId, exactBefore) {
|
|
289
|
+
try {
|
|
290
|
+
const card = this.store.messagesFor(threadId).find((candidate) => candidate.id === messageId)?.card;
|
|
291
|
+
const target = this.store.bot(targetBotId);
|
|
292
|
+
if (!card?.profileRequest || !target)
|
|
293
|
+
return;
|
|
294
|
+
this.store.patchMessage(threadId, messageId, {
|
|
295
|
+
card: {
|
|
296
|
+
...card,
|
|
297
|
+
autoApplied: true,
|
|
298
|
+
profileRequest: { ...card.profileRequest, ...(exactBefore ? { undo: { appliedRevision: profileRevision(target) } } : {}) },
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// The profile change itself is durable; only its Undo is lost.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/** Puts back the fields a change that applied without a person changed.
|
|
307
|
+
* Authorized by the caller exactly like answering the card; refuses,
|
|
308
|
+
* applying nothing, once the profile changed since. */
|
|
309
|
+
undo(args) {
|
|
310
|
+
const message = this.store
|
|
311
|
+
.messagesFor(args.threadId)
|
|
312
|
+
.find((candidate) => candidate.card?.requestId === args.requestId && candidate.card.profileRequest);
|
|
313
|
+
const card = message?.card;
|
|
314
|
+
const payload = card?.profileRequest;
|
|
315
|
+
if (!message || !card || !payload)
|
|
316
|
+
return { claimed: false };
|
|
317
|
+
if (card.undone)
|
|
318
|
+
return { claimed: true, state: "already_undone" };
|
|
319
|
+
const cannot = (error, status = 409) => ({ claimed: true, state: "invalid", error, status });
|
|
320
|
+
if (!card.autoApplied || card.answered !== "allow")
|
|
321
|
+
return cannot("Only a change that applied on its own can be undone here.");
|
|
322
|
+
if (payload.botId !== args.botId || payload.threadId !== args.threadId) {
|
|
323
|
+
return cannot("This profile change belongs to another conversation", 403);
|
|
324
|
+
}
|
|
325
|
+
if (!payload.undo)
|
|
326
|
+
return cannot("This change can't be undone here.");
|
|
327
|
+
const target = this.store.bot(payload.targetBotId);
|
|
328
|
+
if (!target || profileRevision(target) !== payload.undo.appliedRevision) {
|
|
329
|
+
return { claimed: true, state: "invalid", error: UNDO_STALE, status: 409, stale: true };
|
|
330
|
+
}
|
|
331
|
+
const { cwd, ...rest } = payload.before;
|
|
332
|
+
const patch = {};
|
|
333
|
+
for (const field of PROFILE_REQUEST_FIELDS) {
|
|
334
|
+
if (field === "cwd" || payload.changes[field] === undefined)
|
|
335
|
+
continue;
|
|
336
|
+
patch[field] = rest[field];
|
|
337
|
+
}
|
|
338
|
+
if (payload.changes.cwd !== undefined) {
|
|
339
|
+
const checked = validateBotCwd(cwd || null);
|
|
340
|
+
if (!checked.ok)
|
|
341
|
+
return cannot(checked.error);
|
|
342
|
+
patch.cwd = checked.cwd ?? undefined;
|
|
343
|
+
}
|
|
344
|
+
if (!this.store.patchBotProfile(target.id, patch))
|
|
345
|
+
return cannot(NO_SUCH_BOT, 404);
|
|
346
|
+
recordProfileChange(target.id, "user", `undo:${message.id}`, payload.changes, payload.before);
|
|
347
|
+
this.store.patchMessage(args.threadId, message.id, { card: { ...card, undone: true } });
|
|
348
|
+
return { claimed: true, state: "undone", targetBotId: target.id };
|
|
349
|
+
}
|
|
274
350
|
/** Claims a profile card even after it was settled, so a duplicate click
|
|
275
351
|
* never re-applies an already-applied change. */
|
|
276
352
|
resolve(args) {
|
|
@@ -283,6 +283,7 @@ export const CLIENT_ALLOW = [
|
|
|
283
283
|
// approvals and cards
|
|
284
284
|
{ methods: ["POST"], path: /^\/api\/bots\/[\w-]+\/respond$/ },
|
|
285
285
|
{ methods: ["POST"], path: /^\/api\/threads\/[\w-]+\/respond$/ },
|
|
286
|
+
{ methods: ["POST"], path: /^\/api\/threads\/[\w-]+\/undo$/ }, // whoever may answer the card: see the handler
|
|
286
287
|
{ methods: ["PATCH"], path: /^\/api\/bots\/[\w-]+\/cards\/[\w-]+$/ },
|
|
287
288
|
{ methods: ["POST"], path: /^\/api\/bots\/[\w-]+\/secret-cards\/[\w-]+\/(?:resume|dismiss)$/ },
|
|
288
289
|
{ methods: ["GET"], path: /^\/api\/bots\/[\w-]+\/connector-cards\/[\w-]+\/status$/ },
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PASS } from "./table.js";
|
|
2
|
+
export function createUndoRoutes(deps) {
|
|
3
|
+
return async ({ req, res, path, method, auth, json, readBody }) => {
|
|
4
|
+
const m = path.match(/^\/api\/threads\/([\w-]+)\/undo$/);
|
|
5
|
+
if (!m || method !== "POST")
|
|
6
|
+
return PASS;
|
|
7
|
+
const threadId = m[1];
|
|
8
|
+
const body = await readBody(req);
|
|
9
|
+
const requestId = typeof body?.requestId === "string" ? body.requestId : "";
|
|
10
|
+
if (!requestId)
|
|
11
|
+
return json(res, 400, { error: "requestId is required" });
|
|
12
|
+
const refusal = deps.refusal(auth, threadId, requestId);
|
|
13
|
+
if (refusal)
|
|
14
|
+
return json(res, 403, { error: refusal });
|
|
15
|
+
deps.undo(auth, res, threadId, requestId);
|
|
16
|
+
};
|
|
17
|
+
}
|