@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
|
@@ -6,7 +6,7 @@ import { logger } from './utils/logger.mjs'
|
|
|
6
6
|
import { firstOptionalBoolean, firstString, parseFrontmatter, splitDelimitedList } from './frontmatter.mjs'
|
|
7
7
|
import { normalizeAgentProfileThinkingLevel, normalizeCapabilityPolicy, normalizeModelReference, validateAgentProfileTools, validateModelReference } from './agent-profile-schema.mjs'
|
|
8
8
|
|
|
9
|
-
const DEFAULT_MAX_RUNTIME_MS =
|
|
9
|
+
const DEFAULT_MAX_RUNTIME_MS = 60 * 60 * 1000
|
|
10
10
|
const DEFAULT_MAX_TOOL_CALLS = 300
|
|
11
11
|
const nameRegex = /^[a-z][a-z0-9_-]{1,39}$/
|
|
12
12
|
const allowedToolNames = new Set(['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'])
|
|
@@ -66,14 +66,18 @@ function quoteFrontmatter(value) {
|
|
|
66
66
|
|
|
67
67
|
function formatModelFrontmatter(model) {
|
|
68
68
|
if (!model || model.mode !== 'fixed') return 'model:\n mode: inherit'
|
|
69
|
-
|
|
69
|
+
const result = [
|
|
70
70
|
'model:',
|
|
71
71
|
' mode: fixed',
|
|
72
|
-
`
|
|
73
|
-
`
|
|
72
|
+
model.source && model.source !== 'legacy-custom' ? ` source: ${quoteFrontmatter(model.source)}` : '',
|
|
73
|
+
model.providerId ? ` providerId: ${quoteFrontmatter(model.providerId)}` : '',
|
|
74
|
+
model.catalogId ? ` catalogId: ${quoteFrontmatter(model.catalogId)}` : '',
|
|
75
|
+
model.provider ? ` provider: ${quoteFrontmatter(model.provider)}` : '',
|
|
76
|
+
model.modelId ? ` modelId: ${quoteFrontmatter(model.modelId)}` : '',
|
|
74
77
|
model.api ? ` api: ${quoteFrontmatter(model.api)}` : '',
|
|
75
78
|
model.baseUrl ? ` baseUrl: ${quoteFrontmatter(model.baseUrl)}` : '',
|
|
76
79
|
].filter(Boolean).join('\n')
|
|
80
|
+
return result
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
export function userAgentProfileFilePath(name) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveModelBinding } from './model-catalog.mjs'
|
|
2
|
+
|
|
1
3
|
export const AGENT_PROFILE_TOOL_NAMES = ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command']
|
|
2
4
|
export const AGENT_PROFILE_MODEL_INHERIT = { mode: 'inherit' }
|
|
3
5
|
export const AGENT_PROFILE_THINKING_LEVELS = ['inherit', 'off', 'low', 'medium', 'high', 'xhigh']
|
|
@@ -41,10 +43,6 @@ function normalizeString(value) {
|
|
|
41
43
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function sameBaseUrl(a, b) {
|
|
45
|
-
return String(a || '').trim().replace(/\/$/, '') === String(b || '').trim().replace(/\/$/, '')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
46
|
function readModelField(value, ...keys) {
|
|
49
47
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
50
48
|
for (const key of keys) {
|
|
@@ -90,10 +88,16 @@ export function normalizeModelReference(input, metadata = {}) {
|
|
|
90
88
|
const flatModelId = normalizeString(metadata['model-id'] ?? metadata.model_id ?? metadata.modelId)
|
|
91
89
|
const flatApi = normalizeString(metadata['model-api'] ?? metadata.modelApi ?? metadata.api)
|
|
92
90
|
const flatBaseUrl = normalizeString(metadata['model-base-url'] ?? metadata.modelBaseUrl ?? metadata.baseUrl)
|
|
91
|
+
const flatSource = normalizeString(metadata['model-source'] ?? metadata.modelSource)?.toLowerCase()
|
|
92
|
+
const flatProviderId = normalizeString(metadata['model-provider-id'] ?? metadata.modelProviderId ?? metadata.providerId)
|
|
93
|
+
const flatCatalogId = normalizeString(metadata['model-catalog-id'] ?? metadata.modelCatalogId ?? metadata.catalogId)
|
|
93
94
|
|
|
94
|
-
if (flatMode === 'fixed' || flatProvider || flatModelId) {
|
|
95
|
+
if (flatMode === 'fixed' || flatSource || flatProvider || flatModelId || flatProviderId || flatCatalogId) {
|
|
95
96
|
return {
|
|
96
97
|
mode: 'fixed',
|
|
98
|
+
source: flatSource,
|
|
99
|
+
providerId: flatProviderId,
|
|
100
|
+
catalogId: flatCatalogId,
|
|
97
101
|
provider: flatProvider,
|
|
98
102
|
modelId: flatModelId,
|
|
99
103
|
api: flatApi,
|
|
@@ -123,6 +127,9 @@ export function normalizeModelReference(input, metadata = {}) {
|
|
|
123
127
|
|
|
124
128
|
return {
|
|
125
129
|
mode: 'fixed',
|
|
130
|
+
source: readModelField(modelValue, 'source'),
|
|
131
|
+
providerId: readModelField(modelValue, 'providerId', 'provider-id', 'provider_id'),
|
|
132
|
+
catalogId: readModelField(modelValue, 'catalogId', 'catalog-id', 'catalog_id', 'quickforgeCatalogId'),
|
|
126
133
|
provider: readModelField(modelValue, 'provider'),
|
|
127
134
|
modelId: readModelField(modelValue, 'modelId', 'model-id', 'model_id', 'id', 'model'),
|
|
128
135
|
api: readModelField(modelValue, 'api'),
|
|
@@ -133,9 +140,19 @@ export function normalizeModelReference(input, metadata = {}) {
|
|
|
133
140
|
export function validateModelReference(modelRef) {
|
|
134
141
|
const ref = modelRef || AGENT_PROFILE_MODEL_INHERIT
|
|
135
142
|
if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
143
|
+
if (ref.source === 'cloud') {
|
|
144
|
+
const catalogId = ref.catalogId || ref.modelId
|
|
145
|
+
if (!catalogId) throw requestError('Fixed Cloud agent model requires catalogId')
|
|
146
|
+
return { mode: 'fixed', source: 'cloud', catalogId }
|
|
147
|
+
}
|
|
148
|
+
if (ref.source === 'custom') {
|
|
149
|
+
if (!ref.providerId || !ref.modelId) throw requestError('Fixed custom agent model requires providerId and modelId')
|
|
150
|
+
return { mode: 'fixed', source: 'custom', providerId: ref.providerId, modelId: ref.modelId }
|
|
151
|
+
}
|
|
136
152
|
if (!ref.provider || !ref.modelId) throw requestError('Fixed agent model requires provider and modelId')
|
|
137
153
|
return {
|
|
138
154
|
mode: 'fixed',
|
|
155
|
+
source: 'legacy-custom',
|
|
139
156
|
provider: ref.provider,
|
|
140
157
|
modelId: ref.modelId,
|
|
141
158
|
api: ref.api,
|
|
@@ -164,14 +181,17 @@ export function modelReferenceSnapshot(modelRef) {
|
|
|
164
181
|
if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
|
|
165
182
|
return {
|
|
166
183
|
mode: 'fixed',
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
...(ref.source && ref.source !== 'legacy-custom' ? { source: ref.source } : {}),
|
|
185
|
+
...(ref.providerId ? { providerId: ref.providerId } : {}),
|
|
186
|
+
...(ref.catalogId ? { catalogId: ref.catalogId } : {}),
|
|
187
|
+
...(ref.provider ? { provider: ref.provider } : {}),
|
|
188
|
+
...(ref.modelId ? { modelId: ref.modelId } : {}),
|
|
169
189
|
...(ref.api ? { api: ref.api } : {}),
|
|
170
190
|
...(ref.baseUrl ? { baseUrl: ref.baseUrl } : {}),
|
|
171
191
|
}
|
|
172
192
|
}
|
|
173
193
|
|
|
174
|
-
export async function resolveAgentProfileModel(profile, parentModel,
|
|
194
|
+
export async function resolveAgentProfileModel(profile, parentModel, _readStore, context = {}) {
|
|
175
195
|
const ref = profile?.model || AGENT_PROFILE_MODEL_INHERIT
|
|
176
196
|
if (!ref || ref.mode !== 'fixed') {
|
|
177
197
|
if (!parentModel) throw requestError('No active model is configured for the parent session.')
|
|
@@ -179,19 +199,23 @@ export async function resolveAgentProfileModel(profile, parentModel, readStore)
|
|
|
179
199
|
}
|
|
180
200
|
|
|
181
201
|
const validRef = validateModelReference(ref)
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
const modelRef = validRef.source === 'cloud'
|
|
203
|
+
? { version: 1, source: 'cloud', catalogId: validRef.catalogId }
|
|
204
|
+
: validRef.source === 'custom'
|
|
205
|
+
? { version: 1, source: 'custom', providerId: validRef.providerId, modelId: validRef.modelId }
|
|
206
|
+
: {
|
|
207
|
+
version: 1,
|
|
208
|
+
source: 'legacy-custom',
|
|
209
|
+
provider: validRef.provider,
|
|
210
|
+
modelId: validRef.modelId,
|
|
211
|
+
...(validRef.api ? { api: validRef.api } : {}),
|
|
212
|
+
...(validRef.baseUrl ? { baseUrl: validRef.baseUrl } : {}),
|
|
213
|
+
}
|
|
214
|
+
const binding = await resolveModelBinding({ modelRef }, {
|
|
215
|
+
context,
|
|
216
|
+
currentModel: parentModel,
|
|
217
|
+
allowCurrentHidden: true,
|
|
218
|
+
forExecution: true,
|
|
219
|
+
})
|
|
220
|
+
return { model: binding.model, modelRef: binding.modelRef, info: { ...modelReferenceSnapshot(validRef), inherited: false } }
|
|
197
221
|
}
|
|
@@ -27,7 +27,7 @@ const BUILTIN_OVERRIDES_STORE = 'agent-profile-overrides'
|
|
|
27
27
|
const RESERVED_NAMES = new Set(subagentDefinitions.map((definition) => definition.name))
|
|
28
28
|
const allowedToolNames = new Set(AGENT_PROFILE_TOOL_NAMES)
|
|
29
29
|
const nameRegex = /^[a-z][a-z0-9_-]{1,39}$/
|
|
30
|
-
const DEFAULT_MAX_RUNTIME_MS =
|
|
30
|
+
const DEFAULT_MAX_RUNTIME_MS = 60 * 60 * 1000
|
|
31
31
|
const DEFAULT_MAX_TOOL_CALLS = 300
|
|
32
32
|
const MIGRATION_MARKER_FILE = path.join(userAgentsDir, '.custom-agents-migrated.json')
|
|
33
33
|
let customAgentMigrationPromise = null
|
|
@@ -75,7 +75,7 @@ function builtinProfileFromSubagent(definition, override = null) {
|
|
|
75
75
|
allowedTools: [...definition.allowedTools],
|
|
76
76
|
capabilityPolicy: definition.capabilityPolicy || inferCapabilityPolicy(definition.allowedTools),
|
|
77
77
|
model: modelReferenceSnapshot(override?.model || definition.model),
|
|
78
|
-
thinkingLevel: 'inherit',
|
|
78
|
+
thinkingLevel: normalizeAgentProfileThinkingLevel(override?.thinkingLevel || 'inherit'),
|
|
79
79
|
lifecycle: 'builtin',
|
|
80
80
|
managed: true,
|
|
81
81
|
maxRuntimeMs: definition.maxRuntimeMs || DEFAULT_MAX_RUNTIME_MS,
|
|
@@ -308,17 +308,36 @@ export async function updateCustomAgentProfile(id, patch) {
|
|
|
308
308
|
return { ...next, filePath: file, relativePath: `~/.quickforge/agents/${next.name}.md` }
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
export async function
|
|
311
|
+
export async function updateBuiltinAgentOverrides(id, patch = {}) {
|
|
312
312
|
const current = await getAgentProfile(id)
|
|
313
313
|
if (!current) throw requestError('Agent not found', 404)
|
|
314
|
-
if (!current.builtin) throw requestError('Only built-in agents support
|
|
315
|
-
const model = validateModelReference(normalizeModelReference(modelInput))
|
|
314
|
+
if (!current.builtin) throw requestError('Only built-in agents support overrides', 400)
|
|
316
315
|
const overrides = await readStore(BUILTIN_OVERRIDES_STORE).catch(() => ({}))
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
const base = overrides && typeof overrides === 'object' ? overrides : {}
|
|
317
|
+
const next = { ...base }
|
|
318
|
+
const entry = { ...(base[current.name] && typeof base[current.name] === 'object' ? base[current.name] : {}) }
|
|
319
|
+
|
|
320
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'model')) {
|
|
321
|
+
const model = validateModelReference(normalizeModelReference(patch.model))
|
|
322
|
+
if (model.mode === 'fixed') entry.model = model
|
|
323
|
+
else delete entry.model
|
|
324
|
+
}
|
|
325
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'thinkingLevel')) {
|
|
326
|
+
const thinkingLevel = normalizeAgentProfileThinkingLevel(patch.thinkingLevel)
|
|
327
|
+
if (thinkingLevel === 'inherit') delete entry.thinkingLevel
|
|
328
|
+
else entry.thinkingLevel = thinkingLevel
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (Object.keys(entry).length === 0) delete next[current.name]
|
|
332
|
+
else next[current.name] = entry
|
|
320
333
|
await writeStore(BUILTIN_OVERRIDES_STORE, next)
|
|
321
|
-
|
|
334
|
+
|
|
335
|
+
const definition = subagentDefinitions.find((item) => item.name === current.name)
|
|
336
|
+
return builtinProfileFromSubagent(definition, next[current.name] || null)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function updateBuiltinAgentModelOverride(id, modelInput) {
|
|
340
|
+
return updateBuiltinAgentOverrides(id, { model: modelInput })
|
|
322
341
|
}
|
|
323
342
|
|
|
324
343
|
export async function deleteCustomAgentProfile(id) {
|
|
@@ -3,6 +3,8 @@ import path from 'node:path'
|
|
|
3
3
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
4
4
|
import { randomUUID } from 'node:crypto'
|
|
5
5
|
import { streamSimple } from '@earendil-works/pi-ai/compat'
|
|
6
|
+
import { resolveManagedCloudProvider } from './cloud/runtime.mjs'
|
|
7
|
+
import { ensureCloudChatIdempotencyKey } from './cloud/chat-idempotency-store.mjs'
|
|
6
8
|
import {
|
|
7
9
|
DEFAULT_AI_STREAM_DEADLINE_MS,
|
|
8
10
|
withDefaultAiProviderOptions,
|
|
@@ -45,14 +47,27 @@ function writeAiHttpRecord(record) {
|
|
|
45
47
|
})
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
const SENSITIVE_HEADER_NAMES = new Set([
|
|
51
|
+
'authorization',
|
|
52
|
+
'proxy-authorization',
|
|
53
|
+
'x-api-key',
|
|
54
|
+
'api-key',
|
|
55
|
+
'idempotency-key',
|
|
56
|
+
'cookie',
|
|
57
|
+
'set-cookie',
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
export function sanitizeHttpHeaders(headers) {
|
|
49
61
|
const result = {}
|
|
50
62
|
if (!headers) return result
|
|
51
63
|
|
|
52
64
|
try {
|
|
53
65
|
const iterable = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers)
|
|
54
66
|
for (const [key, value] of iterable) {
|
|
55
|
-
|
|
67
|
+
const name = String(key)
|
|
68
|
+
result[name] = SENSITIVE_HEADER_NAMES.has(name.toLowerCase())
|
|
69
|
+
? '[REDACTED]'
|
|
70
|
+
: Array.isArray(value) ? value.join(', ') : String(value)
|
|
56
71
|
}
|
|
57
72
|
} catch {
|
|
58
73
|
// ignore malformed headers
|
|
@@ -61,6 +76,10 @@ function headersToRecord(headers) {
|
|
|
61
76
|
return result
|
|
62
77
|
}
|
|
63
78
|
|
|
79
|
+
function headersToRecord(headers) {
|
|
80
|
+
return sanitizeHttpHeaders(headers)
|
|
81
|
+
}
|
|
82
|
+
|
|
64
83
|
function isRequest(value) {
|
|
65
84
|
return typeof Request !== 'undefined' && value instanceof Request
|
|
66
85
|
}
|
|
@@ -253,6 +272,63 @@ function wrapStreamWithDeadline(stream, deadlineController, deadlineMs) {
|
|
|
253
272
|
}
|
|
254
273
|
}
|
|
255
274
|
|
|
275
|
+
function lazyStream(streamPromise) {
|
|
276
|
+
return {
|
|
277
|
+
result: () => streamPromise.then((stream) => stream.result()),
|
|
278
|
+
[Symbol.asyncIterator]() {
|
|
279
|
+
let iteratorPromise
|
|
280
|
+
const iterator = () => {
|
|
281
|
+
iteratorPromise ||= streamPromise.then((stream) => stream[Symbol.asyncIterator]())
|
|
282
|
+
return iteratorPromise
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
next: async () => (await iterator()).next(),
|
|
286
|
+
return: async () => {
|
|
287
|
+
const resolved = await iterator()
|
|
288
|
+
if (typeof resolved.return === 'function') return resolved.return()
|
|
289
|
+
return { value: undefined, done: true }
|
|
290
|
+
},
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function createProviderStream(model, context, options) {
|
|
297
|
+
return aiHttpLogEnabled
|
|
298
|
+
? aiHttpContext.run({
|
|
299
|
+
traceId: randomUUID(),
|
|
300
|
+
sessionId: options.sessionId,
|
|
301
|
+
purpose: options.metadata?.quickforgePurpose || 'chat',
|
|
302
|
+
provider: model?.provider,
|
|
303
|
+
api: model?.api,
|
|
304
|
+
model: model?.id,
|
|
305
|
+
}, () => streamSimple(model, context, options))
|
|
306
|
+
: streamSimple(model, context, options)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const CLIENT_MESSAGE_ID_FIELD = 'quickforgeClientMessageId'
|
|
310
|
+
|
|
311
|
+
function logicalMessageIdFromContext(context) {
|
|
312
|
+
const messages = Array.isArray(context?.messages) ? context.messages : []
|
|
313
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
314
|
+
const message = messages[index]
|
|
315
|
+
if (message?.role !== 'user' && message?.role !== 'user-with-attachments') continue
|
|
316
|
+
const metadata = message.metadata
|
|
317
|
+
const messageId = metadata && typeof metadata === 'object' && !Array.isArray(metadata)
|
|
318
|
+
? metadata[CLIENT_MESSAGE_ID_FIELD]
|
|
319
|
+
: undefined
|
|
320
|
+
return typeof messageId === 'string' && messageId ? messageId : undefined
|
|
321
|
+
}
|
|
322
|
+
return undefined
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function managedCloudIdempotencyKey(context, options) {
|
|
326
|
+
const messageId = logicalMessageIdFromContext(context)
|
|
327
|
+
return options.sessionId && messageId
|
|
328
|
+
? ensureCloudChatIdempotencyKey(options.sessionId, messageId)
|
|
329
|
+
: randomUUID()
|
|
330
|
+
}
|
|
331
|
+
|
|
256
332
|
export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
|
|
257
333
|
const providerOptions = withDefaultAiProviderOptions(options)
|
|
258
334
|
const deadlineMs = Number.isFinite(providerOptions.deadlineMs)
|
|
@@ -265,16 +341,26 @@ export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
|
|
|
265
341
|
}
|
|
266
342
|
delete effectiveOptions.deadlineMs
|
|
267
343
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
344
|
+
const managedCloud = model?.provider === 'quickforge-cloud' && model?.quickforgeModelSource === 'cloud'
|
|
345
|
+
const stream = managedCloud
|
|
346
|
+
? lazyStream(Promise.all([
|
|
347
|
+
resolveManagedCloudProvider(model, effectiveOptions.signal),
|
|
348
|
+
managedCloudIdempotencyKey(context, effectiveOptions),
|
|
349
|
+
]).then(([resolved, idempotencyKey]) => createProviderStream(
|
|
350
|
+
{
|
|
351
|
+
...resolved.model,
|
|
352
|
+
headers: {
|
|
353
|
+
...(resolved.model.headers || {}),
|
|
354
|
+
'Idempotency-Key': idempotencyKey,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
context,
|
|
358
|
+
{
|
|
359
|
+
...effectiveOptions,
|
|
360
|
+
apiKey: resolved.apiKey,
|
|
361
|
+
},
|
|
362
|
+
)))
|
|
363
|
+
: createProviderStream(model, context, effectiveOptions)
|
|
278
364
|
|
|
279
365
|
return wrapStreamWithDeadline(stream, deadlineController, deadlineMs)
|
|
280
366
|
}
|
|
@@ -18,6 +18,14 @@ export const DEFAULT_AUTO_COMPACT_SETTINGS = {
|
|
|
18
18
|
requireConfirmation: true,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* 保留尾部(最近回合)的字符预算上限。当会话以工具消息为主、user 回合
|
|
23
|
+
* 稀疏时,按 keepRecentTurns 从尾部回溯会跨越大量工具消息,导致压缩
|
|
24
|
+
* 范围过小甚至为空(tailStart 与 compactedUpToIndex 重合)。达到该预算
|
|
25
|
+
* 时按当前用户轮次起点截断,将更早的完整工具链历史并入压缩范围。
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_MAX_TAIL_CHARS = 40000
|
|
28
|
+
|
|
21
29
|
const AUTO_COMPACT_MIN_INTERVAL_MS = 30_000
|
|
22
30
|
const AUTO_COMPACT_REJECTION_SUPPRESS_MS = 10 * 60_000
|
|
23
31
|
|
|
@@ -64,11 +72,13 @@ function contentToText(content) {
|
|
|
64
72
|
}).filter(Boolean).join('\n')
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
function messageChars(message) {
|
|
76
|
+
if (!message || typeof message !== 'object') return 0
|
|
77
|
+
return [message.role || '', contentToText(message.content), safeJson(message.attachments)].join('\n').length
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
function estimateMessagesChars(messages) {
|
|
68
|
-
return (Array.isArray(messages) ? messages : []).reduce((total, message) =>
|
|
69
|
-
if (!message || typeof message !== 'object') return total
|
|
70
|
-
return total + [message.role || '', contentToText(message.content), safeJson(message.attachments)].join('\n').length
|
|
71
|
-
}, 0)
|
|
81
|
+
return (Array.isArray(messages) ? messages : []).reduce((total, message) => total + messageChars(message), 0)
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
function isUserMessage(message) {
|
|
@@ -76,13 +86,26 @@ function isUserMessage(message) {
|
|
|
76
86
|
&& !isCompactSummaryMessage(message)
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
function
|
|
89
|
+
function isToolResultMessage(message) {
|
|
90
|
+
return message?.role === 'toolResult'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function tailStartForRecentTurns(messages, keepRecentTurns, maxTailChars = DEFAULT_MAX_TAIL_CHARS) {
|
|
80
94
|
const source = Array.isArray(messages) ? messages : []
|
|
81
95
|
let seenUserTurns = 0
|
|
96
|
+
let tailChars = 0
|
|
82
97
|
for (let index = source.length - 1; index >= 0; index--) {
|
|
83
|
-
if (
|
|
84
|
-
|
|
98
|
+
if (isUserMessage(source[index])) seenUserTurns += 1
|
|
99
|
+
tailChars += messageChars(source[index])
|
|
85
100
|
if (seenUserTurns >= keepRecentTurns) return index
|
|
101
|
+
// 保留的尾部超过预算:仍按完整用户轮次切分,避免把当前运行中的
|
|
102
|
+
// assistant/toolResult 链从中间截断,导致压缩线落在一轮对话内部。
|
|
103
|
+
if (tailChars >= maxTailChars) {
|
|
104
|
+
for (let turnStart = index; turnStart >= 0; turnStart--) {
|
|
105
|
+
if (isUserMessage(source[turnStart])) return turnStart
|
|
106
|
+
}
|
|
107
|
+
return 0
|
|
108
|
+
}
|
|
86
109
|
}
|
|
87
110
|
return 0
|
|
88
111
|
}
|
|
@@ -145,7 +168,11 @@ export function buildAutoCompactLoopMessages(session, messages) {
|
|
|
145
168
|
if (!summaryMessage) return messages
|
|
146
169
|
const source = Array.isArray(messages) ? messages : []
|
|
147
170
|
const compactedUpToIndex = Math.min(source.length, Math.max(0, Number(session.contextCompaction?.compactedUpToIndex) || 0))
|
|
148
|
-
|
|
171
|
+
// 防御旧持久化数据:compactedUpToIndex 可能指向 toolResult,跳过开头的孤儿,
|
|
172
|
+
// 避免压缩后的消息序列以 tool 消息开头(LLM API 会拒绝孤立 tool 消息)
|
|
173
|
+
let tailStart = compactedUpToIndex
|
|
174
|
+
while (tailStart < source.length && isToolResultMessage(source[tailStart])) tailStart += 1
|
|
175
|
+
return [summaryMessage, ...source.slice(tailStart)]
|
|
149
176
|
}
|
|
150
177
|
|
|
151
178
|
export async function compactSessionInPlace({
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { ensureStorage, storageDir } from '../storage.mjs'
|
|
5
|
+
|
|
6
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
7
|
+
const MESSAGE_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/
|
|
8
|
+
|
|
9
|
+
function digest(value) {
|
|
10
|
+
return createHash('sha256').update(value).digest('hex')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function assertIdentifier(value, label, pattern) {
|
|
14
|
+
const normalized = typeof value === 'string' ? value.trim() : ''
|
|
15
|
+
if (!pattern.test(normalized)) throw new Error(`Invalid ${label}.`)
|
|
16
|
+
return normalized
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createCloudChatIdempotencyStore({
|
|
20
|
+
directory = path.join(storageDir, 'security', 'cloud-chat-idempotency'),
|
|
21
|
+
ensureBaseStorage = ensureStorage,
|
|
22
|
+
} = {}) {
|
|
23
|
+
function entryPath(sessionId, messageId) {
|
|
24
|
+
return path.join(directory, digest(sessionId), `${digest(messageId)}.json`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function ensureDirectory(file) {
|
|
28
|
+
await ensureBaseStorage()
|
|
29
|
+
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function readEntry(file, sessionId, messageId) {
|
|
33
|
+
try {
|
|
34
|
+
const raw = await fs.readFile(file, 'utf8')
|
|
35
|
+
const entry = raw.trim() ? JSON.parse(raw) : null
|
|
36
|
+
if (entry?.sessionId !== sessionId || entry?.messageId !== messageId || !UUID_PATTERN.test(entry?.idempotencyKey || '')) {
|
|
37
|
+
throw new Error('Invalid QuickForge Cloud chat idempotency record.')
|
|
38
|
+
}
|
|
39
|
+
return entry
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error?.code === 'ENOENT') return null
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function ensure(sessionIdInput, messageIdInput) {
|
|
47
|
+
const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
|
|
48
|
+
const messageId = assertIdentifier(messageIdInput, 'logical message ID', MESSAGE_ID_PATTERN)
|
|
49
|
+
const file = entryPath(sessionId, messageId)
|
|
50
|
+
await ensureDirectory(file)
|
|
51
|
+
|
|
52
|
+
const existing = await readEntry(file, sessionId, messageId)
|
|
53
|
+
if (existing) return existing.idempotencyKey
|
|
54
|
+
|
|
55
|
+
const entry = {
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
sessionId,
|
|
58
|
+
messageId,
|
|
59
|
+
idempotencyKey: randomUUID(),
|
|
60
|
+
createdAt: new Date().toISOString(),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`
|
|
64
|
+
await fs.writeFile(temporary, `${JSON.stringify(entry, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 })
|
|
65
|
+
try { await fs.chmod(temporary, 0o600) } catch { /* best effort on Windows */ }
|
|
66
|
+
try {
|
|
67
|
+
await fs.link(temporary, file)
|
|
68
|
+
try { await fs.chmod(file, 0o600) } catch { /* best effort on Windows */ }
|
|
69
|
+
return entry.idempotencyKey
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code !== 'EEXIST') throw error
|
|
72
|
+
const concurrent = await readEntry(file, sessionId, messageId)
|
|
73
|
+
if (!concurrent) throw error
|
|
74
|
+
return concurrent.idempotencyKey
|
|
75
|
+
} finally {
|
|
76
|
+
await fs.rm(temporary, { force: true })
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function removeSession(sessionIdInput) {
|
|
81
|
+
const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
|
|
82
|
+
await fs.rm(path.join(directory, digest(sessionId)), { recursive: true, force: true })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { directory, ensure, removeSession }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const defaultStore = createCloudChatIdempotencyStore()
|
|
89
|
+
|
|
90
|
+
export function ensureCloudChatIdempotencyKey(sessionId, messageId) {
|
|
91
|
+
return defaultStore.ensure(sessionId, messageId)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function deleteCloudChatIdempotencySession(sessionId) {
|
|
95
|
+
return defaultStore.removeSession(sessionId)
|
|
96
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { cloudEndpoint } from './config.mjs'
|
|
2
|
+
|
|
3
|
+
export class CloudApiError extends Error {
|
|
4
|
+
constructor(message, { status = 0, code = 'cloud_request_failed', retryable = false, details } = {}) {
|
|
5
|
+
super(message)
|
|
6
|
+
this.name = 'CloudApiError'
|
|
7
|
+
this.status = status
|
|
8
|
+
this.code = code
|
|
9
|
+
this.retryable = retryable
|
|
10
|
+
this.details = details
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function parseError(response) {
|
|
15
|
+
let payload
|
|
16
|
+
try { payload = await response.json() } catch { payload = undefined }
|
|
17
|
+
const error = payload?.error
|
|
18
|
+
const structured = error && typeof error === 'object' ? error : undefined
|
|
19
|
+
const oauthCode = typeof error === 'string' ? error : undefined
|
|
20
|
+
return new CloudApiError(
|
|
21
|
+
structured?.message || payload?.error_description || `QuickForge Cloud request failed (${response.status}).`,
|
|
22
|
+
{
|
|
23
|
+
status: response.status,
|
|
24
|
+
code: structured?.code || payload?.code || oauthCode || 'cloud_request_failed',
|
|
25
|
+
retryable: Boolean(structured?.retryable || payload?.retryable),
|
|
26
|
+
details: structured?.details || payload?.details,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function timeoutSignal(timeoutMs, outerSignal) {
|
|
32
|
+
const timeout = AbortSignal.timeout(timeoutMs)
|
|
33
|
+
return outerSignal ? AbortSignal.any([outerSignal, timeout]) : timeout
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class CloudClient {
|
|
37
|
+
constructor({ baseUrl, timeoutMs = 10_000, fetchImpl = fetch } = {}) {
|
|
38
|
+
if (!(baseUrl instanceof URL)) throw new Error('CloudClient requires a configured base URL.')
|
|
39
|
+
this.baseUrl = baseUrl
|
|
40
|
+
this.timeoutMs = timeoutMs
|
|
41
|
+
this.fetch = fetchImpl
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async request(pathname, { method = 'GET', token, body, headers, signal, raw = false, timeout = true } = {}) {
|
|
45
|
+
const requestHeaders = { Accept: 'application/json', ...headers }
|
|
46
|
+
if (body !== undefined) requestHeaders['Content-Type'] = 'application/json'
|
|
47
|
+
if (token) requestHeaders.Authorization = `Bearer ${token}`
|
|
48
|
+
const response = await this.fetch(cloudEndpoint(this.baseUrl, pathname), {
|
|
49
|
+
method,
|
|
50
|
+
headers: requestHeaders,
|
|
51
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
52
|
+
signal: timeout ? timeoutSignal(this.timeoutMs, signal) : signal,
|
|
53
|
+
redirect: 'error',
|
|
54
|
+
})
|
|
55
|
+
if (!response.ok) throw await parseError(response)
|
|
56
|
+
if (raw) return response
|
|
57
|
+
if (response.status === 204) return undefined
|
|
58
|
+
return response.json()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
health(signal) { return this.request('healthz', { signal }) }
|
|
62
|
+
ready(signal) { return this.request('readyz', { signal }) }
|
|
63
|
+
refresh(refreshToken, signal) {
|
|
64
|
+
return this.request('oauth/token', {
|
|
65
|
+
method: 'POST', body: { grantType: 'refresh_token', refreshToken }, signal,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
authorizeDevice({ installationId, clientId = 'quickforge-desktop', publicKey, installationName, platform, clientVersion, signal } = {}) {
|
|
69
|
+
return this.request('oauth/device_authorization', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
body: { installationId, clientId, publicKey, installationName, platform, clientVersion },
|
|
72
|
+
signal,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
exchangeDeviceCode(deviceCode, clientId = 'quickforge-desktop', signal) {
|
|
76
|
+
return this.request('oauth/token', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: {
|
|
79
|
+
grantType: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
80
|
+
deviceCode,
|
|
81
|
+
clientId,
|
|
82
|
+
},
|
|
83
|
+
signal,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
me(token, signal) { return this.request('v1/me', { token, signal }) }
|
|
87
|
+
models(token, signal) { return this.request('v1/models', { token, signal }) }
|
|
88
|
+
usage(token, signal) { return this.request('v1/usage', { token, signal }) }
|
|
89
|
+
installations(token, signal) { return this.request('v1/installations', { token, signal }) }
|
|
90
|
+
revokeInstallation(token, installationId, signal) {
|
|
91
|
+
return this.request(`v1/installations/${encodeURIComponent(installationId)}`, { method: 'DELETE', token, signal })
|
|
92
|
+
}
|
|
93
|
+
chat(token, payload, idempotencyKey, signal) {
|
|
94
|
+
return this.request('v1/chat/completions', {
|
|
95
|
+
method: 'POST', token, body: payload,
|
|
96
|
+
headers: { 'Idempotency-Key': idempotencyKey }, signal, raw: true, timeout: false,
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|