@shawnstack/quickforge 1.7.4 → 1.7.5
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/AgentProfilesPage-XNjuqJs7.js +1 -0
- package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
- package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
- package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
- package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
- package/dist/assets/{SettingsWorkspacePage-B53_eDf2.js → SettingsWorkspacePage-Cb54Ic91.js} +389 -378
- package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
- package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
- package/dist/assets/{TerminalDock-Dgg-_ZmU.js → TerminalDock-D4zJBJc4.js} +2 -2
- package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
- package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
- package/dist/assets/channel-Duu1CuH9.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
- package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
- package/dist/assets/{esm-oa69b5u4.js → esm-Df4edDpj.js} +2 -2
- package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
- package/dist/assets/icons-CXPP1fFT.js +1 -0
- package/dist/assets/index-_ZhK6bvp.js +65 -0
- package/dist/assets/index-iVV2bP_f.css +3 -0
- package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
- package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
- package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
- package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
- package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
- package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
- package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
- package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
- package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
- package/dist/assets/{monaco-BVtCspYZ.js → monaco-D6OhXgjg.js} +1 -1
- package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
- package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
- package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
- package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
- package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-1KC5PyRx.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
- package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BThnIUBs.js} +3 -3
- package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +9 -8
- package/package.json +3 -1
- package/plugins/{openai-documents → documents}/plugin.json +2 -2
- package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
- package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
- package/server/acp/server.mjs +36 -26
- package/server/agent-manager.mjs +130 -28
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +1 -1
- package/server/ai-http-logger.mjs +98 -12
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +102 -0
- package/server/cloud/config.mjs +53 -0
- package/server/cloud/credential-store.mjs +193 -0
- package/server/cloud/identity.mjs +355 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/runtime.mjs +129 -0
- package/server/cloud/service-config.mjs +77 -0
- package/server/index.mjs +28 -11
- package/server/model-catalog.mjs +262 -0
- package/server/plugins/registry.mjs +29 -5
- package/server/routes/agent-profiles.mjs +41 -7
- package/server/routes/agent.mjs +20 -11
- package/server/routes/backup.mjs +7 -1
- package/server/routes/cloud.mjs +227 -0
- package/server/routes/models.mjs +7 -1
- package/server/routes/scheduled-tasks.mjs +38 -8
- package/server/routes/shared-conversation.mjs +62 -58
- package/server/routes/shares.mjs +76 -1
- package/server/routes/storage.mjs +24 -1
- package/server/routes/workspace.mjs +6 -4
- package/server/share-store.mjs +168 -13
- package/server/skills.mjs +30 -3
- package/server/storage.mjs +7 -1
- package/server/subagents.mjs +3 -3
- package/server/tools/definitions.mjs +1 -1
- package/server/tools/index.mjs +2 -2
- package/server/utils/network.mjs +9 -0
- package/server/utils/response.mjs +8 -1
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/UPSTREAM.md +6 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
- package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
- package/dist/assets/PluginsPage-D2862wul.js +0 -1
- package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
- package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
- package/dist/assets/channel-DToA8d-4.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
- package/dist/assets/icons-cTkqD_Gi.js +0 -1
- package/dist/assets/index-B48prgLL.js +0 -65
- package/dist/assets/index-DQhwHHer.css +0 -3
- package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
- package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
- package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
- package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
- /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
- /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
- /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
- /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
- /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
- /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/dist/assets/{web-RgX7H-OH.js → web-DoV8MSMn.js} +0 -0
- /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
- /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
- /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
- /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
- /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
- /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { CloudClient, CloudApiError } from '../cloud/client.mjs'
|
|
2
|
+
import { parseCloudBaseUrl } from '../cloud/config.mjs'
|
|
3
|
+
import { createCloudCredentialStore } from '../cloud/credential-store.mjs'
|
|
4
|
+
import { getCloudRuntime, invalidateCloudRuntime } from '../cloud/runtime.mjs'
|
|
5
|
+
import {
|
|
6
|
+
publicCloudServiceConfig,
|
|
7
|
+
readCloudServiceConfig,
|
|
8
|
+
saveCloudServiceConfig,
|
|
9
|
+
} from '../cloud/service-config.mjs'
|
|
10
|
+
import { isTailscaleAddress } from '../utils/network.mjs'
|
|
11
|
+
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
12
|
+
|
|
13
|
+
const CLOUD_CONFIG_BODY_MAX_BYTES = 16 * 1024
|
|
14
|
+
const CLOUD_ACTION_HEADER = 'x-quickforge-action'
|
|
15
|
+
const CLOUD_ACTION_VALUE = 'cloud-action'
|
|
16
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
|
|
17
|
+
const JSON_WRITE_ROUTES = new Set([
|
|
18
|
+
'POST /api/cloud/test-connection',
|
|
19
|
+
'PUT /api/cloud/config',
|
|
20
|
+
'POST /api/cloud/identity/reset',
|
|
21
|
+
'POST /api/cloud/device/start',
|
|
22
|
+
'POST /api/cloud/device/poll',
|
|
23
|
+
'POST /api/cloud/device/cancel',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
function routeError(message, statusCode, code) {
|
|
27
|
+
const error = new Error(message)
|
|
28
|
+
error.statusCode = statusCode
|
|
29
|
+
error.errorCode = code
|
|
30
|
+
error.code = code
|
|
31
|
+
return error
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mapCloudError(error) {
|
|
35
|
+
if (!(error instanceof CloudApiError)) return error
|
|
36
|
+
const mapped = routeError(error.message, error.status || 503, error.code)
|
|
37
|
+
mapped.retryable = error.retryable
|
|
38
|
+
mapped.details = error.details
|
|
39
|
+
return mapped
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createDefaultRuntime() {
|
|
43
|
+
return getCloudRuntime()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sameCloudUrl(left, right) {
|
|
47
|
+
try {
|
|
48
|
+
return parseCloudBaseUrl(left).href === parseCloudBaseUrl(right).href
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireDangerousConfirmation(body) {
|
|
55
|
+
if (body?.confirm !== 'reset-cloud-identity') {
|
|
56
|
+
throw routeError('Explicit confirmation is required to rebuild the QuickForge Cloud identity.', 400, 'cloud_reset_confirmation_required')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function requireProtectedCloudWrite(req, pathname) {
|
|
61
|
+
const method = String(req.method || 'GET').toUpperCase()
|
|
62
|
+
if (SAFE_METHODS.has(method)) return
|
|
63
|
+
|
|
64
|
+
if (req.headers[CLOUD_ACTION_HEADER] !== CLOUD_ACTION_VALUE) {
|
|
65
|
+
throw routeError('QuickForge Cloud action header is required.', 403, 'cloud_action_header_required')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!JSON_WRITE_ROUTES.has(`${method} ${pathname}`)) return
|
|
69
|
+
const mediaType = String(req.headers['content-type'] || '').split(';', 1)[0].trim().toLowerCase()
|
|
70
|
+
if (mediaType !== 'application/json') {
|
|
71
|
+
throw routeError('QuickForge Cloud JSON requests require Content-Type: application/json.', 415, 'cloud_json_content_type_required')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createCloudRouteHandler({
|
|
76
|
+
runtimeFactory = createDefaultRuntime,
|
|
77
|
+
readServiceConfig = readCloudServiceConfig,
|
|
78
|
+
saveServiceConfig = saveCloudServiceConfig,
|
|
79
|
+
credentialStoreFactory = createCloudCredentialStore,
|
|
80
|
+
invalidateRuntime = invalidateCloudRuntime,
|
|
81
|
+
cloudClientFactory = (config) => new CloudClient(config),
|
|
82
|
+
} = {}) {
|
|
83
|
+
async function runtimeOrError() {
|
|
84
|
+
try {
|
|
85
|
+
return { runtime: await runtimeFactory() }
|
|
86
|
+
} catch (error) {
|
|
87
|
+
return { configurationError: error }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return async function handleCloudApi(req, res, url, {
|
|
92
|
+
isLocalRequest = false,
|
|
93
|
+
remoteAddress,
|
|
94
|
+
remoteAuthorized = false,
|
|
95
|
+
} = {}) {
|
|
96
|
+
const tailscaleAllowed = remoteAuthorized === true
|
|
97
|
+
&& isTailscaleAddress(remoteAddress)
|
|
98
|
+
if (!isLocalRequest && !tailscaleAllowed) {
|
|
99
|
+
throw routeError('QuickForge Cloud is available only on this computer or an authorized Tailscale client.', 403, 'cloud_local_only')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
requireProtectedCloudWrite(req, url.pathname)
|
|
103
|
+
|
|
104
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/config') {
|
|
105
|
+
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/test-connection') {
|
|
110
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
111
|
+
const baseUrl = parseCloudBaseUrl(body?.cloudUrl)
|
|
112
|
+
const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
|
|
113
|
+
const client = cloudClientFactory({
|
|
114
|
+
baseUrl,
|
|
115
|
+
timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
|
|
116
|
+
})
|
|
117
|
+
try {
|
|
118
|
+
const [health, ready] = await Promise.all([client.health(), client.ready()])
|
|
119
|
+
sendJson(res, 200, { ok: true, cloudUrl: baseUrl.href, health, ready })
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw mapCloudError(error)
|
|
122
|
+
}
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (req.method === 'PUT' && url.pathname === '/api/cloud/config') {
|
|
127
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
128
|
+
const nextUrl = parseCloudBaseUrl(body?.cloudUrl).href
|
|
129
|
+
const credentialStore = credentialStoreFactory()
|
|
130
|
+
const identityRecord = await credentialStore.read()
|
|
131
|
+
if (identityRecord.refreshToken || identityRecord.pendingDeviceFlow) {
|
|
132
|
+
const boundUrl = String(identityRecord.pendingDeviceFlow?.sessionCloudUrl || identityRecord.sessionCloudUrl || '').trim()
|
|
133
|
+
const sameUrl = boundUrl ? sameCloudUrl(boundUrl, nextUrl) : false
|
|
134
|
+
if (!sameUrl) {
|
|
135
|
+
throw routeError('A QuickForge Cloud session is active. Sign out first, or explicitly rebuild the identity before switching services.', 409, 'cloud_session_active')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
await saveServiceConfig(nextUrl)
|
|
139
|
+
invalidateRuntime()
|
|
140
|
+
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/identity/reset') {
|
|
145
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
146
|
+
requireDangerousConfirmation(body)
|
|
147
|
+
const { runtime } = await runtimeOrError()
|
|
148
|
+
if (runtime?.enabled) await runtime.identity.resetIdentity()
|
|
149
|
+
else {
|
|
150
|
+
const store = credentialStoreFactory()
|
|
151
|
+
await store.rotateInstallation()
|
|
152
|
+
}
|
|
153
|
+
invalidateRuntime()
|
|
154
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { runtime: current, configurationError } = await runtimeOrError()
|
|
159
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/status') {
|
|
160
|
+
if (configurationError) {
|
|
161
|
+
sendJson(res, 200, { configured: false, mode: 'local', configurationError: configurationError.message })
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
if (!current?.enabled) {
|
|
165
|
+
sendJson(res, 200, { configured: false, mode: 'local' })
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
sendJson(res, 200, { configured: true, ...(await current.identity.status()) })
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (configurationError) throw routeError(configurationError.message, 503, 'cloud_configuration_error')
|
|
173
|
+
if (!current?.enabled) throw routeError('QuickForge Cloud is not configured.', 503, 'cloud_not_configured')
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/guest/start') {
|
|
177
|
+
sendJson(res, 201, await current.identity.startGuest())
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/start') {
|
|
181
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
182
|
+
sendJson(res, 201, await current.identity.startDeviceFlow())
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/poll') {
|
|
186
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
187
|
+
sendJson(res, 200, await current.identity.pollDeviceFlow())
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/cancel') {
|
|
191
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
192
|
+
sendJson(res, 200, await current.identity.cancelDeviceFlow())
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/models') {
|
|
196
|
+
sendJson(res, 200, { items: await current.models.list(undefined, { refresh: true }) })
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/usage') {
|
|
200
|
+
sendJson(res, 200, await current.identity.usage())
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/installations') {
|
|
204
|
+
sendJson(res, 200, await current.identity.installations())
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
|
|
208
|
+
await current.identity.logout()
|
|
209
|
+
invalidateRuntime()
|
|
210
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
const revokeMatch = url.pathname.match(/^\/api\/cloud\/installations\/([^/]+)$/)
|
|
214
|
+
if (req.method === 'DELETE' && revokeMatch) {
|
|
215
|
+
await current.identity.revokeInstallation(decodeURIComponent(revokeMatch[1]))
|
|
216
|
+
sendJson(res, 200, { ok: true })
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
throw mapCloudError(error)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw routeError('Not found', 404, 'route_not_found')
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const handleCloudApi = createCloudRouteHandler()
|
package/server/routes/models.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
|
|
2
|
+
import { listModelCatalog } from '../model-catalog.mjs'
|
|
2
3
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
3
4
|
import { readStore } from '../storage.mjs'
|
|
4
5
|
import { logger } from '../utils/logger.mjs'
|
|
@@ -46,7 +47,12 @@ async function probeModelConnection(model, apiKeyOverride) {
|
|
|
46
47
|
return { ok: true }
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export async function handleModelsApi(req, res, url) {
|
|
50
|
+
export async function handleModelsApi(req, res, url, context = {}) {
|
|
51
|
+
if (req.method === 'GET' && url.pathname === '/api/models/catalog') {
|
|
52
|
+
sendJson(res, 200, { models: await listModelCatalog({ context, refreshCloud: url.searchParams.get('refresh') === 'true' }) })
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
if (req.method === 'POST' && url.pathname === '/api/models/test-connection') {
|
|
51
57
|
const body = await readJsonBody(req)
|
|
52
58
|
const model = body?.model
|
|
@@ -6,6 +6,7 @@ import { createAgent, getSessionEventBus, agentEvents, persistSessionState, abor
|
|
|
6
6
|
import { agentProfileSnapshot, getAgentProfile } from '../agent-profiles.mjs'
|
|
7
7
|
import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
|
|
8
8
|
import { logger } from '../utils/logger.mjs'
|
|
9
|
+
import { resolveModelBinding } from '../model-catalog.mjs'
|
|
9
10
|
import {
|
|
10
11
|
dayMs,
|
|
11
12
|
formatLocalDateTime,
|
|
@@ -133,10 +134,14 @@ async function getApiKey(provider) {
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
async function parseScheduledTaskInstructionWithAi(instruction,
|
|
137
|
+
async function parseScheduledTaskInstructionWithAi(instruction, modelInput, thinkingLevel = 'off', context = {}) {
|
|
137
138
|
const text = String(instruction || '').trim()
|
|
138
139
|
if (!text) return { needMoreInfo: true, question: '请输入要创建的定时任务。' }
|
|
139
|
-
if (!
|
|
140
|
+
if (!modelInput) return { needMoreInfo: true, question: '请先选择用于解析任务的大模型。' }
|
|
141
|
+
const { model } = await resolveModelBinding(
|
|
142
|
+
modelInput?.modelRef || modelInput?.model ? modelInput : { model: modelInput },
|
|
143
|
+
{ context, legacySnapshot: modelInput?.model || modelInput },
|
|
144
|
+
)
|
|
140
145
|
|
|
141
146
|
const now = new Date()
|
|
142
147
|
const systemPrompt = `你是定时任务解析器。把用户的中文自然语言定时任务解析为 JSON。
|
|
@@ -529,6 +534,18 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
529
534
|
|
|
530
535
|
try {
|
|
531
536
|
const executionProject = await resolveExecutionProject(task)
|
|
537
|
+
const binding = task.modelRef || task.model
|
|
538
|
+
? await resolveModelBinding(
|
|
539
|
+
task.modelRef ? { modelRef: task.modelRef } : { model: task.model },
|
|
540
|
+
{
|
|
541
|
+
context: { source: 'scheduled', allowCloud: true },
|
|
542
|
+
currentModel: task.model,
|
|
543
|
+
allowCurrentHidden: true,
|
|
544
|
+
forExecution: true,
|
|
545
|
+
legacySnapshot: task.model,
|
|
546
|
+
},
|
|
547
|
+
)
|
|
548
|
+
: null
|
|
532
549
|
if (task.agentId) {
|
|
533
550
|
executionAgent = await getAgentProfile(task.agentId, { projectId: executionProject?.id || null, workspaceRoot: executionProject?.path })
|
|
534
551
|
if (!executionAgent) agentWarning = `Configured agent not found: ${task.agentId}`
|
|
@@ -541,7 +558,9 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
541
558
|
scope: executionProject ? 'project' : 'global',
|
|
542
559
|
projectId: executionProject?.id || null,
|
|
543
560
|
yoloMode,
|
|
544
|
-
model: task.model,
|
|
561
|
+
model: binding?.model || task.model,
|
|
562
|
+
modelRef: binding?.modelRef || task.modelRef || null,
|
|
563
|
+
modelAccessContext: { source: 'scheduled', allowCloud: true },
|
|
545
564
|
thinkingLevel: task.thinkingLevel,
|
|
546
565
|
title: `[定时任务] ${task.title}`,
|
|
547
566
|
agentProfile: executionAgent,
|
|
@@ -577,7 +596,7 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
|
|
|
577
596
|
onStarted?.({ taskId: task.id, runId, sessionId })
|
|
578
597
|
|
|
579
598
|
const eventBus = getSessionEventBus(sessionId)
|
|
580
|
-
const runtimeLimitMs = Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs ||
|
|
599
|
+
const runtimeLimitMs = Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs || 60 * 60 * 1000), 60 * 60 * 1000))
|
|
581
600
|
let timeout = null
|
|
582
601
|
let handler = null
|
|
583
602
|
let timedOut = false
|
|
@@ -770,12 +789,12 @@ export function stopScheduledTaskRunner() {
|
|
|
770
789
|
runningTaskRunIds.clear()
|
|
771
790
|
}
|
|
772
791
|
|
|
773
|
-
export async function handleScheduledTasksApi(req, res, url) {
|
|
792
|
+
export async function handleScheduledTasksApi(req, res, url, context = {}) {
|
|
774
793
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
775
794
|
|
|
776
795
|
if (req.method === 'POST' && url.pathname === '/api/scheduled-tasks/parse') {
|
|
777
796
|
const body = await readJsonBody(req)
|
|
778
|
-
sendJson(res, 200, await parseScheduledTaskInstructionWithAi(body?.instruction, body?.model, body?.thinkingLevel))
|
|
797
|
+
sendJson(res, 200, await parseScheduledTaskInstructionWithAi(body?.instruction, body?.modelRef ? body : body?.model, body?.thinkingLevel, context))
|
|
779
798
|
return
|
|
780
799
|
}
|
|
781
800
|
|
|
@@ -796,12 +815,14 @@ export async function handleScheduledTasksApi(req, res, url) {
|
|
|
796
815
|
const now = new Date().toISOString()
|
|
797
816
|
const normalized = normalizeTaskInput(parsed)
|
|
798
817
|
const enabled = parsed.enabled !== false && parsed.status !== 'paused'
|
|
818
|
+
const binding = await resolveModelBinding(body, { context, legacySnapshot: body?.model })
|
|
799
819
|
const task = {
|
|
800
820
|
id: createId(),
|
|
801
821
|
...normalized,
|
|
802
822
|
scheduleRule: normalized.scheduleRule || scheduleRuleFor(normalized),
|
|
803
823
|
executionMode: normalized.executionMode || 'serial',
|
|
804
|
-
model:
|
|
824
|
+
model: binding.model,
|
|
825
|
+
modelRef: binding.modelRef,
|
|
805
826
|
thinkingLevel: body?.thinkingLevel || (body?.model?.reasoning ? 'medium' : 'off'),
|
|
806
827
|
projectId: body?.projectId || null,
|
|
807
828
|
projectName: body?.projectName || null,
|
|
@@ -830,6 +851,14 @@ export async function handleScheduledTasksApi(req, res, url) {
|
|
|
830
851
|
const parsed = body?.task
|
|
831
852
|
if (!parsed) throw requestError('Missing task')
|
|
832
853
|
const normalized = normalizeTaskInput(parsed, existing)
|
|
854
|
+
const binding = body?.modelRef || body?.model
|
|
855
|
+
? await resolveModelBinding(body, {
|
|
856
|
+
context,
|
|
857
|
+
currentModel: existing.model,
|
|
858
|
+
allowCurrentHidden: true,
|
|
859
|
+
legacySnapshot: body?.model,
|
|
860
|
+
})
|
|
861
|
+
: null
|
|
833
862
|
const now = new Date().toISOString()
|
|
834
863
|
const hasProject = Object.prototype.hasOwnProperty.call(body, 'projectId')
|
|
835
864
|
const task = await updateTask(taskId, (current) => ({
|
|
@@ -837,7 +866,8 @@ export async function handleScheduledTasksApi(req, res, url) {
|
|
|
837
866
|
...normalized,
|
|
838
867
|
scheduleRule: normalized.scheduleRule || scheduleRuleFor(normalized),
|
|
839
868
|
executionMode: normalized.executionMode || 'serial',
|
|
840
|
-
model:
|
|
869
|
+
model: binding?.model ?? current.model,
|
|
870
|
+
modelRef: binding?.modelRef ?? current.modelRef,
|
|
841
871
|
thinkingLevel: body?.thinkingLevel ?? current.thinkingLevel,
|
|
842
872
|
projectId: hasProject ? (body.projectId || null) : current.projectId,
|
|
843
873
|
projectName: hasProject ? (body.projectName || null) : current.projectName,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
|
-
import { readSessionValue
|
|
2
|
+
import { readSessionValue } from '../storage.mjs'
|
|
3
|
+
import { listModelCatalog, resolveModelBinding } from '../model-catalog.mjs'
|
|
3
4
|
import { sendSessionAsset } from './session-assets.mjs'
|
|
4
5
|
import { abortRun, restoreAgent, runPrompt, getSessionState, getSessionEventBus, updateSessionModel, updateSessionThinkingLevel } from '../agent-manager.mjs'
|
|
5
6
|
import {
|
|
6
7
|
assertShareActive,
|
|
7
8
|
issueConversationShareToken,
|
|
9
|
+
onConversationShareInvalidated,
|
|
8
10
|
parseCookies,
|
|
9
11
|
readConversationShare,
|
|
10
12
|
rollbackSharedSessionMessages,
|
|
@@ -49,6 +51,7 @@ function publicSharePayload(record) {
|
|
|
49
51
|
scope: record.scope,
|
|
50
52
|
projectId: record.projectId,
|
|
51
53
|
hasPassword: Boolean(record.passwordHash),
|
|
54
|
+
allowCloudUsage: record.allowCloudUsage === true,
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -101,13 +104,6 @@ function sanitizeModel(model) {
|
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
function sanitizeIncomingModel(model) {
|
|
105
|
-
if (!model || typeof model !== 'object') return null
|
|
106
|
-
const sanitized = sanitizeModel(model)
|
|
107
|
-
if (!sanitized.id || !sanitized.provider) return null
|
|
108
|
-
return sanitized
|
|
109
|
-
}
|
|
110
|
-
|
|
111
107
|
function sanitizeEvent(event) {
|
|
112
108
|
if (!event || typeof event !== 'object') return event
|
|
113
109
|
const next = { ...event }
|
|
@@ -147,6 +143,20 @@ async function handleSharedEvents(req, res, record) {
|
|
|
147
143
|
|
|
148
144
|
writeSseEvent(res, 'state', await sharedSessionPayload(record))
|
|
149
145
|
|
|
146
|
+
let cleanedUp = false
|
|
147
|
+
let expirationTimeout
|
|
148
|
+
let removeShareInvalidationListener = () => undefined
|
|
149
|
+
|
|
150
|
+
const cleanup = () => {
|
|
151
|
+
if (cleanedUp) return
|
|
152
|
+
cleanedUp = true
|
|
153
|
+
clearInterval(keepAlive)
|
|
154
|
+
if (expirationTimeout) clearTimeout(expirationTimeout)
|
|
155
|
+
removeShareInvalidationListener()
|
|
156
|
+
eventBus.removeListener('agent_event', onAgentEvent)
|
|
157
|
+
if (!res.writableEnded) res.end()
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
const keepAlive = setInterval(() => {
|
|
151
161
|
try {
|
|
152
162
|
res.write(': ping\n\n')
|
|
@@ -164,11 +174,19 @@ async function handleSharedEvents(req, res, record) {
|
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
removeShareInvalidationListener = onConversationShareInvalidated(({ shareId }) => {
|
|
178
|
+
if (shareId === record.id) cleanup()
|
|
179
|
+
})
|
|
180
|
+
const scheduleExpiration = () => {
|
|
181
|
+
if (!record.expiresAt || cleanedUp) return
|
|
182
|
+
const delay = Date.parse(record.expiresAt) - Date.now()
|
|
183
|
+
if (delay <= 0) {
|
|
184
|
+
cleanup()
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
expirationTimeout = setTimeout(scheduleExpiration, Math.min(delay, 2_147_483_647))
|
|
171
188
|
}
|
|
189
|
+
scheduleExpiration()
|
|
172
190
|
|
|
173
191
|
eventBus.on('agent_event', onAgentEvent)
|
|
174
192
|
req.on('close', cleanup)
|
|
@@ -269,42 +287,7 @@ async function sharedSessionPayload(record) {
|
|
|
269
287
|
return sanitizeSession(session, record)
|
|
270
288
|
}
|
|
271
289
|
|
|
272
|
-
function
|
|
273
|
-
if (!provider || typeof provider !== 'object') return null
|
|
274
|
-
const models = Array.isArray(provider.models)
|
|
275
|
-
? provider.models.map(sanitizeModel).filter((model) => model?.id && model?.provider && model?.api)
|
|
276
|
-
: []
|
|
277
|
-
if (!models.length) return null
|
|
278
|
-
return {
|
|
279
|
-
id: provider.id || provider.name || models[0].provider,
|
|
280
|
-
name: provider.name || models[0].provider,
|
|
281
|
-
type: provider.type || models[0].api,
|
|
282
|
-
baseUrl: provider.baseUrl,
|
|
283
|
-
models,
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async function listSharedModelProviders() {
|
|
288
|
-
const providers = await readStore('custom-providers')
|
|
289
|
-
return Object.values(providers || {}).map(sanitizeProvider).filter(Boolean)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async function readConfiguredModel(model) {
|
|
293
|
-
if (!model || typeof model !== 'object') return null
|
|
294
|
-
const providers = await readStore('custom-providers')
|
|
295
|
-
for (const provider of Object.values(providers || {})) {
|
|
296
|
-
if (!provider || typeof provider !== 'object') continue
|
|
297
|
-
const matched = Array.isArray(provider.models)
|
|
298
|
-
? provider.models.find((candidate) => {
|
|
299
|
-
return candidate?.id === model.id && candidate?.provider === model.provider && candidate?.api === model.api
|
|
300
|
-
})
|
|
301
|
-
: undefined
|
|
302
|
-
if (matched) return matched
|
|
303
|
-
}
|
|
304
|
-
return null
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
export async function handleSharedConversationApi(req, res, url) {
|
|
290
|
+
export async function handleSharedConversationApi(req, res, url, context = {}) {
|
|
308
291
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
309
292
|
const shareId = decodeSegment(parts[2])
|
|
310
293
|
const action = parts[3]
|
|
@@ -360,7 +343,21 @@ export async function handleSharedConversationApi(req, res, url) {
|
|
|
360
343
|
|
|
361
344
|
if (req.method === 'GET' && action === 'models') {
|
|
362
345
|
assertOperate(record)
|
|
363
|
-
|
|
346
|
+
const session = await sharedSessionPayload(record)
|
|
347
|
+
const models = await listModelCatalog({
|
|
348
|
+
context: { ...context, allowCloud: record.allowCloudUsage === true },
|
|
349
|
+
currentModel: session.model,
|
|
350
|
+
})
|
|
351
|
+
const providers = []
|
|
352
|
+
for (const model of models) {
|
|
353
|
+
let provider = providers.find((item) => item.id === model.provider)
|
|
354
|
+
if (!provider) {
|
|
355
|
+
provider = { id: model.provider, name: model.provider, type: model.api, baseUrl: model.baseUrl, models: [] }
|
|
356
|
+
providers.push(provider)
|
|
357
|
+
}
|
|
358
|
+
provider.models.push(sanitizeModel(model))
|
|
359
|
+
}
|
|
360
|
+
sendJson(res, 200, { providers })
|
|
364
361
|
return
|
|
365
362
|
}
|
|
366
363
|
|
|
@@ -373,7 +370,14 @@ export async function handleSharedConversationApi(req, res, url) {
|
|
|
373
370
|
assertOperate(record)
|
|
374
371
|
const body = await readJsonBody(req)
|
|
375
372
|
await restoreAgent(record.sessionId)
|
|
376
|
-
const result = await runPrompt(
|
|
373
|
+
const result = await runPrompt(
|
|
374
|
+
record.sessionId,
|
|
375
|
+
messageFromBody(body, record, req),
|
|
376
|
+
[],
|
|
377
|
+
body?.command,
|
|
378
|
+
null,
|
|
379
|
+
{ ...context, allowCloud: record.allowCloudUsage === true, source: 'shared' },
|
|
380
|
+
)
|
|
377
381
|
sendJson(res, 200, result)
|
|
378
382
|
return
|
|
379
383
|
}
|
|
@@ -381,15 +385,15 @@ export async function handleSharedConversationApi(req, res, url) {
|
|
|
381
385
|
if (req.method === 'POST' && action === 'model') {
|
|
382
386
|
assertOperate(record)
|
|
383
387
|
const body = await readJsonBody(req)
|
|
384
|
-
const model = sanitizeIncomingModel(body?.model)
|
|
385
|
-
if (!model) {
|
|
386
|
-
const error = new Error('Missing model in request body')
|
|
387
|
-
error.statusCode = 400
|
|
388
|
-
throw error
|
|
389
|
-
}
|
|
390
388
|
await restoreAgent(record.sessionId)
|
|
391
|
-
const
|
|
392
|
-
|
|
389
|
+
const currentModel = getSessionState(record.sessionId)?.model
|
|
390
|
+
const binding = await resolveModelBinding(body, {
|
|
391
|
+
context: { ...context, allowCloud: record.allowCloudUsage === true, source: 'shared' },
|
|
392
|
+
currentModel,
|
|
393
|
+
allowCurrentHidden: true,
|
|
394
|
+
legacySnapshot: body?.model,
|
|
395
|
+
})
|
|
396
|
+
sendJson(res, 200, updateSessionModel(record.sessionId, sanitizeModel(binding.model), binding.modelRef))
|
|
393
397
|
return
|
|
394
398
|
}
|
|
395
399
|
|
package/server/routes/shares.mjs
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { readSessionValue } from '../storage.mjs'
|
|
3
|
-
import { getLanUrls } from '../utils/network.mjs'
|
|
3
|
+
import { getLanUrls, isTailscaleAddress } from '../utils/network.mjs'
|
|
4
4
|
import {
|
|
5
5
|
createConversationShare,
|
|
6
|
+
deleteConversationShare,
|
|
6
7
|
listConversationShares,
|
|
8
|
+
restoreConversationShare,
|
|
7
9
|
revokeConversationShare,
|
|
10
|
+
updateConversationShare,
|
|
11
|
+
updateConversationShareExpiration,
|
|
8
12
|
} from '../share-store.mjs'
|
|
9
13
|
|
|
10
14
|
function localBaseUrl(req, port) {
|
|
@@ -46,6 +50,13 @@ export async function handleSharesApi(req, res, url, context = {}) {
|
|
|
46
50
|
const passwordProvided = typeof body?.password === 'string'
|
|
47
51
|
const password = passwordProvided ? body.password.trim() : undefined
|
|
48
52
|
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
53
|
+
const allowCloudUsage = permission === 'operate' && body?.allowCloudUsage === true
|
|
54
|
+
if (allowCloudUsage && context.isLocalRequest === false
|
|
55
|
+
&& !(context.remoteAuthorized === true && isTailscaleAddress(context.remoteAddress))) {
|
|
56
|
+
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
|
|
57
|
+
error.statusCode = 403
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
49
60
|
|
|
50
61
|
const session = sessionId ? await readSessionValue(sessionId) : null
|
|
51
62
|
if (!session) {
|
|
@@ -59,6 +70,7 @@ export async function handleSharesApi(req, res, url, context = {}) {
|
|
|
59
70
|
permission,
|
|
60
71
|
password: passwordProvided ? password : undefined,
|
|
61
72
|
expiresAt,
|
|
73
|
+
allowCloudUsage,
|
|
62
74
|
titleSnapshot: session.title,
|
|
63
75
|
scope: session.scope,
|
|
64
76
|
projectId: session.projectId,
|
|
@@ -77,6 +89,69 @@ export async function handleSharesApi(req, res, url, context = {}) {
|
|
|
77
89
|
return
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
if (parts.length === 4 && parts[0] === 'api' && parts[1] === 'shares') {
|
|
93
|
+
const shareId = decodeSegment(parts[2])
|
|
94
|
+
const action = parts[3]
|
|
95
|
+
|
|
96
|
+
if (req.method === 'POST' && action === 'disable') {
|
|
97
|
+
const share = await revokeConversationShare(shareId)
|
|
98
|
+
sendJson(res, 200, { ok: true, share })
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (req.method === 'POST' && action === 'restore') {
|
|
103
|
+
const body = await readJsonBody(req)
|
|
104
|
+
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
105
|
+
const currentShares = await listConversationShares()
|
|
106
|
+
const current = currentShares.find((share) => share.id === shareId)
|
|
107
|
+
if (!current) {
|
|
108
|
+
const error = new Error('Share not found')
|
|
109
|
+
error.statusCode = 404
|
|
110
|
+
throw error
|
|
111
|
+
}
|
|
112
|
+
const session = current.sessionId ? await readSessionValue(current.sessionId) : null
|
|
113
|
+
if (!session) {
|
|
114
|
+
const error = new Error('Session not found')
|
|
115
|
+
error.statusCode = 404
|
|
116
|
+
throw error
|
|
117
|
+
}
|
|
118
|
+
const share = await restoreConversationShare(shareId, expiresAt)
|
|
119
|
+
sendJson(res, 200, { ok: true, share })
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (req.method === 'POST' && action === 'expiration') {
|
|
124
|
+
const body = await readJsonBody(req)
|
|
125
|
+
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
126
|
+
const share = await updateConversationShareExpiration(shareId, expiresAt)
|
|
127
|
+
sendJson(res, 200, { ok: true, share })
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (req.method === 'POST' && action === 'update') {
|
|
132
|
+
const body = await readJsonBody(req)
|
|
133
|
+
const permission = body?.permission
|
|
134
|
+
const password = typeof body?.password === 'string' ? body.password : undefined
|
|
135
|
+
const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
|
|
136
|
+
const allowCloudUsage = body?.allowCloudUsage
|
|
137
|
+
if (allowCloudUsage === true && context.isLocalRequest === false
|
|
138
|
+
&& !(context.remoteAuthorized === true && isTailscaleAddress(context.remoteAddress))) {
|
|
139
|
+
const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
|
|
140
|
+
error.statusCode = 403
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
const share = await updateConversationShare(shareId, { permission, password, expiresAt, allowCloudUsage })
|
|
144
|
+
sendJson(res, 200, { ok: true, share })
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (req.method === 'DELETE' && action === 'permanent') {
|
|
149
|
+
const share = await deleteConversationShare(shareId)
|
|
150
|
+
sendJson(res, 200, { ok: true, share })
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
80
155
|
if (req.method === 'DELETE' && parts.length === 3 && parts[0] === 'api' && parts[1] === 'shares') {
|
|
81
156
|
const shareId = decodeSegment(parts[2])
|
|
82
157
|
const share = await revokeConversationShare(shareId)
|