@shawnstack/quickforge 1.7.4 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
- package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
- package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
- package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
- package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
- package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
- package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
- package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
- package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
- package/dist/assets/WorkspaceInspector-BnfDLf9a.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/{dist-DLKC_odd.js → dist-yDHbi5ux.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-sdsK6gaB.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-2D18YbSI.js +1 -0
- package/dist/assets/index-1ei6nhs-.css +3 -0
- package/dist/assets/index-BivLuZvX.js +65 -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-BEV4_TXC.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/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.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-DvXErxp2.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-3UaA-C9W.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-BTJ7mzk_.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/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +11 -10
- package/dist/sw.js +17 -0
- package/package.json +4 -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/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
- package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
- package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
- package/runtime-assets/agent/linux-x64/qf-agent +0 -0
- package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
- package/server/access-policy.mjs +11 -0
- package/server/acp/server.mjs +36 -26
- package/server/agent-manager.mjs +474 -71
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +28 -9
- package/server/ai-http-logger.mjs +98 -12
- package/server/auto-compaction.mjs +35 -8
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +99 -0
- package/server/cloud/config.mjs +54 -0
- package/server/cloud/credential-store.mjs +200 -0
- package/server/cloud/identity.mjs +312 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/qf-agent-process.mjs +571 -0
- package/server/cloud/runtime.mjs +135 -0
- package/server/cloud/service-config.mjs +81 -0
- package/server/index.mjs +124 -48
- package/server/mcp/registry.mjs +8 -7
- package/server/message-converters.mjs +40 -19
- package/server/model-catalog.mjs +269 -0
- package/server/opencode-acp-agent.mjs +1151 -0
- package/server/plugins/registry.mjs +29 -5
- package/server/public-api.mjs +11 -5
- package/server/routes/agent-profiles.mjs +46 -9
- package/server/routes/agent.mjs +60 -11
- package/server/routes/backup.mjs +8 -1
- package/server/routes/cloud.mjs +246 -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 +75 -1
- package/server/routes/storage.mjs +9 -1
- package/server/routes/system.mjs +7 -4
- 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/process-tree.mjs +78 -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/SettingsWorkspacePage-B53_eDf2.js +0 -1628
- package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
- package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
- 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/{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
|
@@ -16,6 +16,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
16
16
|
const bundledPluginDir = path.resolve(__dirname, '..', '..', 'plugins')
|
|
17
17
|
const globalPluginDir = path.join(dataDir, 'plugins')
|
|
18
18
|
const legacyGlobalPluginDir = path.join(os.homedir(), '.agents', 'plugins')
|
|
19
|
+
const PLUGIN_STORE_SCHEMA_VERSION = 2
|
|
20
|
+
const LEGACY_BUNDLED_PLUGIN_NAMES = {
|
|
21
|
+
'openai-documents': 'documents',
|
|
22
|
+
'openai-presentations': 'presentations',
|
|
23
|
+
'openai-spreadsheets': 'spreadsheets',
|
|
24
|
+
}
|
|
19
25
|
export const DEFAULT_PLUGIN_TOOL_TIMEOUT_MS = 2 * 60 * 1000
|
|
20
26
|
let cachedCatalog = null
|
|
21
27
|
let cachedCatalogKey = null
|
|
@@ -108,13 +114,31 @@ async function listPluginDirs(root) {
|
|
|
108
114
|
function normalizePluginStore(store) {
|
|
109
115
|
const source = isPlainObject(store) ? store : {}
|
|
110
116
|
return {
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
schemaVersion: Number.isInteger(source.schemaVersion) ? source.schemaVersion : 1,
|
|
118
|
+
enabled: isPlainObject(source.enabled) ? { ...source.enabled } : {},
|
|
119
|
+
config: isPlainObject(source.config) ? { ...source.config } : {},
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function migratePluginStore(store) {
|
|
124
|
+
const next = normalizePluginStore(store)
|
|
125
|
+
if (next.schemaVersion >= PLUGIN_STORE_SCHEMA_VERSION) return { store: next, changed: false }
|
|
126
|
+
|
|
127
|
+
for (const [legacyName, name] of Object.entries(LEGACY_BUNDLED_PLUGIN_NAMES)) {
|
|
128
|
+
if (!(name in next.enabled) && legacyName in next.enabled) next.enabled[name] = next.enabled[legacyName]
|
|
129
|
+
if (!(name in next.config) && legacyName in next.config) next.config[name] = next.config[legacyName]
|
|
130
|
+
delete next.enabled[legacyName]
|
|
131
|
+
delete next.config[legacyName]
|
|
113
132
|
}
|
|
133
|
+
next.schemaVersion = PLUGIN_STORE_SCHEMA_VERSION
|
|
134
|
+
return { store: next, changed: true }
|
|
114
135
|
}
|
|
115
136
|
|
|
116
137
|
async function readPluginStore() {
|
|
117
|
-
|
|
138
|
+
const current = await readStore('plugins')
|
|
139
|
+
const migration = migratePluginStore(current)
|
|
140
|
+
if (!migration.changed) return migration.store
|
|
141
|
+
return atomicUpdate('plugins', (store) => migratePluginStore(store).store)
|
|
118
142
|
}
|
|
119
143
|
|
|
120
144
|
function contributionSummary(contribution) {
|
|
@@ -292,7 +316,7 @@ export async function getPluginStatus(projectContext = null) {
|
|
|
292
316
|
|
|
293
317
|
export async function setPluginEnabled(name, enabled) {
|
|
294
318
|
await atomicUpdate('plugins', (store) => {
|
|
295
|
-
const next =
|
|
319
|
+
const next = migratePluginStore(store).store
|
|
296
320
|
next.enabled[name] = enabled === true
|
|
297
321
|
return next
|
|
298
322
|
})
|
|
@@ -301,7 +325,7 @@ export async function setPluginEnabled(name, enabled) {
|
|
|
301
325
|
|
|
302
326
|
export async function setPluginConfig(name, config) {
|
|
303
327
|
await atomicUpdate('plugins', (store) => {
|
|
304
|
-
const next =
|
|
328
|
+
const next = migratePluginStore(store).store
|
|
305
329
|
next.config[name] = isPlainObject(config) ? config : {}
|
|
306
330
|
return next
|
|
307
331
|
})
|
package/server/public-api.mjs
CHANGED
|
@@ -68,7 +68,7 @@ function createVersionMismatchError(health, options = {}) {
|
|
|
68
68
|
return error
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function buildEnv(options = {}) {
|
|
71
|
+
export function buildEnv(options = {}) {
|
|
72
72
|
const host = normalizeHost(options.host)
|
|
73
73
|
const port = getPort(options.port)
|
|
74
74
|
const shareLan = options.shareLan === true
|
|
@@ -84,6 +84,10 @@ function buildEnv(options = {}) {
|
|
|
84
84
|
if (options.workspaceDir) env.QUICKFORGE_WORKSPACE_DIR = path.resolve(options.workspaceDir)
|
|
85
85
|
if (options.vitePort) env.QUICKFORGE_VITE_PORT = String(options.vitePort)
|
|
86
86
|
if (options.terminal === false) env.QUICKFORGE_TERMINAL = '0'
|
|
87
|
+
if (options.qfAgentPath) env.QUICKFORGE_QF_AGENT_PATH = path.resolve(options.qfAgentPath)
|
|
88
|
+
if (options.qfAgentIdentityDir) env.QUICKFORGE_QF_AGENT_IDENTITY_DIR = path.resolve(options.qfAgentIdentityDir)
|
|
89
|
+
if (options.qfAgentEnabled !== undefined) env.QUICKFORGE_QF_AGENT_ENABLED = options.qfAgentEnabled === false ? '0' : '1'
|
|
90
|
+
if (options.runtimeKind) env.QUICKFORGE_RUNTIME_KIND = String(options.runtimeKind)
|
|
87
91
|
if (options.allowRemote || shareLan) env.QUICKFORGE_ALLOW_REMOTE = '1'
|
|
88
92
|
delete env.ELECTRON_RUN_AS_NODE
|
|
89
93
|
delete env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
|
|
@@ -172,7 +176,7 @@ export async function startQuickForge(options = {}) {
|
|
|
172
176
|
|
|
173
177
|
if (options.inline === true) {
|
|
174
178
|
prepareQuickForgeEnv(options)
|
|
175
|
-
await import(serverScriptUrl)
|
|
179
|
+
const serverModule = await import(serverScriptUrl)
|
|
176
180
|
const health = await waitForQuickForge(options)
|
|
177
181
|
if (!health) throw new Error('QuickForge failed to start: health check timed out')
|
|
178
182
|
return {
|
|
@@ -184,7 +188,8 @@ export async function startQuickForge(options = {}) {
|
|
|
184
188
|
reused: false,
|
|
185
189
|
inline: true,
|
|
186
190
|
async stop() {
|
|
187
|
-
|
|
191
|
+
await serverModule.stopQuickForgeServer()
|
|
192
|
+
return true
|
|
188
193
|
},
|
|
189
194
|
}
|
|
190
195
|
}
|
|
@@ -242,8 +247,9 @@ export async function startQuickForge(options = {}) {
|
|
|
242
247
|
}
|
|
243
248
|
|
|
244
249
|
export async function stopQuickForge(instance) {
|
|
245
|
-
if (!instance
|
|
246
|
-
if (instance.
|
|
250
|
+
if (!instance || instance.reused) return false
|
|
251
|
+
if (typeof instance.stop === 'function') return instance.stop()
|
|
252
|
+
if (!instance.child || instance.child.killed) return false
|
|
247
253
|
instance.child.kill('SIGTERM')
|
|
248
254
|
return true
|
|
249
255
|
}
|
|
@@ -3,6 +3,36 @@ import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
|
|
|
3
3
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
4
4
|
import { readStore } from '../storage.mjs'
|
|
5
5
|
import { logger } from '../utils/logger.mjs'
|
|
6
|
+
import { resolveModelBinding } from '../model-catalog.mjs'
|
|
7
|
+
|
|
8
|
+
function profileModelReference(model) {
|
|
9
|
+
if (!model || model.mode !== 'fixed') return null
|
|
10
|
+
if (model.source === 'cloud') return { version: 1, source: 'cloud', catalogId: model.catalogId || model.modelId }
|
|
11
|
+
if (model.source === 'custom') return { version: 1, source: 'custom', providerId: model.providerId, modelId: model.modelId }
|
|
12
|
+
return {
|
|
13
|
+
version: 1,
|
|
14
|
+
source: 'legacy-custom',
|
|
15
|
+
provider: model.provider,
|
|
16
|
+
modelId: model.modelId,
|
|
17
|
+
...(model.api ? { api: model.api } : {}),
|
|
18
|
+
...(model.baseUrl ? { baseUrl: model.baseUrl } : {}),
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function canonicalizeProfileModel(body, current, context) {
|
|
23
|
+
if (!body || !Object.prototype.hasOwnProperty.call(body, 'model')) return body
|
|
24
|
+
const model = body.model
|
|
25
|
+
if (!model || model.mode !== 'fixed') return body
|
|
26
|
+
if (current?.model && JSON.stringify(current.model) === JSON.stringify(model)) return body
|
|
27
|
+
const binding = await resolveModelBinding({ modelRef: profileModelReference(model) }, { context })
|
|
28
|
+
const ref = binding.modelRef
|
|
29
|
+
return {
|
|
30
|
+
...body,
|
|
31
|
+
model: ref.source === 'cloud'
|
|
32
|
+
? { mode: 'fixed', source: 'cloud', catalogId: ref.catalogId }
|
|
33
|
+
: { mode: 'fixed', source: 'custom', providerId: ref.providerId, modelId: ref.modelId },
|
|
34
|
+
}
|
|
35
|
+
}
|
|
6
36
|
import {
|
|
7
37
|
agentProfileSnapshot,
|
|
8
38
|
createCustomAgentProfile,
|
|
@@ -10,7 +40,7 @@ import {
|
|
|
10
40
|
getAgentProfile,
|
|
11
41
|
listAgentProfiles,
|
|
12
42
|
listAvailableAgentTools,
|
|
13
|
-
|
|
43
|
+
updateBuiltinAgentOverrides,
|
|
14
44
|
updateCustomAgentProfile,
|
|
15
45
|
} from '../agent-profiles.mjs'
|
|
16
46
|
|
|
@@ -65,10 +95,14 @@ function normalizeGeneratedAgentProfile(value) {
|
|
|
65
95
|
return { name, label, description, systemPrompt }
|
|
66
96
|
}
|
|
67
97
|
|
|
68
|
-
async function generateAgentProfileWithAi(instruction,
|
|
98
|
+
async function generateAgentProfileWithAi(instruction, modelInput, thinkingLevel = 'off', context = {}) {
|
|
69
99
|
const text = String(instruction || '').trim()
|
|
70
100
|
if (!text) throw requestError('Please describe the agent you want to create')
|
|
71
|
-
if (!
|
|
101
|
+
if (!modelInput) throw requestError('Please configure a default model first')
|
|
102
|
+
const { model } = await resolveModelBinding(
|
|
103
|
+
modelInput?.modelRef || modelInput?.model ? modelInput : { model: modelInput },
|
|
104
|
+
{ context, legacySnapshot: modelInput?.model || modelInput },
|
|
105
|
+
)
|
|
72
106
|
|
|
73
107
|
const systemPrompt = `You are a QuickForge Agent Profile generator.
|
|
74
108
|
Generate only the basic definition fields for a custom Agent Profile from the user's request.
|
|
@@ -120,7 +154,7 @@ Rules:
|
|
|
120
154
|
}
|
|
121
155
|
}
|
|
122
156
|
|
|
123
|
-
export async function handleAgentProfilesApi(req, res, url) {
|
|
157
|
+
export async function handleAgentProfilesApi(req, res, url, context = {}) {
|
|
124
158
|
const parts = url.pathname.split('/').filter(Boolean)
|
|
125
159
|
|
|
126
160
|
if (req.method === 'GET' && url.pathname === '/api/agent-profiles') {
|
|
@@ -136,13 +170,13 @@ export async function handleAgentProfilesApi(req, res, url) {
|
|
|
136
170
|
|
|
137
171
|
if (req.method === 'POST' && url.pathname === '/api/agent-profiles/ai-fill') {
|
|
138
172
|
const body = await readJsonBody(req)
|
|
139
|
-
sendJson(res, 200, { agent: await generateAgentProfileWithAi(body?.instruction, body?.model, body?.thinkingLevel) })
|
|
173
|
+
sendJson(res, 200, { agent: await generateAgentProfileWithAi(body?.instruction, body?.modelRef ? body : body?.model, body?.thinkingLevel, context) })
|
|
140
174
|
return
|
|
141
175
|
}
|
|
142
176
|
|
|
143
177
|
if (req.method === 'POST' && url.pathname === '/api/agent-profiles') {
|
|
144
178
|
const body = await readJsonBody(req)
|
|
145
|
-
sendJson(res, 200, { agent: await createCustomAgentProfile(body || {}) })
|
|
179
|
+
sendJson(res, 200, { agent: await createCustomAgentProfile(await canonicalizeProfileModel(body || {}, null, context)) })
|
|
146
180
|
return
|
|
147
181
|
}
|
|
148
182
|
|
|
@@ -160,13 +194,16 @@ export async function handleAgentProfilesApi(req, res, url) {
|
|
|
160
194
|
const current = await getAgentProfile(id)
|
|
161
195
|
const body = await readJsonBody(req)
|
|
162
196
|
if (current?.builtin) {
|
|
197
|
+
const allowedKeys = ['model', 'thinkingLevel']
|
|
163
198
|
const keys = Object.keys(body || {})
|
|
164
|
-
if (keys.length
|
|
165
|
-
|
|
199
|
+
if (keys.length === 0 || keys.some((key) => !allowedKeys.includes(key))) {
|
|
200
|
+
throw requestError('Built-in agents only allow model and thinkingLevel updates', 403)
|
|
201
|
+
}
|
|
202
|
+
sendJson(res, 200, { agent: agentProfileSnapshot(await updateBuiltinAgentOverrides(id, await canonicalizeProfileModel(body, current, context))) })
|
|
166
203
|
return
|
|
167
204
|
}
|
|
168
205
|
if (current?.readonly) throw requestError('Read-only agents cannot be modified from the API', 403)
|
|
169
|
-
sendJson(res, 200, { agent: await updateCustomAgentProfile(id, body || {}) })
|
|
206
|
+
sendJson(res, 200, { agent: await updateCustomAgentProfile(id, await canonicalizeProfileModel(body || {}, current, context)) })
|
|
170
207
|
return
|
|
171
208
|
}
|
|
172
209
|
|
package/server/routes/agent.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
|
|
2
2
|
import { logger } from '../utils/logger.mjs'
|
|
3
|
+
import { resolveModelBinding } from '../model-catalog.mjs'
|
|
3
4
|
import {
|
|
4
5
|
createAgent,
|
|
6
|
+
validateAgentHarness,
|
|
5
7
|
runPrompt,
|
|
6
8
|
abortRun,
|
|
7
9
|
steerAgent,
|
|
@@ -21,6 +23,9 @@ import {
|
|
|
21
23
|
updateSessionYoloMode,
|
|
22
24
|
updateSessionModel,
|
|
23
25
|
updateSessionThinkingLevel,
|
|
26
|
+
updateSessionHarnessConfigOption,
|
|
27
|
+
updateSessionHarnessMode,
|
|
28
|
+
forkSession,
|
|
24
29
|
approveToolCall,
|
|
25
30
|
rejectToolCall,
|
|
26
31
|
approveAutoCompact,
|
|
@@ -31,7 +36,7 @@ import {
|
|
|
31
36
|
agentEvents,
|
|
32
37
|
} from '../agent-manager.mjs'
|
|
33
38
|
|
|
34
|
-
export async function handleAgentApi(req, res, url) {
|
|
39
|
+
export async function handleAgentApi(req, res, url, context = {}) {
|
|
35
40
|
const pathname = url.pathname
|
|
36
41
|
const parts = pathname.split('/').filter(Boolean)
|
|
37
42
|
|
|
@@ -85,7 +90,7 @@ export async function handleAgentApi(req, res, url) {
|
|
|
85
90
|
error.statusCode = 400
|
|
86
91
|
throw error
|
|
87
92
|
}
|
|
88
|
-
const result = await runPrompt(sessionId, message, body?.selectedCapabilities, body?.command)
|
|
93
|
+
const result = await runPrompt(sessionId, message, body?.selectedCapabilities, body?.command, null, context)
|
|
89
94
|
sendJson(res, 200, result)
|
|
90
95
|
return
|
|
91
96
|
}
|
|
@@ -160,13 +165,23 @@ export async function handleAgentApi(req, res, url) {
|
|
|
160
165
|
// POST /api/agents/:sessionId — create/ensure agent
|
|
161
166
|
if (req.method === 'POST' && parts.length === 3) {
|
|
162
167
|
const body = await readJsonBody(req)
|
|
163
|
-
const
|
|
168
|
+
const harness = validateAgentHarness(body?.harness)
|
|
169
|
+
let config
|
|
170
|
+
if (harness === 'quickforge' && (body?.modelRef || body?.model)) {
|
|
171
|
+
const binding = await resolveModelBinding(body, { context, legacySnapshot: body?.model })
|
|
172
|
+
config = { ...body, harness, model: binding.model, modelRef: binding.modelRef, modelAccessContext: context, resolvePersistedModel: true }
|
|
173
|
+
} else {
|
|
174
|
+
config = { ...body, harness, modelAccessContext: context, resolvePersistedModel: true }
|
|
175
|
+
}
|
|
176
|
+
const session = await createAgent(sessionId, config)
|
|
164
177
|
sendJson(res, 200, {
|
|
165
178
|
sessionId: session.sessionId,
|
|
166
179
|
status: session.status,
|
|
167
180
|
scope: session.scope,
|
|
168
181
|
title: session.title,
|
|
169
182
|
source: session.source || undefined,
|
|
183
|
+
harness: session.harness,
|
|
184
|
+
harnessSessionId: session.agent.harnessSessionId || session.harnessSessionId || undefined,
|
|
170
185
|
channelId: session.channelId || undefined,
|
|
171
186
|
channelName: session.channelName || undefined,
|
|
172
187
|
accessMode: session.accessMode,
|
|
@@ -209,13 +224,14 @@ export async function handleAgentApi(req, res, url) {
|
|
|
209
224
|
// POST /api/agents/:sessionId/model — update session model
|
|
210
225
|
if (req.method === 'POST' && subPath === 'model') {
|
|
211
226
|
const body = await readJsonBody(req)
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
227
|
+
const currentModel = getSessionState(sessionId)?.model
|
|
228
|
+
const binding = await resolveModelBinding(body, {
|
|
229
|
+
context,
|
|
230
|
+
currentModel,
|
|
231
|
+
allowCurrentHidden: true,
|
|
232
|
+
legacySnapshot: body?.model,
|
|
233
|
+
})
|
|
234
|
+
const result = updateSessionModel(sessionId, binding.model, binding.modelRef)
|
|
219
235
|
sendJson(res, 200, result)
|
|
220
236
|
return
|
|
221
237
|
}
|
|
@@ -234,6 +250,39 @@ export async function handleAgentApi(req, res, url) {
|
|
|
234
250
|
return
|
|
235
251
|
}
|
|
236
252
|
|
|
253
|
+
// POST /api/agents/:sessionId/harness/config-option — update an advertised Harness config option
|
|
254
|
+
if (req.method === 'POST' && subPath === 'harness/config-option') {
|
|
255
|
+
const body = await readJsonBody(req)
|
|
256
|
+
if (typeof body?.configId !== 'string' || !body.configId || body.value === undefined) {
|
|
257
|
+
const error = new Error('Missing configId or value in request body')
|
|
258
|
+
error.statusCode = 400
|
|
259
|
+
throw error
|
|
260
|
+
}
|
|
261
|
+
const result = await updateSessionHarnessConfigOption(sessionId, body.configId, body.value)
|
|
262
|
+
sendJson(res, 200, result)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// POST /api/agents/:sessionId/harness/mode — update an advertised Harness mode
|
|
267
|
+
if (req.method === 'POST' && subPath === 'harness/mode') {
|
|
268
|
+
const body = await readJsonBody(req)
|
|
269
|
+
if (typeof body?.modeId !== 'string' || !body.modeId) {
|
|
270
|
+
const error = new Error('Missing modeId in request body')
|
|
271
|
+
error.statusCode = 400
|
|
272
|
+
throw error
|
|
273
|
+
}
|
|
274
|
+
const result = await updateSessionHarnessMode(sessionId, body.modeId)
|
|
275
|
+
sendJson(res, 200, result)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// POST /api/agents/:sessionId/fork — fork the entire current OpenCode session
|
|
280
|
+
if (req.method === 'POST' && subPath === 'fork') {
|
|
281
|
+
const result = await forkSession(sessionId)
|
|
282
|
+
sendJson(res, 200, result)
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
237
286
|
// POST /api/agents/:sessionId/rollback — roll back from a message index on the authoritative server state
|
|
238
287
|
if (req.method === 'POST' && subPath === 'rollback') {
|
|
239
288
|
const body = await readJsonBody(req)
|
|
@@ -244,7 +293,7 @@ export async function handleAgentApi(req, res, url) {
|
|
|
244
293
|
|
|
245
294
|
// POST /api/agents/:sessionId/continue — continue generation from last message (retry)
|
|
246
295
|
if (req.method === 'POST' && subPath === 'continue') {
|
|
247
|
-
const result = await continueSession(sessionId)
|
|
296
|
+
const result = await continueSession(sessionId, context)
|
|
248
297
|
sendJson(res, 200, result)
|
|
249
298
|
return
|
|
250
299
|
}
|
package/server/routes/backup.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
2
|
import { promises as fs } from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
4
5
|
import { sendJson, readJsonBody } from '../utils/response.mjs'
|
|
5
6
|
import {
|
|
6
7
|
ensureStorage,
|
|
@@ -580,7 +581,13 @@ async function restoreValidatedBackup(backup, mode = 'replace') {
|
|
|
580
581
|
return summary
|
|
581
582
|
}
|
|
582
583
|
|
|
583
|
-
export async function handleBackupApi(req, res, url) {
|
|
584
|
+
export async function handleBackupApi(req, res, url, context = { isLocalRequest: true }) {
|
|
585
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
586
|
+
const error = new Error('Backup import and export require a local or authenticated remote client.')
|
|
587
|
+
error.statusCode = 403
|
|
588
|
+
error.errorCode = 'backup_auth_required'
|
|
589
|
+
throw error
|
|
590
|
+
}
|
|
584
591
|
if (req.method === 'GET' && url.pathname === '/api/backup/export') {
|
|
585
592
|
await ensureStorage()
|
|
586
593
|
sendJson(res, 200, await buildBackup(url.searchParams.get('scope'), {
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { getQfAgentStatus } from '../cloud/qf-agent-process.mjs'
|
|
2
|
+
import { isAuthenticatedAppClient } from '../access-policy.mjs'
|
|
3
|
+
import { CloudClient, CloudApiError } from '../cloud/client.mjs'
|
|
4
|
+
import { parseCloudBaseUrl } from '../cloud/config.mjs'
|
|
5
|
+
import { createCloudCredentialStore } from '../cloud/credential-store.mjs'
|
|
6
|
+
import { getCloudRuntime, invalidateCloudRuntime } from '../cloud/runtime.mjs'
|
|
7
|
+
import {
|
|
8
|
+
publicCloudServiceConfig,
|
|
9
|
+
readCloudServiceConfig,
|
|
10
|
+
saveCloudServiceConfig,
|
|
11
|
+
} from '../cloud/service-config.mjs'
|
|
12
|
+
import { readJsonBody, sendJson } from '../utils/response.mjs'
|
|
13
|
+
|
|
14
|
+
const CLOUD_CONFIG_BODY_MAX_BYTES = 16 * 1024
|
|
15
|
+
const CLOUD_ACTION_HEADER = 'x-quickforge-action'
|
|
16
|
+
const CLOUD_ACTION_VALUE = 'cloud-action'
|
|
17
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
|
|
18
|
+
const JSON_WRITE_ROUTES = new Set([
|
|
19
|
+
'POST /api/cloud/test-connection',
|
|
20
|
+
'PUT /api/cloud/config',
|
|
21
|
+
'POST /api/cloud/identity/reset',
|
|
22
|
+
'POST /api/cloud/device/start',
|
|
23
|
+
'POST /api/cloud/device/poll',
|
|
24
|
+
'POST /api/cloud/device/cancel',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
function routeError(message, statusCode, code) {
|
|
28
|
+
const error = new Error(message)
|
|
29
|
+
error.statusCode = statusCode
|
|
30
|
+
error.errorCode = code
|
|
31
|
+
error.code = code
|
|
32
|
+
return error
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function mapCloudError(error) {
|
|
36
|
+
if (!(error instanceof CloudApiError)) return error
|
|
37
|
+
const mapped = routeError(error.message, error.status || 503, error.code)
|
|
38
|
+
mapped.retryable = error.retryable
|
|
39
|
+
mapped.details = error.details
|
|
40
|
+
return mapped
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createDefaultRuntime() {
|
|
44
|
+
return getCloudRuntime()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sameCloudUrl(left, right) {
|
|
48
|
+
try {
|
|
49
|
+
return parseCloudBaseUrl(left).href === parseCloudBaseUrl(right).href
|
|
50
|
+
} catch {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requireDangerousConfirmation(body) {
|
|
56
|
+
if (body?.confirm !== 'reset-cloud-identity') {
|
|
57
|
+
throw routeError('Explicit confirmation is required to rebuild the QuickForge Cloud identity.', 400, 'cloud_reset_confirmation_required')
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function requireProtectedCloudWrite(req, pathname) {
|
|
62
|
+
const method = String(req.method || 'GET').toUpperCase()
|
|
63
|
+
if (SAFE_METHODS.has(method)) return
|
|
64
|
+
|
|
65
|
+
if (req.headers[CLOUD_ACTION_HEADER] !== CLOUD_ACTION_VALUE) {
|
|
66
|
+
throw routeError('QuickForge Cloud action header is required.', 403, 'cloud_action_header_required')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!JSON_WRITE_ROUTES.has(`${method} ${pathname}`)) return
|
|
70
|
+
const mediaType = String(req.headers['content-type'] || '').split(';', 1)[0].trim().toLowerCase()
|
|
71
|
+
if (mediaType !== 'application/json') {
|
|
72
|
+
throw routeError('QuickForge Cloud JSON requests require Content-Type: application/json.', 415, 'cloud_json_content_type_required')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createCloudRouteHandler({
|
|
77
|
+
runtimeFactory = createDefaultRuntime,
|
|
78
|
+
readServiceConfig = readCloudServiceConfig,
|
|
79
|
+
saveServiceConfig = saveCloudServiceConfig,
|
|
80
|
+
credentialStoreFactory = createCloudCredentialStore,
|
|
81
|
+
invalidateRuntime = invalidateCloudRuntime,
|
|
82
|
+
cloudClientFactory = (config) => new CloudClient(config),
|
|
83
|
+
qfAgentStatus = getQfAgentStatus,
|
|
84
|
+
onServiceConfigChanged,
|
|
85
|
+
} = {}) {
|
|
86
|
+
async function runtimeOrError() {
|
|
87
|
+
try {
|
|
88
|
+
return { runtime: await runtimeFactory() }
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return { configurationError: error }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return async function handleCloudApi(req, res, url, context = {}) {
|
|
95
|
+
if (!isAuthenticatedAppClient(context)) {
|
|
96
|
+
throw routeError('QuickForge Cloud is available only on this computer or an authenticated remote client.', 403, 'cloud_local_only')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
requireProtectedCloudWrite(req, url.pathname)
|
|
100
|
+
|
|
101
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/remote/status') {
|
|
102
|
+
const service = await readServiceConfig({ strict: false })
|
|
103
|
+
if (!service.enabled) {
|
|
104
|
+
sendJson(res, 200, { ...qfAgentStatus(), enabled: false, status: 'disabled', serverUrl: null, pid: null, verificationUriComplete: null, error: null })
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
sendJson(res, 200, qfAgentStatus())
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/config') {
|
|
112
|
+
sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/test-connection') {
|
|
117
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
118
|
+
const baseUrl = parseCloudBaseUrl(body?.cloudUrl)
|
|
119
|
+
const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
|
|
120
|
+
const client = cloudClientFactory({
|
|
121
|
+
baseUrl,
|
|
122
|
+
timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
|
|
123
|
+
})
|
|
124
|
+
try {
|
|
125
|
+
const [health, ready] = await Promise.all([client.health(), client.ready()])
|
|
126
|
+
sendJson(res, 200, { ok: true, cloudUrl: baseUrl.href, health, ready })
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw mapCloudError(error)
|
|
129
|
+
}
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (req.method === 'PUT' && url.pathname === '/api/cloud/config') {
|
|
134
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
135
|
+
if (body?.enabled !== undefined && typeof body.enabled !== 'boolean') {
|
|
136
|
+
throw routeError('QuickForge Cloud enabled must be a boolean.', 400, 'cloud_enabled_invalid')
|
|
137
|
+
}
|
|
138
|
+
const currentConfig = await readServiceConfig({ strict: false })
|
|
139
|
+
const nextUrl = parseCloudBaseUrl(body?.cloudUrl === undefined ? currentConfig.cloudUrl : body.cloudUrl).href
|
|
140
|
+
const nextEnabled = body?.enabled === undefined ? currentConfig.enabled === true : body.enabled
|
|
141
|
+
const urlChanged = !sameCloudUrl(currentConfig.cloudUrl, nextUrl)
|
|
142
|
+
if (body?.cloudUrl !== undefined) {
|
|
143
|
+
const credentialStore = credentialStoreFactory()
|
|
144
|
+
const identityRecord = await credentialStore.read()
|
|
145
|
+
if (identityRecord.refreshToken || identityRecord.pendingDeviceFlow) {
|
|
146
|
+
const boundUrl = String(identityRecord.pendingDeviceFlow?.sessionCloudUrl || identityRecord.sessionCloudUrl || '').trim()
|
|
147
|
+
const sameUrl = boundUrl ? sameCloudUrl(boundUrl, nextUrl) : false
|
|
148
|
+
if (!sameUrl) {
|
|
149
|
+
throw routeError('A QuickForge Cloud session is active. Sign out first, or explicitly rebuild the identity before switching services.', 409, 'cloud_session_active')
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
await saveServiceConfig({ cloudUrl: nextUrl, enabled: nextEnabled })
|
|
154
|
+
invalidateRuntime()
|
|
155
|
+
const saved = await readServiceConfig({ strict: false })
|
|
156
|
+
const notify = context.onCloudServiceConfigChanged || onServiceConfigChanged
|
|
157
|
+
if (typeof notify === 'function') await notify(saved, { urlChanged })
|
|
158
|
+
sendJson(res, 200, publicCloudServiceConfig(saved))
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/identity/reset') {
|
|
163
|
+
const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
164
|
+
requireDangerousConfirmation(body)
|
|
165
|
+
const { runtime } = await runtimeOrError()
|
|
166
|
+
if (runtime?.identity) await runtime.identity.resetIdentity()
|
|
167
|
+
else {
|
|
168
|
+
const store = credentialStoreFactory()
|
|
169
|
+
await store.rotateInstallation()
|
|
170
|
+
}
|
|
171
|
+
invalidateRuntime()
|
|
172
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const { runtime: current, configurationError } = await runtimeOrError()
|
|
177
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/status') {
|
|
178
|
+
if (configurationError) {
|
|
179
|
+
sendJson(res, 200, { configured: false, enabled: false, mode: 'local', configurationError: configurationError.message })
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
if (!current?.enabled) {
|
|
183
|
+
const localStatus = current?.identity ? await current.identity.status() : current?.store ? await current.store.readPublic() : { mode: 'local' }
|
|
184
|
+
sendJson(res, 200, { ...localStatus, configured: Boolean(current?.config?.baseUrl), enabled: false })
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
sendJson(res, 200, { configured: true, enabled: true, ...(await current.identity.status()) })
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (configurationError) throw routeError(configurationError.message, 503, 'cloud_configuration_error')
|
|
192
|
+
|
|
193
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
|
|
194
|
+
if (current?.identity) await current.identity.logout()
|
|
195
|
+
else if (current?.store) await current.store.clearSession({ rotateInstallationBeforeRegistration: true })
|
|
196
|
+
else await credentialStoreFactory().clearSession({ rotateInstallationBeforeRegistration: true })
|
|
197
|
+
invalidateRuntime()
|
|
198
|
+
sendJson(res, 200, { ok: true, mode: 'local' })
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!current?.enabled) throw routeError('QuickForge Cloud is disabled.', 503, 'cloud_disabled')
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/start') {
|
|
206
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
207
|
+
sendJson(res, 201, await current.identity.startDeviceFlow())
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/poll') {
|
|
211
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
212
|
+
sendJson(res, 200, await current.identity.pollDeviceFlow())
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
if (req.method === 'POST' && url.pathname === '/api/cloud/device/cancel') {
|
|
216
|
+
await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
|
|
217
|
+
sendJson(res, 200, await current.identity.cancelDeviceFlow())
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/models') {
|
|
221
|
+
sendJson(res, 200, { items: await current.models.list(undefined, { refresh: true }) })
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/usage') {
|
|
225
|
+
sendJson(res, 200, await current.identity.usage())
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
if (req.method === 'GET' && url.pathname === '/api/cloud/installations') {
|
|
229
|
+
sendJson(res, 200, await current.identity.installations())
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
const revokeMatch = url.pathname.match(/^\/api\/cloud\/installations\/([^/]+)$/)
|
|
233
|
+
if (req.method === 'DELETE' && revokeMatch) {
|
|
234
|
+
await current.identity.revokeInstallation(decodeURIComponent(revokeMatch[1]))
|
|
235
|
+
sendJson(res, 200, { ok: true })
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
throw mapCloudError(error)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
throw routeError('Not found', 404, 'route_not_found')
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
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
|