@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.
Files changed (258) hide show
  1. package/dist/assets/AgentProfilesPage-Dl5Peqov.js +1 -0
  2. package/dist/assets/ChatPanelHost-D3987TUJ.js +307 -0
  3. package/dist/assets/CloudAccountSettingsPage-BG5Nn3bq.js +1 -0
  4. package/dist/assets/PluginsPage-BDKYfLna.js +1 -0
  5. package/dist/assets/ScheduledTasksPage--GAki3lB.js +2 -0
  6. package/dist/assets/SettingsWorkspacePage-DSy66Tuw.js +1696 -0
  7. package/dist/assets/ShareLinksSettingsPage-BQQMsvJo.js +1 -0
  8. package/dist/assets/SharedConversationPage-B9BUVCri.js +1 -0
  9. package/dist/assets/TerminalDock-CT1nYo7P.js +2 -0
  10. package/dist/assets/WorkspaceInspector-BnfDLf9a.js +36 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-Dy7veffk.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  12. package/dist/assets/{arc-Bo3p2lGy.js → arc-w21O0TLn.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-BiuaAOFa.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  16. package/dist/assets/{c4Diagram-LMCZKHZV-BTcGfhK2.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  17. package/dist/assets/channel-Duu1CuH9.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  19. package/dist/assets/{chunk-32BRIVSS-B9PuF_RF.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  20. package/dist/assets/{chunk-52WLFC77-3zGItfIM.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  21. package/dist/assets/{chunk-5FCAYU7R-DQ66qlmt.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  22. package/dist/assets/{chunk-5HE753X5-CLqTO-VF.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  23. package/dist/assets/{chunk-5JV3BV7I-Csxv1jr6.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  24. package/dist/assets/{chunk-5TONJI2A-BCQttpBr.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-DEWJGSTb.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  26. package/dist/assets/{chunk-7BUUIJ7U-DNorm9MM.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  27. package/dist/assets/{chunk-BIQX33UG-DgvY_BOT.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-DMvn9IPs.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  29. package/dist/assets/{chunk-CQNSW5MT-Dq2duUgr.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  30. package/dist/assets/{chunk-CYSBUYHQ-BovYyc4Y.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  31. package/dist/assets/{chunk-EMLP6XTP-BMDOpuYN.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  32. package/dist/assets/{chunk-EX3LRPZG-6eP2UPOf.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  33. package/dist/assets/{chunk-FWX5IMBZ-D4q-wTSy.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  34. package/dist/assets/{chunk-HOUHSVGY-XjIcuzH7.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  35. package/dist/assets/{chunk-ICXQ74PX-YBXbanq9.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  36. package/dist/assets/{chunk-JG7HCLWE-CcN3uNXF.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  37. package/dist/assets/{chunk-JWPE2WC7-Cwg6AUAt.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  38. package/dist/assets/{chunk-MOJQB5TN-NNspcGIU.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  39. package/dist/assets/{chunk-MOZMSUNE-pMJ_jMos.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-CJy-3Ldv.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  41. package/dist/assets/{chunk-OSBZ3O6U-D7cLHHOA.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  42. package/dist/assets/{chunk-PUDLZKDR-D5YBi1Pi.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  43. package/dist/assets/{chunk-Q4XR5HBZ-DYmqQdIf.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  44. package/dist/assets/{chunk-QBLGF6JB-Bfcx00X0.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  45. package/dist/assets/{chunk-R7FJI6CG-oXzadpZY.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  46. package/dist/assets/{chunk-RYQCIY6F-Dt3-8yUh.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  47. package/dist/assets/{chunk-U6XO7XAA-B55eSvpx.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  48. package/dist/assets/{chunk-V7JOEXUC-Btk4JwCt.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  49. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  50. package/dist/assets/{chunk-VR4S4FIN-DgaSiVOY.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  51. package/dist/assets/{chunk-WYO6CB5R-rRDZ_Tta.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  52. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  53. package/dist/assets/{chunk-YOTPTUD7-BnFxj5o4.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  54. package/dist/assets/{chunk-ZGVPDNZ5-CrnlPlmJ.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  55. package/dist/assets/{chunk-ZIRB5QZD-BYTiATIH.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  56. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  57. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  58. package/dist/assets/{cose-bilkent-JH36ORCC-BORKJRwF.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  59. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  60. package/dist/assets/{cynefinDiagram-TSTJHNR4-BXKeaSl8.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  61. package/dist/assets/{dagre-CrA0XVwJ.js → dagre-DmHb3Ctt.js} +1 -1
  62. package/dist/assets/{dagre-VKFMJZFB-ouq_lA1a.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BxR5kUiQ.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-DWonk1sL.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-fmV_wV5e.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-DvOSRRiV.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-Bp-xb3uS.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  68. package/dist/assets/{dist-DLKC_odd.js → dist-yDHbi5ux.js} +1 -1
  69. package/dist/assets/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  70. package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  71. package/dist/assets/{esm-oa69b5u4.js → esm-sdsK6gaB.js} +2 -2
  72. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  73. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  74. package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  75. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  76. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  77. package/dist/assets/icons-2D18YbSI.js +1 -0
  78. package/dist/assets/index-1ei6nhs-.css +3 -0
  79. package/dist/assets/index-BivLuZvX.js +65 -0
  80. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  81. package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  82. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  83. package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  84. package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  85. package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
  86. package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
  87. package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
  88. package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BEV4_TXC.js} +2 -2
  89. package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  90. package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  91. package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
  92. package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  93. package/dist/assets/{logger-8REuLPGK.js → model-display-label-C6-0eDxF.js} +1 -1
  94. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  95. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  96. package/dist/assets/{monaco-BVtCspYZ.js → monaco-DvXErxp2.js} +1 -1
  97. package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
  98. package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
  99. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  100. package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  101. package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
  102. package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  105. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  106. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  107. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  108. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  109. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  110. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  111. package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  112. package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-3UaA-C9W.js} +1 -1
  113. package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  114. package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  115. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  116. package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  117. package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BTJ7mzk_.js} +3 -3
  118. package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
  119. package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  120. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  121. package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  122. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  123. package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  124. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  125. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  126. package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  127. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  128. package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  129. package/dist/assets/{web-RgX7H-OH.js → web-B5qgZJRv.js} +1 -1
  130. package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  131. package/dist/index.html +11 -10
  132. package/dist/sw.js +17 -0
  133. package/package.json +4 -1
  134. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  135. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  136. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  137. package/runtime-assets/agent/darwin-arm64/qf-agent +0 -0
  138. package/runtime-assets/agent/darwin-x64/qf-agent +0 -0
  139. package/runtime-assets/agent/linux-arm64/qf-agent +0 -0
  140. package/runtime-assets/agent/linux-x64/qf-agent +0 -0
  141. package/runtime-assets/agent/win32-x64/qf-agent.exe +0 -0
  142. package/server/access-policy.mjs +11 -0
  143. package/server/acp/server.mjs +36 -26
  144. package/server/agent-manager.mjs +474 -71
  145. package/server/agent-profile-files.mjs +8 -4
  146. package/server/agent-profile-schema.mjs +47 -23
  147. package/server/agent-profiles.mjs +28 -9
  148. package/server/ai-http-logger.mjs +98 -12
  149. package/server/auto-compaction.mjs +35 -8
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +99 -0
  152. package/server/cloud/config.mjs +54 -0
  153. package/server/cloud/credential-store.mjs +200 -0
  154. package/server/cloud/identity.mjs +312 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/qf-agent-process.mjs +571 -0
  158. package/server/cloud/runtime.mjs +135 -0
  159. package/server/cloud/service-config.mjs +81 -0
  160. package/server/index.mjs +124 -48
  161. package/server/mcp/registry.mjs +8 -7
  162. package/server/message-converters.mjs +40 -19
  163. package/server/model-catalog.mjs +269 -0
  164. package/server/opencode-acp-agent.mjs +1151 -0
  165. package/server/plugins/registry.mjs +29 -5
  166. package/server/public-api.mjs +11 -5
  167. package/server/routes/agent-profiles.mjs +46 -9
  168. package/server/routes/agent.mjs +60 -11
  169. package/server/routes/backup.mjs +8 -1
  170. package/server/routes/cloud.mjs +246 -0
  171. package/server/routes/models.mjs +7 -1
  172. package/server/routes/scheduled-tasks.mjs +38 -8
  173. package/server/routes/shared-conversation.mjs +62 -58
  174. package/server/routes/shares.mjs +75 -1
  175. package/server/routes/storage.mjs +9 -1
  176. package/server/routes/system.mjs +7 -4
  177. package/server/routes/workspace.mjs +6 -4
  178. package/server/share-store.mjs +168 -13
  179. package/server/skills.mjs +30 -3
  180. package/server/storage.mjs +7 -1
  181. package/server/subagents.mjs +3 -3
  182. package/server/tools/definitions.mjs +1 -1
  183. package/server/tools/index.mjs +2 -2
  184. package/server/utils/network.mjs +9 -0
  185. package/server/utils/process-tree.mjs +78 -0
  186. package/server/utils/response.mjs +8 -1
  187. package/skills/skill-creator/LICENSE.txt +202 -0
  188. package/skills/skill-creator/SKILL.md +485 -0
  189. package/skills/skill-creator/UPSTREAM.md +6 -0
  190. package/skills/skill-creator/agents/analyzer.md +274 -0
  191. package/skills/skill-creator/agents/comparator.md +202 -0
  192. package/skills/skill-creator/agents/grader.md +223 -0
  193. package/skills/skill-creator/assets/eval_review.html +146 -0
  194. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  195. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  196. package/skills/skill-creator/references/schemas.md +430 -0
  197. package/skills/skill-creator/scripts/__init__.py +0 -0
  198. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  199. package/skills/skill-creator/scripts/generate_report.py +326 -0
  200. package/skills/skill-creator/scripts/improve_description.py +247 -0
  201. package/skills/skill-creator/scripts/package_skill.py +136 -0
  202. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  203. package/skills/skill-creator/scripts/run_eval.py +310 -0
  204. package/skills/skill-creator/scripts/run_loop.py +328 -0
  205. package/skills/skill-creator/scripts/utils.py +47 -0
  206. package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
  207. package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
  208. package/dist/assets/PluginsPage-D2862wul.js +0 -1
  209. package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
  210. package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +0 -1628
  211. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
  212. package/dist/assets/TerminalDock-Dgg-_ZmU.js +0 -2
  213. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
  214. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
  215. package/dist/assets/channel-DToA8d-4.js +0 -1
  216. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
  217. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
  218. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
  219. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
  220. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
  221. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
  222. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
  223. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
  224. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
  225. package/dist/assets/icons-cTkqD_Gi.js +0 -1
  226. package/dist/assets/index-B48prgLL.js +0 -65
  227. package/dist/assets/index-DQhwHHer.css +0 -3
  228. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
  229. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
  230. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
  231. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
  232. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
  233. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
  234. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
  235. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
  236. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
  237. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
  238. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
  239. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
  240. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
  241. /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  242. /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  243. /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  244. /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
  245. /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  246. /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
  247. /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
  248. /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  249. /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  250. /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
  251. /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
  252. /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
  253. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  254. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  255. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  256. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  257. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  258. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -0,0 +1,81 @@
1
+ import { atomicUpdate, readStore } from '../storage.mjs'
2
+ import { parseCloudBaseUrl } from './config.mjs'
3
+
4
+ export const CLOUD_SERVICE_SETTINGS_KEY = 'quickforge-cloud-service'
5
+ export const CLOUD_SERVICE_SCHEMA_VERSION = 1
6
+ export const CLOUD_SERVICE_TYPE = 'quickforge-cloud'
7
+ export const DEFAULT_CLOUD_URL = 'http://127.0.0.1:8082/'
8
+
9
+ function savedServiceRecord(settings) {
10
+ const value = settings?.[CLOUD_SERVICE_SETTINGS_KEY]
11
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined
12
+ }
13
+
14
+ function candidateFrom(settings, env) {
15
+ const saved = savedServiceRecord(settings)
16
+ if (saved && typeof saved.cloudUrl === 'string' && saved.cloudUrl.trim()) {
17
+ return { value: saved.cloudUrl, source: 'saved', saved: true, enabled: saved.enabled === true }
18
+ }
19
+ if (typeof env?.QUICKFORGE_CLOUD_URL === 'string' && env.QUICKFORGE_CLOUD_URL.trim()) {
20
+ return { value: env.QUICKFORGE_CLOUD_URL, source: 'env', saved: false, enabled: false }
21
+ }
22
+ return { value: DEFAULT_CLOUD_URL, source: 'default', saved: false, enabled: false }
23
+ }
24
+
25
+ export async function readCloudServiceConfig({ readSettings = readStore, env = process.env, strict = true } = {}) {
26
+ const settings = await readSettings('settings')
27
+ const candidate = candidateFrom(settings, env)
28
+ try {
29
+ const baseUrl = parseCloudBaseUrl(candidate.value)
30
+ return {
31
+ schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
32
+ serviceType: CLOUD_SERVICE_TYPE,
33
+ enabled: candidate.enabled,
34
+ cloudUrl: baseUrl.href,
35
+ baseUrl,
36
+ source: candidate.source,
37
+ saved: candidate.saved,
38
+ valid: true,
39
+ }
40
+ } catch (error) {
41
+ if (strict) throw error
42
+ return {
43
+ schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
44
+ serviceType: CLOUD_SERVICE_TYPE,
45
+ enabled: candidate.enabled,
46
+ cloudUrl: String(candidate.value || '').trim(),
47
+ source: candidate.source,
48
+ saved: candidate.saved,
49
+ valid: false,
50
+ configurationError: error instanceof Error ? error.message : String(error),
51
+ }
52
+ }
53
+ }
54
+
55
+ export async function saveCloudServiceConfig({ cloudUrl, enabled }, { updateSettings = atomicUpdate } = {}) {
56
+ const baseUrl = parseCloudBaseUrl(cloudUrl)
57
+ const record = {
58
+ schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
59
+ serviceType: CLOUD_SERVICE_TYPE,
60
+ enabled: enabled === true,
61
+ cloudUrl: baseUrl.href,
62
+ }
63
+ await updateSettings('settings', (settings) => ({
64
+ ...settings,
65
+ [CLOUD_SERVICE_SETTINGS_KEY]: record,
66
+ }))
67
+ return record
68
+ }
69
+
70
+ export function publicCloudServiceConfig(config) {
71
+ return {
72
+ schemaVersion: CLOUD_SERVICE_SCHEMA_VERSION,
73
+ serviceType: CLOUD_SERVICE_TYPE,
74
+ enabled: config.enabled === true,
75
+ cloudUrl: config.cloudUrl,
76
+ source: config.source,
77
+ saved: config.saved,
78
+ valid: config.valid,
79
+ configurationError: config.configurationError,
80
+ }
81
+ }
package/server/index.mjs CHANGED
@@ -17,6 +17,7 @@ import { handleToolApi, handleGetTools } from './routes/tools.mjs'
17
17
  import { handleInstructionsApi } from './routes/instructions.mjs'
18
18
  import { handleMemoryApi } from './routes/memory.mjs'
19
19
  import { handleSkillsApi } from './routes/skills.mjs'
20
+ import { ensureDefaultGlobalSkills } from './skills.mjs'
20
21
  import { handleAgentApi } from './routes/agent.mjs'
21
22
  import { handleAgentProfilesApi } from './routes/agent-profiles.mjs'
22
23
  import { handleScheduledTasksApi, startScheduledTaskRunner, stopScheduledTaskRunner } from './routes/scheduled-tasks.mjs'
@@ -33,6 +34,9 @@ import { handleWorkspaceApi, handleGitApi } from './routes/workspace.mjs'
33
34
  import { handleTerminalApi, handleTerminalUpgrade } from './routes/terminal.mjs'
34
35
  import { handleChannelsApi } from './routes/channels.mjs'
35
36
  import { handleModelsApi } from './routes/models.mjs'
37
+ import { handleCloudApi } from './routes/cloud.mjs'
38
+ import { readCloudServiceConfig } from './cloud/service-config.mjs'
39
+ import { startQfAgent, stopQfAgent, getQfAgentStatus } from './cloud/qf-agent-process.mjs'
36
40
  import { serveStatic } from './routes/static.mjs'
37
41
  import { logger, flushLogger } from './utils/logger.mjs'
38
42
  import { getPackageInfo, checkForUpdates, checkDesktopRelease } from './utils/package-update.mjs'
@@ -45,6 +49,7 @@ import { initializeChannels, shutdownChannels } from './channels/registry.mjs'
45
49
  import { shutdownMcpConnections } from './mcp/registry.mjs'
46
50
  import { shutdownTerminalSessions } from './terminal/terminal-manager.mjs'
47
51
  import { createNodeProcessEnv } from './utils/process-env.mjs'
52
+ import { isAuthenticatedAppClient } from './access-policy.mjs'
48
53
 
49
54
  const __filename = fileURLToPath(import.meta.url)
50
55
  const __dirname = path.dirname(__filename)
@@ -65,6 +70,10 @@ const port = Number(process.env.QUICKFORGE_PORT || (isDev ? 32176 : 5176))
65
70
  const vitePort = Number(process.env.QUICKFORGE_VITE_PORT || 5176)
66
71
  let restartInProgress = false
67
72
  let updateInProgress = false
73
+ let shutdownPromise = null
74
+ let shutdownStarted = false
75
+ let qfAgentStartPromise = null
76
+ let boundServerPort = null
68
77
 
69
78
  setDefaultWorkspaceRoot(process.env.QUICKFORGE_WORKSPACE_DIR || path.join(dataDir, 'workspace'))
70
79
 
@@ -72,11 +81,12 @@ function getRestartSupport() {
72
81
  return { supported: true, reason: null }
73
82
  }
74
83
 
75
- async function getSystemStatus({ isLocalRequest = true } = {}) {
84
+ async function getSystemStatus({ isLocalRequest = true, remoteAuthorized = false } = {}) {
76
85
  const config = await readProjectConfig()
77
86
  const restartSupport = getRestartSupport()
78
87
  const packageInfo = await getPackageInfo(projectRoot)
79
- const restartSupported = isLocalRequest && restartSupport.supported
88
+ const restartAllowed = isAuthenticatedAppClient({ isLocalRequest, remoteAuthorized })
89
+ const restartSupported = restartAllowed && restartSupport.supported
80
90
  return {
81
91
  ok: true,
82
92
  mode: isDev ? 'development' : 'production',
@@ -92,7 +102,7 @@ async function getSystemStatus({ isLocalRequest = true } = {}) {
92
102
  terminal: isLocalRequest,
93
103
  },
94
104
  restartSupported,
95
- restartUnsupportedReason: isLocalRequest ? restartSupport.reason : 'Restart is only allowed from this computer',
105
+ restartUnsupportedReason: restartAllowed ? restartSupport.reason : 'Restart requires an authenticated client',
96
106
  dataDir,
97
107
  configDir,
98
108
  storageDir,
@@ -149,11 +159,9 @@ function closeHttpServer() {
149
159
  })
150
160
  }
151
161
 
152
- async function performRestart() {
153
- logger.info('Restart requested from settings UI.')
154
- const supervisorPid = await spawnRestartSupervisor()
155
- logger.info(`Restart supervisor started (PID ${supervisorPid}).`)
156
-
162
+ async function shutdownRuntime() {
163
+ shutdownStarted = true
164
+ await stopQfAgent()
157
165
  stopScheduledTaskRunner()
158
166
  stopAutoArchiveRunner()
159
167
  stopVite()
@@ -162,6 +170,19 @@ async function performRestart() {
162
170
  await shutdownChannels()
163
171
  shutdownTerminalSessions()
164
172
  await closeHttpServer()
173
+ }
174
+
175
+ export function stopQuickForgeServer() {
176
+ if (!shutdownPromise) shutdownPromise = shutdownRuntime()
177
+ return shutdownPromise
178
+ }
179
+
180
+ async function performRestart() {
181
+ logger.info('Restart requested from settings UI.')
182
+ const supervisorPid = await spawnRestartSupervisor()
183
+ logger.info(`Restart supervisor started (PID ${supervisorPid}).`)
184
+
185
+ await stopQuickForgeServer()
165
186
  process.exit(0)
166
187
  }
167
188
 
@@ -228,14 +249,7 @@ function spawnUpdateSupervisor(update) {
228
249
 
229
250
  async function shutdownForUpdate() {
230
251
  logger.info('Shutting down QuickForge for external updater.')
231
- stopScheduledTaskRunner()
232
- stopAutoArchiveRunner()
233
- stopVite()
234
- await shutdownAgentManager()
235
- await shutdownMcpConnections()
236
- await shutdownChannels()
237
- shutdownTerminalSessions()
238
- await closeHttpServer()
252
+ await stopQuickForgeServer()
239
253
  flushLogger()
240
254
  process.exit(0)
241
255
  }
@@ -282,6 +296,22 @@ async function updateQuickForge() {
282
296
  }
283
297
  }
284
298
 
299
+ async function applyCloudServiceConfig(cloudConfig, { urlChanged = false } = {}) {
300
+ if (shutdownStarted) return null
301
+ if (!cloudConfig.enabled || !cloudConfig.valid) {
302
+ if (!cloudConfig.valid) logger.warn(`QuickForge remote agent was not started: ${cloudConfig.configurationError || 'invalid Cloud configuration'}`)
303
+ return stopQfAgent({ disabled: true })
304
+ }
305
+ if (!boundServerPort) return null
306
+ const agentStatus = getQfAgentStatus()
307
+ if (urlChanged || agentStatus.status === 'disabled' || agentStatus.enabled === false) await stopQfAgent()
308
+ return startQfAgent({
309
+ serverUrl: `http://127.0.0.1:${boundServerPort}/`,
310
+ ownerPid: process.pid,
311
+ cloudUrl: cloudConfig.cloudUrl,
312
+ })
313
+ }
314
+
285
315
  // --- Route dispatching ---
286
316
  async function handleApi(req, res, url, requestContext = {}) {
287
317
  const pathname = url.pathname
@@ -289,12 +319,12 @@ async function handleApi(req, res, url, requestContext = {}) {
289
319
 
290
320
  // Conversation share routes (management + public LAN access)
291
321
  if (pathname === '/api/shares' || pathname.startsWith('/api/shares/')) {
292
- await handleSharesApi(req, res, url, { port })
322
+ await handleSharesApi(req, res, url, { ...requestContext, port })
293
323
  return
294
324
  }
295
325
 
296
326
  if (pathname.startsWith('/api/shared/')) {
297
- await handleSharedConversationApi(req, res, url)
327
+ await handleSharedConversationApi(req, res, url, requestContext)
298
328
  return
299
329
  }
300
330
 
@@ -336,13 +366,24 @@ async function handleApi(req, res, url, requestContext = {}) {
336
366
 
337
367
  // Agent profiles
338
368
  if (pathname === '/api/agent-profiles' || pathname.startsWith('/api/agent-profiles/')) {
339
- await handleAgentProfilesApi(req, res, url)
369
+ await handleAgentProfilesApi(req, res, url, requestContext)
370
+ return
371
+ }
372
+
373
+ // QuickForge Cloud account and managed models (local or authenticated Tailscale requests).
374
+ if (pathname === '/api/cloud' || pathname.startsWith('/api/cloud/')) {
375
+ await handleCloudApi(req, res, url, {
376
+ isLocalRequest: requestContext.isLocalRequest === true,
377
+ remoteAddress: requestContext.remoteAddress,
378
+ remoteAuthorized: requestContext.remoteAuthorized === true,
379
+ onCloudServiceConfigChanged: applyCloudServiceConfig,
380
+ })
340
381
  return
341
382
  }
342
383
 
343
384
  // Custom model management (connection test)
344
- if (pathname === '/api/models/test-connection') {
345
- await handleModelsApi(req, res, url)
385
+ if (pathname === '/api/models/catalog' || pathname === '/api/models/test-connection') {
386
+ await handleModelsApi(req, res, url, requestContext)
346
387
  return
347
388
  }
348
389
 
@@ -387,7 +428,7 @@ async function handleApi(req, res, url, requestContext = {}) {
387
428
  }
388
429
 
389
430
  if (pathname.startsWith('/api/git/')) {
390
- await handleGitApi(req, res, url)
431
+ await handleGitApi(req, res, url, requestContext)
391
432
  return
392
433
  }
393
434
 
@@ -411,19 +452,19 @@ async function handleApi(req, res, url, requestContext = {}) {
411
452
 
412
453
  // Agent routes
413
454
  if (parts[0] === 'api' && parts[1] === 'agents') {
414
- await handleAgentApi(req, res, url)
455
+ await handleAgentApi(req, res, url, requestContext)
415
456
  return
416
457
  }
417
458
 
418
459
  // Scheduled task routes
419
460
  if (pathname === '/api/scheduled-tasks' || pathname.startsWith('/api/scheduled-tasks/')) {
420
- await handleScheduledTasksApi(req, res, url)
461
+ await handleScheduledTasksApi(req, res, url, requestContext)
421
462
  return
422
463
  }
423
464
 
424
465
  // Backup / import-export routes
425
466
  if (pathname === '/api/backup/export' || pathname === '/api/backup/import' || pathname === '/api/backup/inspect' || pathname === '/api/backup/inspect-file') {
426
- await handleBackupApi(req, res, url)
467
+ await handleBackupApi(req, res, url, requestContext)
427
468
  return
428
469
  }
429
470
 
@@ -437,6 +478,7 @@ async function handleApi(req, res, url, requestContext = {}) {
437
478
  checkDesktopRelease: () => checkDesktopRelease(projectRoot),
438
479
  updateQuickForge,
439
480
  isLocalRequest: requestContext.isLocalRequest === true,
481
+ remoteAuthorized: requestContext.remoteAuthorized === true,
440
482
  getTerminalShellSetting: readTerminalShellSetting,
441
483
  updateTerminalShellSetting,
442
484
  getTerminalShellConfig: readTerminalShellConfig,
@@ -461,7 +503,7 @@ async function handleApi(req, res, url, requestContext = {}) {
461
503
 
462
504
  // Storage routes (catch-all)
463
505
  if (parts[0] === 'api' && parts[1] === 'storage') {
464
- await handleStorageApi(req, res, url)
506
+ await handleStorageApi(req, res, url, requestContext)
465
507
  return
466
508
  }
467
509
 
@@ -532,6 +574,24 @@ function isAllowedHostHeader(value) {
532
574
  return allowedHosts.has(parsed.hostname) && hostPort === expectedPort
533
575
  }
534
576
 
577
+ const trustedTunnelSocket = Symbol('trustedTunnelSocket')
578
+
579
+ function isTunnelClientRequest(req) {
580
+ if (
581
+ isLoopbackAddress(req.socket.remoteAddress)
582
+ && req.headers['x-quickforge-tunnel'] === '1'
583
+ && req.headers.host === '127.0.0.1:18080'
584
+ ) {
585
+ req.socket[trustedTunnelSocket] = true
586
+ }
587
+ return req.socket[trustedTunnelSocket] === true
588
+ }
589
+
590
+ function isAllowedRequestHost(req, isTunnelClient) {
591
+ return isAllowedHostHeader(req.headers.host)
592
+ || (isTunnelClient && req.headers.host === '127.0.0.1:18080')
593
+ }
594
+
535
595
  function isLanAccessBootstrapPath(pathname) {
536
596
  return pathname === '/api/health'
537
597
  || pathname === '/api/lan-access/status'
@@ -571,7 +631,11 @@ const server = createServer(async (req, res) => {
571
631
  reqLogger.info(`${req.method} ${req.url} ${res.statusCode}`, { method: req.method, url: req.url, status: res.statusCode, durationMs })
572
632
  })
573
633
 
574
- if (!isAllowedHostHeader(req.headers.host)) {
634
+ const remoteAddress = req.socket.remoteAddress
635
+ // 云远程访问(RemoteTunnel):agent 与 qf 同机时流量经 127.0.0.1 回环进入并携带
636
+ // X-QuickForge-Tunnel: 1。仅该可信隧道允许手机本地入口 127.0.0.1:18080 作为 Host。
637
+ const isTunnelClient = isTunnelClientRequest(req)
638
+ if (!isAllowedRequestHost(req, isTunnelClient)) {
575
639
  res.writeHead(403, { 'content-type': 'application/json; charset=utf-8' })
576
640
  res.end(JSON.stringify({ error: 'Forbidden host' }))
577
641
  return
@@ -593,11 +657,11 @@ const server = createServer(async (req, res) => {
593
657
  }
594
658
  try {
595
659
  const url = new URL(req.url || '/', `http://${req.headers.host || `${host}:${port}`}`)
596
- const remoteAddress = req.socket.remoteAddress
597
- const isRemoteRequest = !isLoopbackAddress(remoteAddress)
598
- const remoteAuthorized = isRemoteRequest ? await isAuthorizedRemoteRequest(req) : true
660
+ // 隧道请求视为“已认证远程客户端”:认证通过,但本地能力按远程请求裁剪。
661
+ const isRemoteRequest = isTunnelClient || !isLoopbackAddress(remoteAddress)
662
+ const remoteAuthorized = isRemoteRequest ? (isTunnelClient ? true : await isAuthorizedRemoteRequest(req)) : true
599
663
 
600
- if (isRemoteRequest && shareLanEnabled && !remoteAuthorized && !isLanAccessBootstrapPath(url.pathname) && !isSharePath(url.pathname) && !isStaticAssetPath(url.pathname)) {
664
+ if (isRemoteRequest && !remoteAuthorized && !isLanAccessBootstrapPath(url.pathname) && !isSharePath(url.pathname) && !isStaticAssetPath(url.pathname)) {
601
665
  if (url.pathname.startsWith('/api/')) {
602
666
  sendLanAuthRequired(res)
603
667
  } else {
@@ -609,7 +673,6 @@ const server = createServer(async (req, res) => {
609
673
  if (
610
674
  url.pathname.startsWith('/api/') &&
611
675
  isRemoteRequest &&
612
- shareLanEnabled &&
613
676
  !remoteAuthorized &&
614
677
  !(url.pathname.startsWith('/api/shared/') || url.pathname === '/api/health' || url.pathname.startsWith('/api/lan-access/'))
615
678
  ) {
@@ -619,7 +682,12 @@ const server = createServer(async (req, res) => {
619
682
  }
620
683
 
621
684
  if (url.pathname.startsWith('/api/')) {
622
- await handleApi(req, res, url, { isLocalRequest: !isRemoteRequest })
685
+ await handleApi(req, res, url, {
686
+ isLocalRequest: !isRemoteRequest,
687
+ remoteAddress,
688
+ remoteAuthorized,
689
+ tunnelClient: isTunnelClient,
690
+ })
623
691
  return
624
692
  }
625
693
 
@@ -633,7 +701,7 @@ const server = createServer(async (req, res) => {
633
701
  return
634
702
  }
635
703
 
636
- if (isRemoteRequest && shareLanEnabled && !remoteAuthorized) {
704
+ if (isRemoteRequest && !remoteAuthorized) {
637
705
  res.writeHead(403, { 'content-type': 'application/json; charset=utf-8' })
638
706
  res.end(JSON.stringify({ error: 'Remote access is limited to shared conversation links.' }))
639
707
  return
@@ -661,7 +729,8 @@ function writeAndDestroySocket(socket, statusLine) {
661
729
  }
662
730
 
663
731
  server.on('upgrade', (req, socket, head) => {
664
- if (!isAllowedHostHeader(req.headers.host)) {
732
+ const isTunnelClient = isTunnelClientRequest(req)
733
+ if (!isAllowedRequestHost(req, isTunnelClient)) {
665
734
  writeAndDestroySocket(socket, 'HTTP/1.1 403 Forbidden')
666
735
  return
667
736
  }
@@ -670,7 +739,8 @@ server.on('upgrade', (req, socket, head) => {
670
739
  const url = new URL(req.url || '/', `http://${req.headers.host || `${host}:${port}`}`)
671
740
  if (url.pathname.startsWith('/api/terminal/sessions/')) {
672
741
  handleTerminalUpgrade(req, socket, head, url, {
673
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
742
+ // 隧道流量同样是远程客户端:终端 WebSocket 保持禁止。
743
+ isLocalRequest: isLoopbackAddress(req.socket.remoteAddress) && !isTunnelClient,
674
744
  })
675
745
  return
676
746
  }
@@ -681,6 +751,7 @@ server.on('upgrade', (req, socket, head) => {
681
751
  })
682
752
 
683
753
  await ensureStorage()
754
+ await ensureDefaultGlobalSkills()
684
755
  await initializeNetworkProxy()
685
756
  installAiHttpLogger()
686
757
  initializeChannels({
@@ -708,35 +779,40 @@ server.on('error', (error) => {
708
779
  })
709
780
 
710
781
  server.listen(port, host, () => {
711
- logger.info(`QuickForge local API: http://${host}:${port}`)
782
+ const address = server.address()
783
+ const boundPort = typeof address === 'object' && address ? address.port : port
784
+ boundServerPort = boundPort
785
+ logger.info(`QuickForge local API: http://${host}:${boundPort}`)
712
786
  if (shareLanEnabled) {
713
- const lanUrls = getLanUrls(port)
714
- logger.info(`QuickForge LAN sharing is enabled. Share pages are available at: ${lanUrls.length ? lanUrls.join(', ') : `http://<your-lan-ip>:${port}`}`)
787
+ const lanUrls = getLanUrls(boundPort)
788
+ logger.info(`QuickForge LAN sharing is enabled. Share pages are available at: ${lanUrls.length ? lanUrls.join(', ') : `http://<your-lan-ip>:${boundPort}`}`)
715
789
  logger.info('Remote non-share API routes are restricted while QUICKFORGE_SHARE_LAN=1.')
716
790
  }
717
791
  logger.info(`QuickForge data dir: ${dataDir}`)
718
792
  logger.info(`QuickForge project: ${getWorkspaceRoot()}`)
719
793
 
794
+ const pending = readCloudServiceConfig({ strict: false }).then((cloudConfig) => applyCloudServiceConfig(cloudConfig)).catch((error) => {
795
+ if (!shutdownStarted) logger.warn(`QuickForge remote agent failed to start: ${error?.message || error}`)
796
+ return null
797
+ }).finally(() => {
798
+ if (qfAgentStartPromise === pending) qfAgentStartPromise = null
799
+ })
800
+ qfAgentStartPromise = pending
801
+
720
802
  if (isDev) {
721
803
  startVite()
722
804
  setTimeout(() => openBrowser(`http://localhost:${vitePort}`), 1000)
723
805
  } else if (shareLanEnabled) {
724
- openBrowser(`http://localhost:${port}`)
806
+ openBrowser(`http://localhost:${boundPort}`)
725
807
  } else {
726
- openBrowser(`http://localhost:${port}`)
808
+ openBrowser(`http://localhost:${boundPort}`)
727
809
  }
728
810
  })
729
811
 
730
812
  // Graceful shutdown
731
813
  async function gracefulShutdown(signal) {
732
814
  logger.info(`Received ${signal}, shutting down gracefully...`)
733
- stopScheduledTaskRunner()
734
- stopAutoArchiveRunner()
735
- stopVite()
736
- await shutdownAgentManager()
737
- await shutdownMcpConnections()
738
- await shutdownChannels()
739
- shutdownTerminalSessions()
815
+ await stopQuickForgeServer()
740
816
  flushLogger()
741
817
  process.exit(0)
742
818
  }
@@ -215,22 +215,21 @@ async function refreshConnections() {
215
215
  }
216
216
  }
217
217
 
218
- for (const config of enabled.values()) {
218
+ const refreshed = await Promise.all([...enabled.values()].map(async (config) => {
219
219
  const existing = connections.get(config.name)
220
220
  if (existing) {
221
221
  if (existing.status === 'error' && Date.now() - (existing.lastAttemptAt || 0) >= RETRY_ERROR_AFTER_MS) {
222
222
  connections.delete(config.name)
223
223
  await closeConnection(existing)
224
224
  } else {
225
- continue
225
+ return [config.name, existing]
226
226
  }
227
227
  }
228
228
  try {
229
- const connection = await connectServer(config)
230
- connections.set(config.name, connection)
229
+ return [config.name, await connectServer(config)]
231
230
  } catch (error) {
232
231
  logger.error(`Failed to connect MCP server ${config.name}:`, error)
233
- connections.set(config.name, {
232
+ return [config.name, {
234
233
  config,
235
234
  client: null,
236
235
  transport: null,
@@ -240,10 +239,12 @@ async function refreshConnections() {
240
239
  connectedAt: null,
241
240
  stderr: '',
242
241
  lastAttemptAt: Date.now(),
243
- })
242
+ }]
244
243
  }
245
- }
244
+ }))
246
245
 
246
+ connections.clear()
247
+ for (const [name, connection] of refreshed) connections.set(name, connection)
247
248
  return connections
248
249
  }
249
250
 
@@ -21,30 +21,51 @@ export function omitDetailsForLlm(message) {
21
21
  * Handles "user-with-attachments" → "user" with multi-modal content blocks.
22
22
  * Without this the default pi-agent-core convertToLlm silently drops
23
23
  * user-with-attachments messages, so the LLM never sees attachments.
24
+ *
25
+ * Also guards against orphaned toolResult messages (a tool result without a
26
+ * preceding assistant tool_call): OpenAI-compatible APIs reject such sequences
27
+ * with 400 "Messages with role 'tool' must be a response to a preceding
28
+ * message with 'tool_calls'". Assistant messages that pi-ai would skip
29
+ * (stopReason error/aborted) are not counted as valid tool_call ancestors,
30
+ * matching the upstream skip logic so their results are dropped too.
24
31
  */
25
32
  export function serverConvertToLlm(messages) {
26
- return messages
27
- .filter(m => m.role !== 'artifact')
28
- .map(m => {
29
- if (m.role === 'user-with-attachments') {
30
- const textContent = typeof m.content === 'string'
31
- ? [{ type: 'text', text: m.content }]
32
- : [...m.content]
33
- if (Array.isArray(m.attachments)) {
34
- for (const att of m.attachments) {
35
- if (att.type === 'image' && att.content) {
36
- textContent.push({ type: 'image', data: att.content, mimeType: att.mimeType })
37
- } else if (att.type === 'document' && att.extractedText) {
38
- textContent.push({ type: 'text', text: `\n\n[Document: ${att.fileName}]\n${att.extractedText}` })
39
- }
33
+ const pendingToolCallIds = new Set()
34
+ const converted = []
35
+ for (const m of messages) {
36
+ if (!m || typeof m !== 'object') continue
37
+ if (m.role === 'artifact') continue
38
+ if (m.role === 'user-with-attachments') {
39
+ const textContent = typeof m.content === 'string'
40
+ ? [{ type: 'text', text: m.content }]
41
+ : [...m.content]
42
+ if (Array.isArray(m.attachments)) {
43
+ for (const att of m.attachments) {
44
+ if (att.type === 'image' && att.content) {
45
+ textContent.push({ type: 'image', data: att.content, mimeType: att.mimeType })
46
+ } else if (att.type === 'document' && att.extractedText) {
47
+ textContent.push({ type: 'text', text: `\n\n[Document: ${att.fileName}]\n${att.extractedText}` })
40
48
  }
41
49
  }
42
- return omitDetailsForLlm({ ...m, role: 'user', content: textContent })
43
50
  }
44
- if (m.role === 'user' || m.role === 'assistant' || m.role === 'toolResult') return omitDetailsForLlm(m)
45
- return null
46
- })
47
- .filter(Boolean)
51
+ converted.push(omitDetailsForLlm({ ...m, role: 'user', content: textContent }))
52
+ } else if (m.role === 'user') {
53
+ converted.push(omitDetailsForLlm(m))
54
+ } else if (m.role === 'assistant') {
55
+ if (m.stopReason !== 'error' && m.stopReason !== 'aborted' && Array.isArray(m.content)) {
56
+ for (const block of m.content) {
57
+ if (block?.type === 'toolCall' && block.id) pendingToolCallIds.add(block.id)
58
+ }
59
+ }
60
+ converted.push(omitDetailsForLlm(m))
61
+ } else if (m.role === 'toolResult') {
62
+ // 孤儿 toolResult(无前导 assistant tool_calls)直接丢弃,避免上游 400
63
+ if (!m.toolCallId || !pendingToolCallIds.has(m.toolCallId)) continue
64
+ pendingToolCallIds.delete(m.toolCallId)
65
+ converted.push(omitDetailsForLlm(m))
66
+ }
67
+ }
68
+ return converted
48
69
  }
49
70
 
50
71
  /**