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