@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
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { chmodSync, existsSync, promises as fs, statSync } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { dataDir } from '../storage.mjs'
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
const __dirname = path.dirname(__filename)
|
|
9
|
+
const projectRoot = path.resolve(__dirname, '../..')
|
|
10
|
+
const RESTART_DELAYS_MS = [1_000, 5_000, 30_000]
|
|
11
|
+
const MAX_CONSECUTIVE_RESTARTS = 5
|
|
12
|
+
const STABLE_RUN_MS = 60_000
|
|
13
|
+
const VERSION_TIMEOUT_MS = 5_000
|
|
14
|
+
const STOP_TIMEOUT_MS = 3_000
|
|
15
|
+
|
|
16
|
+
let child = null
|
|
17
|
+
let childStartedAt = 0
|
|
18
|
+
let lockPath = null
|
|
19
|
+
let lockOwnerPid = null
|
|
20
|
+
let lockCleanupPromise = null
|
|
21
|
+
let restartTimer = null
|
|
22
|
+
let restartCount = 0
|
|
23
|
+
let stopRequested = false
|
|
24
|
+
let startPromise = null
|
|
25
|
+
let stopPromise = null
|
|
26
|
+
let lifecycleGeneration = 0
|
|
27
|
+
let versionCheckChild = null
|
|
28
|
+
let launchOptions = null
|
|
29
|
+
let status = createStatus()
|
|
30
|
+
|
|
31
|
+
function createStatus(overrides = {}) {
|
|
32
|
+
return {
|
|
33
|
+
enabled: process.env.QUICKFORGE_QF_AGENT_ENABLED !== '0',
|
|
34
|
+
status: 'stopped',
|
|
35
|
+
serverUrl: null,
|
|
36
|
+
pid: null,
|
|
37
|
+
verificationUriComplete: null,
|
|
38
|
+
error: null,
|
|
39
|
+
updatedAt: new Date().toISOString(),
|
|
40
|
+
...overrides,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setStatus(next) {
|
|
45
|
+
status = createStatus({ ...status, ...next, updatedAt: new Date().toISOString() })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function nodePlatformTarget(platform = process.platform) {
|
|
49
|
+
if (platform === 'win32') return 'windows'
|
|
50
|
+
return platform
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function nodeArchTarget(arch = process.arch) {
|
|
54
|
+
if (arch === 'x64') return 'amd64'
|
|
55
|
+
if (arch === 'ia32') return '386'
|
|
56
|
+
return arch
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function runtimeAssetName(platform = process.platform) {
|
|
60
|
+
return platform === 'win32' ? 'qf-agent.exe' : 'qf-agent'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isDevelopmentRuntime(env = process.env) {
|
|
64
|
+
return process.argv.includes('--dev')
|
|
65
|
+
|| env.NODE_ENV === 'development'
|
|
66
|
+
|| env.QUICKFORGE_QF_AGENT_DEV_FALLBACK === '1'
|
|
67
|
+
|| process.defaultApp === true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveQfAgentExecutable({
|
|
71
|
+
env = process.env,
|
|
72
|
+
platform = process.platform,
|
|
73
|
+
arch = process.arch,
|
|
74
|
+
root = projectRoot,
|
|
75
|
+
development = isDevelopmentRuntime(env),
|
|
76
|
+
pathExists = existsSync,
|
|
77
|
+
} = {}) {
|
|
78
|
+
const name = runtimeAssetName(platform)
|
|
79
|
+
const candidates = []
|
|
80
|
+
if (env.QUICKFORGE_QF_AGENT_PATH) candidates.push(path.resolve(env.QUICKFORGE_QF_AGENT_PATH))
|
|
81
|
+
candidates.push(path.join(root, 'runtime-assets', 'agent', `${platform}-${arch}`, name))
|
|
82
|
+
if (development && platform === 'win32') {
|
|
83
|
+
candidates.push(path.resolve(root, '..', 'quickforge-cloud', 'bin', 'agent.exe'))
|
|
84
|
+
}
|
|
85
|
+
return candidates.find((candidate) => pathExists(candidate)) || null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function ensureAgentExecutable(executable, platform = process.platform) {
|
|
89
|
+
if (platform === 'win32') return
|
|
90
|
+
try {
|
|
91
|
+
if (!(statSync(executable).mode & 0o111)) {
|
|
92
|
+
chmodSync(executable, 0o755)
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Best effort: spawn will surface real permission errors through status.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function identityDirectory(serverUrl) {
|
|
100
|
+
if (process.env.QUICKFORGE_QF_AGENT_IDENTITY_DIR) {
|
|
101
|
+
return path.resolve(process.env.QUICKFORGE_QF_AGENT_IDENTITY_DIR)
|
|
102
|
+
}
|
|
103
|
+
const runtimeKind = String(process.env.QUICKFORGE_RUNTIME_KIND || 'server').trim() || 'server'
|
|
104
|
+
const safeRuntimeKind = runtimeKind.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
105
|
+
const parsed = new URL(serverUrl)
|
|
106
|
+
const port = parsed.port || (parsed.protocol === 'https:' ? '443' : '80')
|
|
107
|
+
return path.join(dataDir, 'remote-agent', `${safeRuntimeKind}-${port}`)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isProcessAlive(pid) {
|
|
111
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
112
|
+
try {
|
|
113
|
+
process.kill(pid, 0)
|
|
114
|
+
return true
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return error?.code === 'EPERM'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function readLock(file) {
|
|
121
|
+
try {
|
|
122
|
+
const value = JSON.parse(await fs.readFile(file, 'utf8'))
|
|
123
|
+
return value && typeof value === 'object' ? value : null
|
|
124
|
+
} catch {
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function acquireLock(identityDir, ownerPid, serverUrl) {
|
|
130
|
+
await fs.mkdir(path.dirname(identityDir), { recursive: true })
|
|
131
|
+
const file = `${identityDir}.lock.json`
|
|
132
|
+
|
|
133
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
134
|
+
try {
|
|
135
|
+
const handle = await fs.open(file, 'wx', 0o600)
|
|
136
|
+
try {
|
|
137
|
+
await handle.writeFile(`${JSON.stringify({ ownerPid, agentPid: null, serverUrl })}\n`, 'utf8')
|
|
138
|
+
} finally {
|
|
139
|
+
await handle.close()
|
|
140
|
+
}
|
|
141
|
+
lockPath = file
|
|
142
|
+
lockOwnerPid = ownerPid
|
|
143
|
+
return { acquired: true }
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error?.code !== 'EEXIST') throw error
|
|
146
|
+
const existing = await readLock(file)
|
|
147
|
+
if (existing && isProcessAlive(Number(existing.ownerPid)) && Number(existing.ownerPid) !== ownerPid) {
|
|
148
|
+
return { acquired: false, conflict: true }
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
await fs.unlink(file)
|
|
152
|
+
} catch (unlinkError) {
|
|
153
|
+
if (unlinkError?.code !== 'ENOENT') throw unlinkError
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { acquired: false, conflict: true }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function updateOwnLock(agentPid, serverUrl) {
|
|
161
|
+
if (!lockPath) return
|
|
162
|
+
const temporary = `${lockPath}.${process.pid}.${Date.now()}.tmp`
|
|
163
|
+
try {
|
|
164
|
+
await fs.writeFile(temporary, `${JSON.stringify({ ownerPid: lockOwnerPid, agentPid, serverUrl })}\n`, { encoding: 'utf8', mode: 0o600 })
|
|
165
|
+
await fs.rename(temporary, lockPath)
|
|
166
|
+
} catch (error) {
|
|
167
|
+
await fs.unlink(temporary).catch(() => {})
|
|
168
|
+
throw error
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function clearOwnLock() {
|
|
173
|
+
if (lockCleanupPromise) return lockCleanupPromise
|
|
174
|
+
const file = lockPath
|
|
175
|
+
const ownerPid = lockOwnerPid
|
|
176
|
+
lockPath = null
|
|
177
|
+
lockOwnerPid = null
|
|
178
|
+
if (!file) return
|
|
179
|
+
|
|
180
|
+
const pending = (async () => {
|
|
181
|
+
const existing = await readLock(file)
|
|
182
|
+
if (existing && Number(existing.ownerPid) !== ownerPid) return
|
|
183
|
+
try {
|
|
184
|
+
await fs.unlink(file)
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error?.code !== 'ENOENT') throw error
|
|
187
|
+
}
|
|
188
|
+
})().finally(() => {
|
|
189
|
+
if (lockCleanupPromise === pending) lockCleanupPromise = null
|
|
190
|
+
})
|
|
191
|
+
lockCleanupPromise = pending
|
|
192
|
+
return pending
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isCurrentLifecycle(generation) {
|
|
196
|
+
return !stopRequested && generation === lifecycleGeneration && launchOptions !== null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function terminateChild(activeChild) {
|
|
200
|
+
if (!activeChild) return
|
|
201
|
+
const exited = new Promise((resolve) => activeChild.once('exit', resolve))
|
|
202
|
+
try { activeChild.kill('SIGTERM') } catch { /* best effort */ }
|
|
203
|
+
await Promise.race([
|
|
204
|
+
exited,
|
|
205
|
+
new Promise((resolve) => setTimeout(resolve, STOP_TIMEOUT_MS)),
|
|
206
|
+
])
|
|
207
|
+
if (process.platform === 'win32' && isProcessAlive(activeChild.pid)) {
|
|
208
|
+
await new Promise((resolve) => {
|
|
209
|
+
const killer = spawn('taskkill', ['/PID', String(activeChild.pid), '/T', '/F'], {
|
|
210
|
+
windowsHide: true,
|
|
211
|
+
shell: false,
|
|
212
|
+
stdio: 'ignore',
|
|
213
|
+
})
|
|
214
|
+
killer.once('error', resolve)
|
|
215
|
+
killer.once('exit', resolve)
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function safeErrorMessage(value) {
|
|
221
|
+
const text = String(value || '').trim()
|
|
222
|
+
if (!text) return null
|
|
223
|
+
return text
|
|
224
|
+
.replace(/\bBearer\s+\S+/gi, 'Bearer [redacted]')
|
|
225
|
+
.replace(/\b(token|secret|password|api[_-]?key|private[_-]?key)\s*[:=]\s*\S+/gi, '$1=[redacted]')
|
|
226
|
+
.replace(/https?:\/\/\S+/gi, '[url]')
|
|
227
|
+
.replace(/[A-Za-z]:\\[^\s"']+/g, '[path]')
|
|
228
|
+
.replace(/\/(?:[^\s/]+\/)+[^\s"']*/g, '[path]')
|
|
229
|
+
.slice(0, 500)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function safeVerificationUrl(value) {
|
|
233
|
+
if (typeof value !== 'string' || !value.trim()) return null
|
|
234
|
+
try {
|
|
235
|
+
const parsed = new URL(value)
|
|
236
|
+
return ['http:', 'https:'].includes(parsed.protocol) ? parsed.href : null
|
|
237
|
+
} catch {
|
|
238
|
+
return null
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function parseQfAgentLogLine(line) {
|
|
243
|
+
const text = String(line || '').trim()
|
|
244
|
+
if (!text) return null
|
|
245
|
+
let record
|
|
246
|
+
try {
|
|
247
|
+
record = JSON.parse(text)
|
|
248
|
+
} catch {
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
if (!record || typeof record !== 'object') return null
|
|
252
|
+
|
|
253
|
+
const message = typeof record.msg === 'string' ? record.msg : typeof record.message === 'string' ? record.message : ''
|
|
254
|
+
const result = {}
|
|
255
|
+
if (message.includes('等待设备授权')) {
|
|
256
|
+
result.status = 'authorizing'
|
|
257
|
+
result.verificationUriComplete = safeVerificationUrl(record.verificationUriComplete)
|
|
258
|
+
} else if (message.includes('信令已连接')) {
|
|
259
|
+
result.status = 'running'
|
|
260
|
+
result.verificationUriComplete = null
|
|
261
|
+
} else if (message.includes('信令连接中断') || message.includes('重连')) {
|
|
262
|
+
result.status = 'starting'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const level = String(record.level || record.severity || '').toLowerCase()
|
|
266
|
+
if (['error', 'fatal', 'panic'].includes(level)) result.error = safeErrorMessage(message) || 'qf-agent reported an error.'
|
|
267
|
+
return Object.keys(result).length ? result : null
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function consumeLines(stream, onLine) {
|
|
271
|
+
let buffered = ''
|
|
272
|
+
stream?.setEncoding('utf8')
|
|
273
|
+
stream?.on('data', (chunk) => {
|
|
274
|
+
buffered += chunk
|
|
275
|
+
let newline = buffered.indexOf('\n')
|
|
276
|
+
while (newline >= 0) {
|
|
277
|
+
onLine(buffered.slice(0, newline).replace(/\r$/, ''))
|
|
278
|
+
buffered = buffered.slice(newline + 1)
|
|
279
|
+
newline = buffered.indexOf('\n')
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
stream?.on('end', () => {
|
|
283
|
+
if (buffered) onLine(buffered.replace(/\r$/, ''))
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function parseVersionOutput(output) {
|
|
288
|
+
const lines = String(output || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean)
|
|
289
|
+
for (const line of lines.reverse()) {
|
|
290
|
+
try {
|
|
291
|
+
const value = JSON.parse(line)
|
|
292
|
+
if (value && typeof value === 'object') return value
|
|
293
|
+
} catch {
|
|
294
|
+
// Ignore non-JSON diagnostic lines.
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function validateQfAgentVersion(version, { platform = process.platform, arch = process.arch } = {}) {
|
|
301
|
+
const expectedTarget = `${nodePlatformTarget(platform)}-${nodeArchTarget(arch)}`
|
|
302
|
+
const actualTarget = String(version?.target || '').trim().toLowerCase().replace(/[/_]/g, '-')
|
|
303
|
+
if (Number(version?.protocolVersion) !== 1) {
|
|
304
|
+
throw new Error('qf-agent protocol version is not supported.')
|
|
305
|
+
}
|
|
306
|
+
if (actualTarget !== expectedTarget) {
|
|
307
|
+
throw new Error(`qf-agent target is incompatible with ${expectedTarget}.`)
|
|
308
|
+
}
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function checkVersion(executable) {
|
|
313
|
+
const result = await new Promise((resolve, reject) => {
|
|
314
|
+
const versionChild = spawn(executable, ['--version'], {
|
|
315
|
+
windowsHide: true,
|
|
316
|
+
shell: false,
|
|
317
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
318
|
+
})
|
|
319
|
+
versionCheckChild = versionChild
|
|
320
|
+
let stdout = ''
|
|
321
|
+
let stderr = ''
|
|
322
|
+
const finish = (callback, value) => {
|
|
323
|
+
if (versionCheckChild === versionChild) versionCheckChild = null
|
|
324
|
+
clearTimeout(timer)
|
|
325
|
+
callback(value)
|
|
326
|
+
}
|
|
327
|
+
const timer = setTimeout(() => {
|
|
328
|
+
try { versionChild.kill('SIGKILL') } catch { /* best effort */ }
|
|
329
|
+
finish(reject, new Error('qf-agent version check timed out.'))
|
|
330
|
+
}, VERSION_TIMEOUT_MS)
|
|
331
|
+
timer.unref?.()
|
|
332
|
+
versionChild.stdout?.setEncoding('utf8')
|
|
333
|
+
versionChild.stderr?.setEncoding('utf8')
|
|
334
|
+
versionChild.stdout?.on('data', (chunk) => { stdout += chunk })
|
|
335
|
+
versionChild.stderr?.on('data', (chunk) => { stderr += chunk })
|
|
336
|
+
versionChild.once('error', (error) => finish(reject, error))
|
|
337
|
+
versionChild.once('exit', (code) => {
|
|
338
|
+
if (code !== 0) {
|
|
339
|
+
finish(reject, new Error(safeErrorMessage(stderr) || `qf-agent version check exited with code ${code}.`))
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
finish(resolve, stdout)
|
|
343
|
+
})
|
|
344
|
+
})
|
|
345
|
+
const version = parseVersionOutput(result)
|
|
346
|
+
if (!version) throw new Error('qf-agent returned an invalid version response.')
|
|
347
|
+
validateQfAgentVersion(version)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function scheduleRestart(generation) {
|
|
351
|
+
if (!isCurrentLifecycle(generation) || restartTimer) return
|
|
352
|
+
if (restartCount >= MAX_CONSECUTIVE_RESTARTS) {
|
|
353
|
+
setStatus({ status: 'error', pid: null, error: status.error || 'qf-agent stopped after repeated failures.' })
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
const delay = RESTART_DELAYS_MS[Math.min(restartCount, RESTART_DELAYS_MS.length - 1)]
|
|
357
|
+
restartCount += 1
|
|
358
|
+
setStatus({ status: 'starting', pid: null })
|
|
359
|
+
restartTimer = setTimeout(() => {
|
|
360
|
+
restartTimer = null
|
|
361
|
+
if (!isCurrentLifecycle(generation)) return
|
|
362
|
+
void beginLaunch(generation).catch((error) => {
|
|
363
|
+
if (!isCurrentLifecycle(generation)) return
|
|
364
|
+
setStatus({ status: 'error', pid: null, error: safeErrorMessage(error?.message) || 'qf-agent failed to restart.' })
|
|
365
|
+
scheduleRestart(generation)
|
|
366
|
+
})
|
|
367
|
+
}, delay)
|
|
368
|
+
restartTimer.unref?.()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function launchQfAgent(generation) {
|
|
372
|
+
const options = launchOptions
|
|
373
|
+
if (!options || !isCurrentLifecycle(generation)) return getQfAgentStatus()
|
|
374
|
+
const { serverUrl, ownerPid, cloudUrl } = options
|
|
375
|
+
const executable = resolveQfAgentExecutable()
|
|
376
|
+
if (!executable) {
|
|
377
|
+
if (isCurrentLifecycle(generation)) {
|
|
378
|
+
setStatus({ status: 'unavailable', serverUrl, pid: null, verificationUriComplete: null, error: null })
|
|
379
|
+
}
|
|
380
|
+
return getQfAgentStatus()
|
|
381
|
+
}
|
|
382
|
+
ensureAgentExecutable(executable)
|
|
383
|
+
|
|
384
|
+
setStatus({ status: 'starting', serverUrl, pid: null, verificationUriComplete: null })
|
|
385
|
+
try {
|
|
386
|
+
await checkVersion(executable)
|
|
387
|
+
} catch (error) {
|
|
388
|
+
if (isCurrentLifecycle(generation)) {
|
|
389
|
+
setStatus({ status: 'error', pid: null, error: safeErrorMessage(error?.message) || 'qf-agent version check failed.' })
|
|
390
|
+
}
|
|
391
|
+
return getQfAgentStatus()
|
|
392
|
+
}
|
|
393
|
+
if (!isCurrentLifecycle(generation)) return getQfAgentStatus()
|
|
394
|
+
|
|
395
|
+
const identityDir = identityDirectory(serverUrl)
|
|
396
|
+
const lock = await acquireLock(identityDir, ownerPid, serverUrl)
|
|
397
|
+
if (!lock.acquired) {
|
|
398
|
+
if (isCurrentLifecycle(generation)) setStatus({ status: 'conflict', pid: null, error: null })
|
|
399
|
+
return getQfAgentStatus()
|
|
400
|
+
}
|
|
401
|
+
if (!isCurrentLifecycle(generation)) {
|
|
402
|
+
await clearOwnLock().catch(() => {})
|
|
403
|
+
return getQfAgentStatus()
|
|
404
|
+
}
|
|
405
|
+
await fs.mkdir(identityDir, { recursive: true })
|
|
406
|
+
if (!isCurrentLifecycle(generation)) {
|
|
407
|
+
await clearOwnLock().catch(() => {})
|
|
408
|
+
return getQfAgentStatus()
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let spawned
|
|
412
|
+
try {
|
|
413
|
+
spawned = spawn(executable, [], {
|
|
414
|
+
windowsHide: true,
|
|
415
|
+
shell: false,
|
|
416
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
417
|
+
env: {
|
|
418
|
+
...process.env,
|
|
419
|
+
QF_CLOUD_URL: cloudUrl,
|
|
420
|
+
QF_CLOUD_AGENT_DATA_DIR: identityDir,
|
|
421
|
+
QF_CLOUD_AGENT_QF_URL: serverUrl,
|
|
422
|
+
QF_CLOUD_AGENT_OWNER_PID: String(ownerPid),
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
await new Promise((resolve, reject) => {
|
|
426
|
+
spawned.once('spawn', resolve)
|
|
427
|
+
spawned.once('error', reject)
|
|
428
|
+
})
|
|
429
|
+
} catch (error) {
|
|
430
|
+
await clearOwnLock().catch(() => {})
|
|
431
|
+
throw error
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
child = spawned
|
|
435
|
+
childStartedAt = Date.now()
|
|
436
|
+
spawned.once('exit', () => {
|
|
437
|
+
if (child !== spawned) return
|
|
438
|
+
const stableRun = Date.now() - childStartedAt >= STABLE_RUN_MS
|
|
439
|
+
child = null
|
|
440
|
+
childStartedAt = 0
|
|
441
|
+
if (stableRun) restartCount = 0
|
|
442
|
+
void clearOwnLock().then(() => {
|
|
443
|
+
if (!isCurrentLifecycle(generation)) {
|
|
444
|
+
setStatus({ status: 'stopped', pid: null, verificationUriComplete: null })
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
scheduleRestart(generation)
|
|
448
|
+
}).catch(() => {
|
|
449
|
+
if (isCurrentLifecycle(generation)) scheduleRestart(generation)
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
const onLine = (line) => {
|
|
454
|
+
if (!isCurrentLifecycle(generation) || child !== spawned) return
|
|
455
|
+
const update = parseQfAgentLogLine(line)
|
|
456
|
+
if (update) setStatus(update)
|
|
457
|
+
}
|
|
458
|
+
consumeLines(spawned.stdout, onLine)
|
|
459
|
+
consumeLines(spawned.stderr, onLine)
|
|
460
|
+
|
|
461
|
+
try {
|
|
462
|
+
await updateOwnLock(spawned.pid, serverUrl)
|
|
463
|
+
} catch (error) {
|
|
464
|
+
if (child === spawned) child = null
|
|
465
|
+
await terminateChild(spawned)
|
|
466
|
+
await clearOwnLock().catch(() => {})
|
|
467
|
+
throw error
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!isCurrentLifecycle(generation) || child !== spawned) {
|
|
471
|
+
if (child === spawned) child = null
|
|
472
|
+
await terminateChild(spawned)
|
|
473
|
+
await clearOwnLock().catch(() => {})
|
|
474
|
+
return getQfAgentStatus()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
setStatus({ status: 'starting', pid: spawned.pid })
|
|
478
|
+
return getQfAgentStatus()
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function beginLaunch(generation) {
|
|
482
|
+
if (startPromise) return startPromise
|
|
483
|
+
const pending = launchQfAgent(generation).finally(() => {
|
|
484
|
+
if (startPromise === pending) startPromise = null
|
|
485
|
+
})
|
|
486
|
+
startPromise = pending
|
|
487
|
+
return pending
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export async function startQfAgent({ serverUrl, ownerPid, cloudUrl }) {
|
|
491
|
+
if (process.env.QUICKFORGE_QF_AGENT_ENABLED === '0') {
|
|
492
|
+
setStatus({ enabled: false, status: 'disabled', serverUrl: null, pid: null, verificationUriComplete: null, error: null })
|
|
493
|
+
return getQfAgentStatus()
|
|
494
|
+
}
|
|
495
|
+
if (stopPromise) await stopPromise
|
|
496
|
+
if (child || restartTimer) return getQfAgentStatus()
|
|
497
|
+
if (startPromise) return startPromise
|
|
498
|
+
|
|
499
|
+
const options = {
|
|
500
|
+
serverUrl: new URL(serverUrl).href,
|
|
501
|
+
ownerPid: Number(ownerPid),
|
|
502
|
+
cloudUrl: new URL(cloudUrl).href,
|
|
503
|
+
}
|
|
504
|
+
if (!Number.isInteger(options.ownerPid) || options.ownerPid <= 0) {
|
|
505
|
+
setStatus({ status: 'error', error: 'qf-agent owner PID is invalid.' })
|
|
506
|
+
return getQfAgentStatus()
|
|
507
|
+
}
|
|
508
|
+
lifecycleGeneration += 1
|
|
509
|
+
const generation = lifecycleGeneration
|
|
510
|
+
launchOptions = options
|
|
511
|
+
stopRequested = false
|
|
512
|
+
stopPromise = null
|
|
513
|
+
restartCount = 0
|
|
514
|
+
setStatus({ enabled: true, error: null })
|
|
515
|
+
try {
|
|
516
|
+
return await beginLaunch(generation)
|
|
517
|
+
} catch (error) {
|
|
518
|
+
if (isCurrentLifecycle(generation)) {
|
|
519
|
+
setStatus({ status: 'error', pid: null, error: safeErrorMessage(error?.message) || 'qf-agent failed to start.' })
|
|
520
|
+
}
|
|
521
|
+
return getQfAgentStatus()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export async function stopQfAgent({ disabled = false } = {}) {
|
|
526
|
+
if (stopPromise) {
|
|
527
|
+
const result = await stopPromise
|
|
528
|
+
if (disabled && result.status !== 'disabled') {
|
|
529
|
+
setStatus({ enabled: false, status: 'disabled', serverUrl: null, pid: null, verificationUriComplete: null, error: null })
|
|
530
|
+
return getQfAgentStatus()
|
|
531
|
+
}
|
|
532
|
+
return result
|
|
533
|
+
}
|
|
534
|
+
stopRequested = true
|
|
535
|
+
lifecycleGeneration += 1
|
|
536
|
+
launchOptions = null
|
|
537
|
+
if (restartTimer) {
|
|
538
|
+
clearTimeout(restartTimer)
|
|
539
|
+
restartTimer = null
|
|
540
|
+
}
|
|
541
|
+
const activeVersionCheck = versionCheckChild
|
|
542
|
+
if (activeVersionCheck) {
|
|
543
|
+
try { activeVersionCheck.kill('SIGKILL') } catch { /* best effort */ }
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
stopPromise = (async () => {
|
|
547
|
+
const pendingStart = startPromise
|
|
548
|
+
if (pendingStart) await pendingStart.catch(() => {})
|
|
549
|
+
const activeChild = child
|
|
550
|
+
if (activeChild) {
|
|
551
|
+
if (child === activeChild) child = null
|
|
552
|
+
await terminateChild(activeChild)
|
|
553
|
+
}
|
|
554
|
+
await clearOwnLock().catch(() => {})
|
|
555
|
+
setStatus({ enabled: disabled ? false : status.enabled, status: disabled ? 'disabled' : 'stopped', serverUrl: null, pid: null, verificationUriComplete: null, error: null })
|
|
556
|
+
return getQfAgentStatus()
|
|
557
|
+
})()
|
|
558
|
+
return stopPromise
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function getQfAgentStatus() {
|
|
562
|
+
return {
|
|
563
|
+
enabled: status.enabled,
|
|
564
|
+
status: status.status,
|
|
565
|
+
serverUrl: status.serverUrl,
|
|
566
|
+
pid: status.pid,
|
|
567
|
+
verificationUriComplete: status.verificationUriComplete,
|
|
568
|
+
error: status.error,
|
|
569
|
+
updatedAt: status.updatedAt,
|
|
570
|
+
}
|
|
571
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { CloudClient } from './client.mjs'
|
|
2
|
+
import { readCloudConfig, cloudEndpoint } from './config.mjs'
|
|
3
|
+
import { createCloudCredentialStore } from './credential-store.mjs'
|
|
4
|
+
import { CloudIdentityManager } from './identity.mjs'
|
|
5
|
+
import { ManagedCloudModels, isManagedCloudModel } from './models.mjs'
|
|
6
|
+
import { readCloudServiceConfig } from './service-config.mjs'
|
|
7
|
+
|
|
8
|
+
let singleton
|
|
9
|
+
let singletonKey
|
|
10
|
+
let singletonPromise
|
|
11
|
+
let runtimeOverride = false
|
|
12
|
+
let runtimeGeneration = 0
|
|
13
|
+
let runtimeFactory = createCloudRuntime
|
|
14
|
+
|
|
15
|
+
function runtimeKey(config, generation = runtimeGeneration) {
|
|
16
|
+
return `${generation}:${config?.enabled === true}:${config?.baseUrl?.href || ''}:${config?.timeoutMs || ''}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createCloudRuntime({ config = readCloudConfig(), store, client } = {}) {
|
|
20
|
+
const credentialStore = store || createCloudCredentialStore({ clientVersion: process.env.npm_package_version || 'unknown' })
|
|
21
|
+
if (!config?.baseUrl) return { enabled: false, config, store: credentialStore }
|
|
22
|
+
const cloudClient = client || new CloudClient(config)
|
|
23
|
+
const identity = new CloudIdentityManager({ client: cloudClient, store: credentialStore, serviceUrl: config.baseUrl.href })
|
|
24
|
+
const models = new ManagedCloudModels({ identity })
|
|
25
|
+
return { enabled: config.enabled === true, config, store: credentialStore, client: cloudClient, identity, models }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function resolvedRuntimeConfig() {
|
|
29
|
+
const service = await readCloudServiceConfig()
|
|
30
|
+
const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
|
|
31
|
+
return {
|
|
32
|
+
enabled: service.enabled === true && Boolean(service.baseUrl),
|
|
33
|
+
baseUrl: service.baseUrl,
|
|
34
|
+
cloudUrl: service.cloudUrl,
|
|
35
|
+
source: service.source,
|
|
36
|
+
saved: service.saved,
|
|
37
|
+
timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getCloudRuntime() {
|
|
42
|
+
if (runtimeOverride) return singleton
|
|
43
|
+
const generation = runtimeGeneration
|
|
44
|
+
const config = await resolvedRuntimeConfig()
|
|
45
|
+
if (runtimeOverride) return singleton
|
|
46
|
+
if (generation !== runtimeGeneration) return getCloudRuntime()
|
|
47
|
+
const key = runtimeKey(config, generation)
|
|
48
|
+
if (singleton && singletonKey === key) return singleton
|
|
49
|
+
if (singletonPromise && singletonKey === key) return singletonPromise
|
|
50
|
+
singletonKey = key
|
|
51
|
+
const promise = Promise.resolve(runtimeFactory({ config })).then((runtime) => {
|
|
52
|
+
if (
|
|
53
|
+
!runtimeOverride
|
|
54
|
+
&& generation === runtimeGeneration
|
|
55
|
+
&& singletonKey === key
|
|
56
|
+
&& singletonPromise === promise
|
|
57
|
+
) {
|
|
58
|
+
singleton = runtime
|
|
59
|
+
}
|
|
60
|
+
return runtime
|
|
61
|
+
}).finally(() => {
|
|
62
|
+
if (singletonPromise === promise) singletonPromise = undefined
|
|
63
|
+
})
|
|
64
|
+
singletonPromise = promise
|
|
65
|
+
return promise
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function invalidateCloudRuntime() {
|
|
69
|
+
runtimeGeneration++
|
|
70
|
+
runtimeOverride = false
|
|
71
|
+
singleton = undefined
|
|
72
|
+
singletonKey = undefined
|
|
73
|
+
singletonPromise = undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function resolveManagedCloudProvider(model, signal) {
|
|
77
|
+
if (!isManagedCloudModel(model)) return undefined
|
|
78
|
+
const runtime = await getCloudRuntime()
|
|
79
|
+
if (!runtime.enabled) {
|
|
80
|
+
const error = new Error(runtime?.config?.baseUrl && runtime?.config?.enabled !== true
|
|
81
|
+
? 'QuickForge Cloud is disabled.'
|
|
82
|
+
: 'QuickForge Cloud is not configured.')
|
|
83
|
+
error.code = runtime?.config?.baseUrl && runtime?.config?.enabled !== true ? 'cloud_disabled' : 'cloud_not_configured'
|
|
84
|
+
throw error
|
|
85
|
+
}
|
|
86
|
+
const resolved = await runtime.models.resolve(model, signal)
|
|
87
|
+
const accessToken = await runtime.identity.access({ signal })
|
|
88
|
+
return {
|
|
89
|
+
model: {
|
|
90
|
+
...resolved.publicModel,
|
|
91
|
+
id: resolved.catalogId,
|
|
92
|
+
name: resolved.publicModel.name || resolved.catalogId,
|
|
93
|
+
provider: 'quickforge-cloud',
|
|
94
|
+
api: 'openai-completions',
|
|
95
|
+
baseUrl: cloudEndpoint(runtime.config.baseUrl, 'v1/').href.replace(/\/$/, ''),
|
|
96
|
+
headers: undefined,
|
|
97
|
+
// The Cloud gateway implements the conservative OpenAI-compatible request
|
|
98
|
+
// shape. Disable newer OpenAI-only fields that strict gateways may reject.
|
|
99
|
+
compat: {
|
|
100
|
+
supportsStore: false,
|
|
101
|
+
supportsDeveloperRole: false,
|
|
102
|
+
supportsReasoningEffort: false,
|
|
103
|
+
supportsUsageInStreaming: false,
|
|
104
|
+
supportsStrictMode: false,
|
|
105
|
+
maxTokensField: 'max_tokens',
|
|
106
|
+
},
|
|
107
|
+
// The cloud catalog does not carry per-model USD prices (billing is done
|
|
108
|
+
// server-side in credits). pi-ai's calculateCost() reads model.cost.*
|
|
109
|
+
// unconditionally, so provide a zero cost table to avoid throwing when
|
|
110
|
+
// parsing streamed usage chunks.
|
|
111
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
112
|
+
},
|
|
113
|
+
apiKey: accessToken,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resetCloudRuntimeForTests() {
|
|
118
|
+
runtimeGeneration = 0
|
|
119
|
+
runtimeOverride = false
|
|
120
|
+
runtimeFactory = createCloudRuntime
|
|
121
|
+
singleton = undefined
|
|
122
|
+
singletonKey = undefined
|
|
123
|
+
singletonPromise = undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function setCloudRuntimeFactoryForTests(factory) {
|
|
127
|
+
runtimeFactory = factory
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function setCloudRuntimeForTests(runtime) {
|
|
131
|
+
runtimeOverride = true
|
|
132
|
+
singleton = runtime
|
|
133
|
+
singletonKey = runtimeKey(runtime?.config)
|
|
134
|
+
singletonPromise = undefined
|
|
135
|
+
}
|