@shawnstack/quickforge 1.7.3 → 1.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/quickforge.mjs +0 -21
- package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
- package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
- package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
- package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
- package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
- package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +1639 -0
- package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
- package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
- package/dist/assets/TerminalDock-D4zJBJc4.js +2 -0
- package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
- package/dist/assets/{arc-Bhg4c0ht.js → arc-w21O0TLn.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.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-CwWLNjMZ.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
- package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.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-DLjV-dTm.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
- package/dist/assets/{dagre-CY3D84Cl.js → dagre-DmHb3Ctt.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-CIave0An.js} +1 -1
- package/dist/assets/dist-DLKC_odd.js +1 -0
- package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
- package/dist/assets/esm-Df4edDpj.js +2 -0
- package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
- package/dist/assets/icons-CXPP1fFT.js +1 -0
- package/dist/assets/index-_ZhK6bvp.js +65 -0
- package/dist/assets/index-iVV2bP_f.css +3 -0
- package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
- package/dist/assets/{line-ikKkQiFp.js → line-B4DxX7tC.js} +1 -1
- package/dist/assets/{linear-GqPyxQkN.js → linear-U-nKUhyW.js} +1 -1
- package/dist/assets/{map-CJwjCvwr.js → map-BneTM-ri.js} +1 -1
- package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
- package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
- package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-jEbqFaTe.js} +2 -2
- package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DWpShXCs.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
- package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
- package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
- package/dist/assets/{monaco-BTsVCDWS.js → monaco-D6OhXgjg.js} +1 -1
- package/dist/assets/{ordinal-CibZU3PM.js → ordinal-B2r5L1v4.js} +1 -1
- package/dist/assets/{otel-BNGM50C-.js → otel-BFnphzjt.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
- package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-Cr2ee4a0.js} +2 -2
- package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-DV5jKjSg.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.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-CTUvpKkv.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
- package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-1KC5PyRx.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
- package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-BThnIUBs.js} +3 -1
- package/dist/assets/{src-B4Tvg3gT.js → src-PYsDme0X.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.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-CR49Y4Lp.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
- package/dist/assets/web-DoV8MSMn.js +1 -0
- package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
- package/dist/index.html +13 -11
- package/dist/mobile-error.html +24 -0
- package/package.json +6 -1
- package/plugins/{openai-documents → documents}/plugin.json +2 -2
- package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
- package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
- package/server/acp/server.mjs +50 -35
- package/server/agent-manager.mjs +343 -46
- package/server/agent-profile-files.mjs +8 -4
- package/server/agent-profile-schema.mjs +47 -23
- package/server/agent-profiles.mjs +1 -1
- package/server/ai-http-logger.mjs +156 -13
- package/server/ai-provider-options.mjs +3 -0
- package/server/channels/channel-logs.mjs +13 -0
- package/server/channels/event-relay.mjs +33 -0
- package/server/channels/process-channel.mjs +29 -0
- package/server/channels/providers/wechat.mjs +9 -3
- package/server/channels/registry.mjs +5 -1
- package/server/cloud/chat-idempotency-store.mjs +96 -0
- package/server/cloud/client.mjs +102 -0
- package/server/cloud/config.mjs +53 -0
- package/server/cloud/credential-store.mjs +193 -0
- package/server/cloud/identity.mjs +355 -0
- package/server/cloud/index.mjs +7 -0
- package/server/cloud/models.mjs +80 -0
- package/server/cloud/runtime.mjs +129 -0
- package/server/cloud/service-config.mjs +77 -0
- package/server/index.mjs +63 -33
- package/server/lan-access-store.mjs +93 -3
- package/server/model-catalog.mjs +262 -0
- package/server/network-proxy.mjs +68 -3
- package/server/plugins/registry.mjs +93 -9
- package/server/public-api.mjs +6 -2
- package/server/routes/agent-profiles.mjs +43 -9
- package/server/routes/agent.mjs +48 -11
- package/server/routes/backup.mjs +7 -1
- package/server/routes/channels.mjs +47 -0
- package/server/routes/cloud.mjs +227 -0
- package/server/routes/lan-access.mjs +246 -21
- package/server/routes/models.mjs +13 -4
- package/server/routes/project.mjs +19 -1
- package/server/routes/scheduled-tasks.mjs +40 -10
- package/server/routes/shared-conversation.mjs +62 -58
- package/server/routes/shares.mjs +76 -1
- package/server/routes/static.mjs +26 -1
- package/server/routes/storage.mjs +24 -1
- package/server/routes/system.mjs +11 -0
- package/server/routes/terminal.mjs +7 -2
- package/server/routes/workspace.mjs +66 -13
- package/server/session-utils.mjs +2 -2
- package/server/share-store.mjs +168 -13
- package/server/skills.mjs +30 -3
- package/server/storage.mjs +7 -1
- package/server/subagents.mjs +4 -4
- package/server/terminal/terminal-manager.mjs +2 -4
- package/server/tools/definitions.mjs +3 -3
- package/server/tools/index.mjs +7 -5
- package/server/utils/network.mjs +9 -0
- package/server/utils/package-update.mjs +70 -47
- package/server/utils/process-env.mjs +7 -0
- package/server/utils/response.mjs +10 -3
- 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-D7pbg8nm.js +0 -1
- package/dist/assets/ChatPanelHost-wfGCaCzD.js +0 -288
- package/dist/assets/PluginsPage-NAGroBm3.js +0 -1
- package/dist/assets/ScheduledTasksPage-Bp0MNfQD.js +0 -2
- package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
- package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
- package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
- package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
- package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
- package/dist/assets/channel-Rz20dKTK.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
- package/dist/assets/icons-BP8YOS-Z.js +0 -1
- package/dist/assets/index-C6k5taeb.js +0 -66
- package/dist/assets/index-CzN8NSKC.css +0 -3
- package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
- package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
- package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
- package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
- /package/dist/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
- /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
- /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-DyfqweF2.js} +0 -0
- /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/assets/{dist-DelZBZpK.js → dist-CLg13NtV.js} +0 -0
- /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-DJkmx0Cq.js} +0 -0
- /package/dist/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
- /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
- /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
- /package/dist/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
- /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-Uu2xAB4d.js} +0 -0
- /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/dist/assets/{xterm-CGiQzDiR.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
|
|
@@ -3,7 +3,12 @@ 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 {
|
|
6
|
+
import { resolveManagedCloudProvider } from './cloud/runtime.mjs'
|
|
7
|
+
import { ensureCloudChatIdempotencyKey } from './cloud/chat-idempotency-store.mjs'
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_AI_STREAM_DEADLINE_MS,
|
|
10
|
+
withDefaultAiProviderOptions,
|
|
11
|
+
} from './ai-provider-options.mjs'
|
|
7
12
|
import { logsDir } from './storage.mjs'
|
|
8
13
|
|
|
9
14
|
const PATCH_MARKER = Symbol.for('quickforge.aiHttpLogger.fetchPatched')
|
|
@@ -42,14 +47,27 @@ function writeAiHttpRecord(record) {
|
|
|
42
47
|
})
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
|
|
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) {
|
|
46
61
|
const result = {}
|
|
47
62
|
if (!headers) return result
|
|
48
63
|
|
|
49
64
|
try {
|
|
50
65
|
const iterable = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers)
|
|
51
66
|
for (const [key, value] of iterable) {
|
|
52
|
-
|
|
67
|
+
const name = String(key)
|
|
68
|
+
result[name] = SENSITIVE_HEADER_NAMES.has(name.toLowerCase())
|
|
69
|
+
? '[REDACTED]'
|
|
70
|
+
: Array.isArray(value) ? value.join(', ') : String(value)
|
|
53
71
|
}
|
|
54
72
|
} catch {
|
|
55
73
|
// ignore malformed headers
|
|
@@ -58,6 +76,10 @@ function headersToRecord(headers) {
|
|
|
58
76
|
return result
|
|
59
77
|
}
|
|
60
78
|
|
|
79
|
+
function headersToRecord(headers) {
|
|
80
|
+
return sanitizeHttpHeaders(headers)
|
|
81
|
+
}
|
|
82
|
+
|
|
61
83
|
function isRequest(value) {
|
|
62
84
|
return typeof Request !== 'undefined' && value instanceof Request
|
|
63
85
|
}
|
|
@@ -206,18 +228,139 @@ export function installAiHttpLogger() {
|
|
|
206
228
|
globalThis[PATCH_MARKER] = true
|
|
207
229
|
}
|
|
208
230
|
|
|
231
|
+
function combineAbortSignals(parentSignal, deadlineSignal) {
|
|
232
|
+
if (!parentSignal) return deadlineSignal
|
|
233
|
+
if (typeof AbortSignal.any === 'function') return AbortSignal.any([parentSignal, deadlineSignal])
|
|
234
|
+
|
|
235
|
+
const controller = new AbortController()
|
|
236
|
+
const abort = () => controller.abort()
|
|
237
|
+
if (parentSignal.aborted || deadlineSignal.aborted) {
|
|
238
|
+
controller.abort()
|
|
239
|
+
} else {
|
|
240
|
+
parentSignal.addEventListener('abort', abort, { once: true })
|
|
241
|
+
deadlineSignal.addEventListener('abort', abort, { once: true })
|
|
242
|
+
}
|
|
243
|
+
return controller.signal
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function wrapStreamWithDeadline(stream, deadlineController, deadlineMs) {
|
|
247
|
+
let timer
|
|
248
|
+
const deadlinePromise = new Promise((_, reject) => {
|
|
249
|
+
timer = setTimeout(() => {
|
|
250
|
+
deadlineController.abort()
|
|
251
|
+
reject(new Error(`AI stream timed out after ${deadlineMs}ms`))
|
|
252
|
+
}, deadlineMs)
|
|
253
|
+
timer.unref?.()
|
|
254
|
+
})
|
|
255
|
+
const resultPromise = Promise.race([stream.result(), deadlinePromise])
|
|
256
|
+
.finally(() => clearTimeout(timer))
|
|
257
|
+
resultPromise.catch(() => {})
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
result: () => resultPromise,
|
|
261
|
+
[Symbol.asyncIterator]() {
|
|
262
|
+
const iterator = stream[Symbol.asyncIterator]()
|
|
263
|
+
return {
|
|
264
|
+
next: () => Promise.race([iterator.next(), deadlinePromise]),
|
|
265
|
+
return: async () => {
|
|
266
|
+
clearTimeout(timer)
|
|
267
|
+
if (typeof iterator.return === 'function') return iterator.return()
|
|
268
|
+
return { value: undefined, done: true }
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
}
|
|
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
|
+
|
|
209
332
|
export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
|
|
210
333
|
const providerOptions = withDefaultAiProviderOptions(options)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
api: model?.api,
|
|
219
|
-
model: model?.id,
|
|
334
|
+
const deadlineMs = Number.isFinite(providerOptions.deadlineMs)
|
|
335
|
+
? Math.max(1, providerOptions.deadlineMs)
|
|
336
|
+
: DEFAULT_AI_STREAM_DEADLINE_MS
|
|
337
|
+
const deadlineController = new AbortController()
|
|
338
|
+
const effectiveOptions = {
|
|
339
|
+
...providerOptions,
|
|
340
|
+
signal: combineAbortSignals(providerOptions.signal, deadlineController.signal),
|
|
220
341
|
}
|
|
342
|
+
delete effectiveOptions.deadlineMs
|
|
343
|
+
|
|
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)
|
|
221
364
|
|
|
222
|
-
return
|
|
365
|
+
return wrapStreamWithDeadline(stream, deadlineController, deadlineMs)
|
|
223
366
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export const DEFAULT_AI_MAX_RETRIES = 3
|
|
2
|
+
export const DEFAULT_AI_HTTP_TIMEOUT_MS = 2 * 60 * 1000
|
|
3
|
+
export const DEFAULT_AI_STREAM_DEADLINE_MS = 5 * 60 * 1000
|
|
2
4
|
|
|
3
5
|
export function withDefaultAiProviderOptions(options = {}) {
|
|
4
6
|
return {
|
|
5
7
|
maxRetries: DEFAULT_AI_MAX_RETRIES,
|
|
8
|
+
timeoutMs: DEFAULT_AI_HTTP_TIMEOUT_MS,
|
|
6
9
|
...options,
|
|
7
10
|
}
|
|
8
11
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
function safeChannelId(channelId) {
|
|
4
|
+
const safeId = String(channelId || '')
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
7
|
+
.replace(/^-+|-+$/g, '')
|
|
8
|
+
return safeId || 'channel'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function channelLogsDirectory(logsDir, channelId) {
|
|
12
|
+
return path.join(path.resolve(logsDir), 'channels', safeChannelId(channelId))
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const CHANNEL_EVENTS_URL_ENV = 'QUICKFORGE_CHANNEL_EVENTS_URL'
|
|
2
|
+
const CHANNEL_EVENT_ACTION = 'channel-event'
|
|
3
|
+
const RELAY_TIMEOUT_MS = 3000
|
|
4
|
+
|
|
5
|
+
export async function publishChannelSessionChanged(event) {
|
|
6
|
+
const url = process.env[CHANNEL_EVENTS_URL_ENV]
|
|
7
|
+
if (!url) return false
|
|
8
|
+
|
|
9
|
+
const controller = new AbortController()
|
|
10
|
+
const timer = setTimeout(() => controller.abort(), RELAY_TIMEOUT_MS)
|
|
11
|
+
timer.unref?.()
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await fetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'content-type': 'application/json',
|
|
18
|
+
'x-quickforge-action': CHANNEL_EVENT_ACTION,
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
...event,
|
|
22
|
+
type: 'sessions-changed',
|
|
23
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
24
|
+
}),
|
|
25
|
+
signal: controller.signal,
|
|
26
|
+
})
|
|
27
|
+
return response.ok
|
|
28
|
+
} catch {
|
|
29
|
+
return false
|
|
30
|
+
} finally {
|
|
31
|
+
clearTimeout(timer)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-control-regex -- ANSI escape matching intentionally includes ESC. */
|
|
2
2
|
import { spawn } from 'node:child_process'
|
|
3
|
+
import { promises as fs } from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
3
5
|
import { EventEmitter } from 'node:events'
|
|
4
6
|
import { setTimeout as delay } from 'node:timers/promises'
|
|
7
|
+
import { channelLogsDirectory } from './channel-logs.mjs'
|
|
8
|
+
import { logger } from '../utils/logger.mjs'
|
|
5
9
|
|
|
6
10
|
const DEFAULT_LOG_LIMIT = 300
|
|
7
11
|
const STOP_TIMEOUT_MS = 5000
|
|
@@ -55,6 +59,9 @@ export class ProcessChannelProvider extends EventEmitter {
|
|
|
55
59
|
super()
|
|
56
60
|
this.definition = definition
|
|
57
61
|
this.logLimit = options.logLimit || DEFAULT_LOG_LIMIT
|
|
62
|
+
this.logsDir = options.logsDir
|
|
63
|
+
this.logWriteErrorReported = false
|
|
64
|
+
this.logWritePromise = Promise.resolve()
|
|
58
65
|
this.status = 'stopped'
|
|
59
66
|
this.process = null
|
|
60
67
|
this.pid = null
|
|
@@ -118,6 +125,27 @@ export class ProcessChannelProvider extends EventEmitter {
|
|
|
118
125
|
this.emitEvent('status', { status, snapshot: this.snapshot() })
|
|
119
126
|
}
|
|
120
127
|
|
|
128
|
+
persistLog(entry) {
|
|
129
|
+
if (!this.logsDir) return
|
|
130
|
+
const directory = channelLogsDirectory(this.logsDir, this.definition.id)
|
|
131
|
+
const filePath = path.join(directory, `channel-${entry.time.slice(0, 10)}.log`)
|
|
132
|
+
const line = `${JSON.stringify({ time: entry.time, stream: entry.stream, text: entry.text })}\n`
|
|
133
|
+
this.logWritePromise = this.logWritePromise
|
|
134
|
+
.then(() => fs.mkdir(directory, { recursive: true }))
|
|
135
|
+
.then(() => fs.appendFile(filePath, line, 'utf8'))
|
|
136
|
+
.then(() => {
|
|
137
|
+
this.logWriteErrorReported = false
|
|
138
|
+
})
|
|
139
|
+
.catch((error) => {
|
|
140
|
+
if (this.logWriteErrorReported) return
|
|
141
|
+
this.logWriteErrorReported = true
|
|
142
|
+
logger.warn('Failed to persist channel log.', {
|
|
143
|
+
channelId: this.definition.id,
|
|
144
|
+
error: error instanceof Error ? error.message : String(error),
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
addLog(stream, text) {
|
|
122
150
|
if (!text) return
|
|
123
151
|
const entry = {
|
|
@@ -131,6 +159,7 @@ export class ProcessChannelProvider extends EventEmitter {
|
|
|
131
159
|
this.logs.splice(0, this.logs.length - this.logLimit)
|
|
132
160
|
}
|
|
133
161
|
this.emitEvent('log', { log: entry })
|
|
162
|
+
this.persistLog(entry)
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
addProcessText(stream, text) {
|
|
@@ -4,6 +4,7 @@ import { promises as fs } from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { ProcessChannelProvider } from '../process-channel.mjs'
|
|
6
6
|
import { getDefaultWorkspaceRoot, readProjectConfig } from '../../project-config.mjs'
|
|
7
|
+
import { createNodeProcessEnv } from '../../utils/process-env.mjs'
|
|
7
8
|
|
|
8
9
|
const WEIXIN_ACP_PACKAGE = 'weixin-acp'
|
|
9
10
|
const MIN_NODE_MAJOR = 22
|
|
@@ -134,7 +135,7 @@ function runCommand(command, args, options = {}) {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
export class WechatChannelProvider extends ProcessChannelProvider {
|
|
137
|
-
constructor({ projectRoot }) {
|
|
138
|
+
constructor({ projectRoot, channelEventsUrl, logsDir }) {
|
|
138
139
|
super({
|
|
139
140
|
id: 'wechat',
|
|
140
141
|
name: '微信',
|
|
@@ -149,8 +150,9 @@ export class WechatChannelProvider extends ProcessChannelProvider {
|
|
|
149
150
|
{ id: 'relogin', label: '重新登录' },
|
|
150
151
|
],
|
|
151
152
|
requirements: [`Node.js >= ${MIN_NODE_MAJOR}`, 'npm/npx 可用', '首次启动需要微信扫码登录'],
|
|
152
|
-
})
|
|
153
|
+
}, { logsDir })
|
|
153
154
|
this.projectRoot = projectRoot
|
|
155
|
+
this.channelEventsUrl = channelEventsUrl
|
|
154
156
|
this.launchWorkspace = null
|
|
155
157
|
}
|
|
156
158
|
|
|
@@ -179,7 +181,11 @@ export class WechatChannelProvider extends ProcessChannelProvider {
|
|
|
179
181
|
command: npxCommand(),
|
|
180
182
|
args: ['-y', WEIXIN_ACP_PACKAGE, 'start', '--', acpCommand, ...acpArgs],
|
|
181
183
|
cwd: this.launchWorkspace?.path || getDefaultWorkspaceRoot() || this.projectRoot,
|
|
182
|
-
env: process.env,
|
|
184
|
+
env: createNodeProcessEnv(process.env, {
|
|
185
|
+
QUICKFORGE_ACP_CHANNEL_ID: this.definition.id,
|
|
186
|
+
QUICKFORGE_ACP_CHANNEL_NAME: this.definition.name,
|
|
187
|
+
...(this.channelEventsUrl ? { QUICKFORGE_CHANNEL_EVENTS_URL: this.channelEventsUrl } : {}),
|
|
188
|
+
}),
|
|
183
189
|
shell: shouldUseShellForNpx(),
|
|
184
190
|
}
|
|
185
191
|
}
|
|
@@ -16,7 +16,11 @@ function attachProvider(provider) {
|
|
|
16
16
|
export function initializeChannels(options = {}) {
|
|
17
17
|
if (initialized) return
|
|
18
18
|
initialized = true
|
|
19
|
-
attachProvider(createWechatChannelProvider({
|
|
19
|
+
attachProvider(createWechatChannelProvider({
|
|
20
|
+
projectRoot: options.projectRoot,
|
|
21
|
+
channelEventsUrl: options.channelEventsUrl,
|
|
22
|
+
logsDir: options.logsDir,
|
|
23
|
+
}))
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
function requireProvider(id) {
|
|
@@ -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
|
+
}
|