@shawnstack/quickforge 1.7.4 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  2. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  3. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  4. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  5. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  6. package/dist/assets/{SettingsWorkspacePage-B53_eDf2.js → SettingsWorkspacePage-Cb54Ic91.js} +389 -378
  7. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  8. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  9. package/dist/assets/{TerminalDock-Dgg-_ZmU.js → TerminalDock-D4zJBJc4.js} +2 -2
  10. package/dist/assets/WorkspaceInspector-D9kdnLDd.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/{ebnfDiagram-CCIWWBDH-BTlnwneA.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  69. package/dist/assets/{erDiagram-Q63AITRT-C3fFWGCm.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  70. package/dist/assets/{esm-oa69b5u4.js → esm-Df4edDpj.js} +2 -2
  71. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  72. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  73. package/dist/assets/{ganttDiagram-NO4QXBWP-DzAiojNi.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  74. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  75. package/dist/assets/{gitGraphDiagram-IHSO6WYX-Cv57oNEA.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  76. package/dist/assets/icons-CXPP1fFT.js +1 -0
  77. package/dist/assets/index-_ZhK6bvp.js +65 -0
  78. package/dist/assets/index-iVV2bP_f.css +3 -0
  79. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  80. package/dist/assets/{infoDiagram-FWYZ7A6U-B5Z_O3Ro.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  81. package/dist/assets/{ishikawaDiagram-FXEZZL3T-CKTK_TKV.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  82. package/dist/assets/{journeyDiagram-5HDEW3XC-Cm9k4ywW.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  83. package/dist/assets/{kanban-definition-HUTT4EX6-DzNteLwx.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  84. package/dist/assets/{line-BG78c_WJ.js → line-B4DxX7tC.js} +1 -1
  85. package/dist/assets/{linear-1X20_EGu.js → linear-U-nKUhyW.js} +1 -1
  86. package/dist/assets/{map-CCI8WZkS.js → map-BneTM-ri.js} +1 -1
  87. package/dist/assets/{mcp-servers-dialog-GXXD9K0U.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  88. package/dist/assets/{mermaid-parser.core-Bb8HI_OS.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  89. package/dist/assets/{mermaid-renderer-PIaNiXAz.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  90. package/dist/assets/{mermaid.core-DUHOIwL5.js → mermaid.core-DWpShXCs.js} +3 -3
  91. package/dist/assets/{mindmap-definition-LN4V7U3C-Tnbszo7b.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  92. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  93. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  94. package/dist/assets/{monaco-BVtCspYZ.js → monaco-D6OhXgjg.js} +1 -1
  95. package/dist/assets/{ordinal-v4prQcuq.js → ordinal-B2r5L1v4.js} +1 -1
  96. package/dist/assets/{otel-D1LswXpM.js → otel-BFnphzjt.js} +1 -1
  97. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  98. package/dist/assets/{pegDiagram-2B236MQR-CX824Kd0.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  99. package/dist/assets/{pi-ai-DAJlO9ZB.js → pi-ai-Cr2ee4a0.js} +2 -2
  100. package/dist/assets/{pi-web-ui-B-RJSxjR.js → pi-web-ui-DV5jKjSg.js} +1 -1
  101. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  102. package/dist/assets/{pieDiagram-ENE6RG2P-BT04VWPQ.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  103. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BvOZxkBr.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  104. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  105. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  106. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  107. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  108. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  109. package/dist/assets/{railroadDiagram-RFXS5EU6-DnTU--BV.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  110. package/dist/assets/{react-vendor-eWH0QhNU.js → react-vendor-1KC5PyRx.js} +1 -1
  111. package/dist/assets/{requirementDiagram-TGXJPOKE--MZX_yEv.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  112. package/dist/assets/{sankeyDiagram-HTMAVEWB-SEZkHhYn.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  113. package/dist/assets/{sequenceDiagram-DBY2YBRQ-BhC2OHiz.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  114. package/dist/assets/{sizeCapture-X5ZJPWSS-Ci5LeToV.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  115. package/dist/assets/{skills-dialog-DDh1h8eH.js → skills-dialog-BThnIUBs.js} +3 -3
  116. package/dist/assets/{src-CovulcRC.js → src-PYsDme0X.js} +1 -1
  117. package/dist/assets/{stateDiagram-2N3HPSRC-DKK5tFT4.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  118. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  119. package/dist/assets/{swimlanes-5IMT3BWC-DxxGCk67.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  120. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  121. package/dist/assets/{timeline-definition-FHXFAJF6-BPXB3Sue.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  122. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  123. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  124. package/dist/assets/{vennDiagram-L72KCM5P-CxcBCKye.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  125. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  126. package/dist/assets/{wardleyDiagram-EHGQE667-DEL6Yes5.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  127. package/dist/assets/{xychartDiagram-FW5EYKEG-N8cPbcjC.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  128. package/dist/index.html +9 -8
  129. package/package.json +3 -1
  130. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  131. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  132. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  133. package/server/acp/server.mjs +36 -26
  134. package/server/agent-manager.mjs +130 -28
  135. package/server/agent-profile-files.mjs +8 -4
  136. package/server/agent-profile-schema.mjs +47 -23
  137. package/server/agent-profiles.mjs +1 -1
  138. package/server/ai-http-logger.mjs +98 -12
  139. package/server/cloud/chat-idempotency-store.mjs +96 -0
  140. package/server/cloud/client.mjs +102 -0
  141. package/server/cloud/config.mjs +53 -0
  142. package/server/cloud/credential-store.mjs +193 -0
  143. package/server/cloud/identity.mjs +355 -0
  144. package/server/cloud/index.mjs +7 -0
  145. package/server/cloud/models.mjs +80 -0
  146. package/server/cloud/runtime.mjs +129 -0
  147. package/server/cloud/service-config.mjs +77 -0
  148. package/server/index.mjs +28 -11
  149. package/server/model-catalog.mjs +262 -0
  150. package/server/plugins/registry.mjs +29 -5
  151. package/server/routes/agent-profiles.mjs +41 -7
  152. package/server/routes/agent.mjs +20 -11
  153. package/server/routes/backup.mjs +7 -1
  154. package/server/routes/cloud.mjs +227 -0
  155. package/server/routes/models.mjs +7 -1
  156. package/server/routes/scheduled-tasks.mjs +38 -8
  157. package/server/routes/shared-conversation.mjs +62 -58
  158. package/server/routes/shares.mjs +76 -1
  159. package/server/routes/storage.mjs +24 -1
  160. package/server/routes/workspace.mjs +6 -4
  161. package/server/share-store.mjs +168 -13
  162. package/server/skills.mjs +30 -3
  163. package/server/storage.mjs +7 -1
  164. package/server/subagents.mjs +3 -3
  165. package/server/tools/definitions.mjs +1 -1
  166. package/server/tools/index.mjs +2 -2
  167. package/server/utils/network.mjs +9 -0
  168. package/server/utils/response.mjs +8 -1
  169. package/skills/skill-creator/LICENSE.txt +202 -0
  170. package/skills/skill-creator/SKILL.md +485 -0
  171. package/skills/skill-creator/UPSTREAM.md +6 -0
  172. package/skills/skill-creator/agents/analyzer.md +274 -0
  173. package/skills/skill-creator/agents/comparator.md +202 -0
  174. package/skills/skill-creator/agents/grader.md +223 -0
  175. package/skills/skill-creator/assets/eval_review.html +146 -0
  176. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  177. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  178. package/skills/skill-creator/references/schemas.md +430 -0
  179. package/skills/skill-creator/scripts/__init__.py +0 -0
  180. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  181. package/skills/skill-creator/scripts/generate_report.py +326 -0
  182. package/skills/skill-creator/scripts/improve_description.py +247 -0
  183. package/skills/skill-creator/scripts/package_skill.py +136 -0
  184. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  185. package/skills/skill-creator/scripts/run_eval.py +310 -0
  186. package/skills/skill-creator/scripts/run_loop.py +328 -0
  187. package/skills/skill-creator/scripts/utils.py +47 -0
  188. package/dist/assets/AgentProfilesPage-B84w-r9S.js +0 -1
  189. package/dist/assets/ChatPanelHost-CyO8fEoj.js +0 -288
  190. package/dist/assets/PluginsPage-D2862wul.js +0 -1
  191. package/dist/assets/ScheduledTasksPage-GqbiJDPX.js +0 -2
  192. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +0 -1
  193. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +0 -13
  194. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +0 -1
  195. package/dist/assets/channel-DToA8d-4.js +0 -1
  196. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +0 -1
  197. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +0 -1
  198. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +0 -1
  199. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +0 -1
  200. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +0 -1
  201. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +0 -1
  202. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +0 -1
  203. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +0 -1
  204. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +0 -1
  205. package/dist/assets/icons-cTkqD_Gi.js +0 -1
  206. package/dist/assets/index-B48prgLL.js +0 -65
  207. package/dist/assets/index-DQhwHHer.css +0 -3
  208. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +0 -1
  209. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +0 -1
  210. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +0 -1
  211. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +0 -1
  212. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +0 -1
  213. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +0 -1
  214. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +0 -1
  215. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +0 -1
  216. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +0 -1
  217. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +0 -8
  218. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +0 -1
  219. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +0 -1
  220. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +0 -1
  221. /package/dist/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  222. /package/dist/assets/{chunk-KEIR6QF5-s7Qq-R3i.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  223. /package/dist/assets/{chunk-Y2CYZVJY-BgISNSiw.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  224. /package/dist/assets/{cytoscape.esm-iekjSZ2n.js → cytoscape.esm-DyfqweF2.js} +0 -0
  225. /package/dist/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  226. /package/dist/assets/{dist-zzfqk5hx.js → dist-CLg13NtV.js} +0 -0
  227. /package/dist/assets/{graphlib-CIz_CfxW.js → graphlib-DJkmx0Cq.js} +0 -0
  228. /package/dist/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  229. /package/dist/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  230. /package/dist/assets/{plugin-api-C8hMT5W6.js → plugin-api-Uu2xAB4d.js} +0 -0
  231. /package/dist/assets/{rough.esm-BMOtDaaS.js → rough.esm-Bud4g4fh.js} +0 -0
  232. /package/dist/assets/{web-RgX7H-OH.js → web-DoV8MSMn.js} +0 -0
  233. /package/dist/assets/{xterm-BFzZ1FH4.js → xterm-Bm_IbozX.js} +0 -0
  234. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  235. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  236. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  237. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  238. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  239. /package/plugins/{openai-spreadsheets → spreadsheets}/skills/spreadsheets/SKILL.md +0 -0
@@ -0,0 +1,227 @@
1
+ import { CloudClient, CloudApiError } from '../cloud/client.mjs'
2
+ import { parseCloudBaseUrl } from '../cloud/config.mjs'
3
+ import { createCloudCredentialStore } from '../cloud/credential-store.mjs'
4
+ import { getCloudRuntime, invalidateCloudRuntime } from '../cloud/runtime.mjs'
5
+ import {
6
+ publicCloudServiceConfig,
7
+ readCloudServiceConfig,
8
+ saveCloudServiceConfig,
9
+ } from '../cloud/service-config.mjs'
10
+ import { isTailscaleAddress } from '../utils/network.mjs'
11
+ import { readJsonBody, sendJson } from '../utils/response.mjs'
12
+
13
+ const CLOUD_CONFIG_BODY_MAX_BYTES = 16 * 1024
14
+ const CLOUD_ACTION_HEADER = 'x-quickforge-action'
15
+ const CLOUD_ACTION_VALUE = 'cloud-action'
16
+ const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])
17
+ const JSON_WRITE_ROUTES = new Set([
18
+ 'POST /api/cloud/test-connection',
19
+ 'PUT /api/cloud/config',
20
+ 'POST /api/cloud/identity/reset',
21
+ 'POST /api/cloud/device/start',
22
+ 'POST /api/cloud/device/poll',
23
+ 'POST /api/cloud/device/cancel',
24
+ ])
25
+
26
+ function routeError(message, statusCode, code) {
27
+ const error = new Error(message)
28
+ error.statusCode = statusCode
29
+ error.errorCode = code
30
+ error.code = code
31
+ return error
32
+ }
33
+
34
+ function mapCloudError(error) {
35
+ if (!(error instanceof CloudApiError)) return error
36
+ const mapped = routeError(error.message, error.status || 503, error.code)
37
+ mapped.retryable = error.retryable
38
+ mapped.details = error.details
39
+ return mapped
40
+ }
41
+
42
+ function createDefaultRuntime() {
43
+ return getCloudRuntime()
44
+ }
45
+
46
+ function sameCloudUrl(left, right) {
47
+ try {
48
+ return parseCloudBaseUrl(left).href === parseCloudBaseUrl(right).href
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ function requireDangerousConfirmation(body) {
55
+ if (body?.confirm !== 'reset-cloud-identity') {
56
+ throw routeError('Explicit confirmation is required to rebuild the QuickForge Cloud identity.', 400, 'cloud_reset_confirmation_required')
57
+ }
58
+ }
59
+
60
+ function requireProtectedCloudWrite(req, pathname) {
61
+ const method = String(req.method || 'GET').toUpperCase()
62
+ if (SAFE_METHODS.has(method)) return
63
+
64
+ if (req.headers[CLOUD_ACTION_HEADER] !== CLOUD_ACTION_VALUE) {
65
+ throw routeError('QuickForge Cloud action header is required.', 403, 'cloud_action_header_required')
66
+ }
67
+
68
+ if (!JSON_WRITE_ROUTES.has(`${method} ${pathname}`)) return
69
+ const mediaType = String(req.headers['content-type'] || '').split(';', 1)[0].trim().toLowerCase()
70
+ if (mediaType !== 'application/json') {
71
+ throw routeError('QuickForge Cloud JSON requests require Content-Type: application/json.', 415, 'cloud_json_content_type_required')
72
+ }
73
+ }
74
+
75
+ export function createCloudRouteHandler({
76
+ runtimeFactory = createDefaultRuntime,
77
+ readServiceConfig = readCloudServiceConfig,
78
+ saveServiceConfig = saveCloudServiceConfig,
79
+ credentialStoreFactory = createCloudCredentialStore,
80
+ invalidateRuntime = invalidateCloudRuntime,
81
+ cloudClientFactory = (config) => new CloudClient(config),
82
+ } = {}) {
83
+ async function runtimeOrError() {
84
+ try {
85
+ return { runtime: await runtimeFactory() }
86
+ } catch (error) {
87
+ return { configurationError: error }
88
+ }
89
+ }
90
+
91
+ return async function handleCloudApi(req, res, url, {
92
+ isLocalRequest = false,
93
+ remoteAddress,
94
+ remoteAuthorized = false,
95
+ } = {}) {
96
+ const tailscaleAllowed = remoteAuthorized === true
97
+ && isTailscaleAddress(remoteAddress)
98
+ if (!isLocalRequest && !tailscaleAllowed) {
99
+ throw routeError('QuickForge Cloud is available only on this computer or an authorized Tailscale client.', 403, 'cloud_local_only')
100
+ }
101
+
102
+ requireProtectedCloudWrite(req, url.pathname)
103
+
104
+ if (req.method === 'GET' && url.pathname === '/api/cloud/config') {
105
+ sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
106
+ return
107
+ }
108
+
109
+ if (req.method === 'POST' && url.pathname === '/api/cloud/test-connection') {
110
+ const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
111
+ const baseUrl = parseCloudBaseUrl(body?.cloudUrl)
112
+ const timeoutValue = Number(process.env.QUICKFORGE_CLOUD_TIMEOUT_MS || 10_000)
113
+ const client = cloudClientFactory({
114
+ baseUrl,
115
+ timeoutMs: Number.isFinite(timeoutValue) ? Math.min(60_000, Math.max(1_000, timeoutValue)) : 10_000,
116
+ })
117
+ try {
118
+ const [health, ready] = await Promise.all([client.health(), client.ready()])
119
+ sendJson(res, 200, { ok: true, cloudUrl: baseUrl.href, health, ready })
120
+ } catch (error) {
121
+ throw mapCloudError(error)
122
+ }
123
+ return
124
+ }
125
+
126
+ if (req.method === 'PUT' && url.pathname === '/api/cloud/config') {
127
+ const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
128
+ const nextUrl = parseCloudBaseUrl(body?.cloudUrl).href
129
+ const credentialStore = credentialStoreFactory()
130
+ const identityRecord = await credentialStore.read()
131
+ if (identityRecord.refreshToken || identityRecord.pendingDeviceFlow) {
132
+ const boundUrl = String(identityRecord.pendingDeviceFlow?.sessionCloudUrl || identityRecord.sessionCloudUrl || '').trim()
133
+ const sameUrl = boundUrl ? sameCloudUrl(boundUrl, nextUrl) : false
134
+ if (!sameUrl) {
135
+ throw routeError('A QuickForge Cloud session is active. Sign out first, or explicitly rebuild the identity before switching services.', 409, 'cloud_session_active')
136
+ }
137
+ }
138
+ await saveServiceConfig(nextUrl)
139
+ invalidateRuntime()
140
+ sendJson(res, 200, publicCloudServiceConfig(await readServiceConfig({ strict: false })))
141
+ return
142
+ }
143
+
144
+ if (req.method === 'POST' && url.pathname === '/api/cloud/identity/reset') {
145
+ const body = await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
146
+ requireDangerousConfirmation(body)
147
+ const { runtime } = await runtimeOrError()
148
+ if (runtime?.enabled) await runtime.identity.resetIdentity()
149
+ else {
150
+ const store = credentialStoreFactory()
151
+ await store.rotateInstallation()
152
+ }
153
+ invalidateRuntime()
154
+ sendJson(res, 200, { ok: true, mode: 'local' })
155
+ return
156
+ }
157
+
158
+ const { runtime: current, configurationError } = await runtimeOrError()
159
+ if (req.method === 'GET' && url.pathname === '/api/cloud/status') {
160
+ if (configurationError) {
161
+ sendJson(res, 200, { configured: false, mode: 'local', configurationError: configurationError.message })
162
+ return
163
+ }
164
+ if (!current?.enabled) {
165
+ sendJson(res, 200, { configured: false, mode: 'local' })
166
+ return
167
+ }
168
+ sendJson(res, 200, { configured: true, ...(await current.identity.status()) })
169
+ return
170
+ }
171
+
172
+ if (configurationError) throw routeError(configurationError.message, 503, 'cloud_configuration_error')
173
+ if (!current?.enabled) throw routeError('QuickForge Cloud is not configured.', 503, 'cloud_not_configured')
174
+
175
+ try {
176
+ if (req.method === 'POST' && url.pathname === '/api/cloud/guest/start') {
177
+ sendJson(res, 201, await current.identity.startGuest())
178
+ return
179
+ }
180
+ if (req.method === 'POST' && url.pathname === '/api/cloud/device/start') {
181
+ await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
182
+ sendJson(res, 201, await current.identity.startDeviceFlow())
183
+ return
184
+ }
185
+ if (req.method === 'POST' && url.pathname === '/api/cloud/device/poll') {
186
+ await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
187
+ sendJson(res, 200, await current.identity.pollDeviceFlow())
188
+ return
189
+ }
190
+ if (req.method === 'POST' && url.pathname === '/api/cloud/device/cancel') {
191
+ await readJsonBody(req, CLOUD_CONFIG_BODY_MAX_BYTES)
192
+ sendJson(res, 200, await current.identity.cancelDeviceFlow())
193
+ return
194
+ }
195
+ if (req.method === 'GET' && url.pathname === '/api/cloud/models') {
196
+ sendJson(res, 200, { items: await current.models.list(undefined, { refresh: true }) })
197
+ return
198
+ }
199
+ if (req.method === 'GET' && url.pathname === '/api/cloud/usage') {
200
+ sendJson(res, 200, await current.identity.usage())
201
+ return
202
+ }
203
+ if (req.method === 'GET' && url.pathname === '/api/cloud/installations') {
204
+ sendJson(res, 200, await current.identity.installations())
205
+ return
206
+ }
207
+ if (req.method === 'POST' && url.pathname === '/api/cloud/logout') {
208
+ await current.identity.logout()
209
+ invalidateRuntime()
210
+ sendJson(res, 200, { ok: true, mode: 'local' })
211
+ return
212
+ }
213
+ const revokeMatch = url.pathname.match(/^\/api\/cloud\/installations\/([^/]+)$/)
214
+ if (req.method === 'DELETE' && revokeMatch) {
215
+ await current.identity.revokeInstallation(decodeURIComponent(revokeMatch[1]))
216
+ sendJson(res, 200, { ok: true })
217
+ return
218
+ }
219
+ } catch (error) {
220
+ throw mapCloudError(error)
221
+ }
222
+
223
+ throw routeError('Not found', 404, 'route_not_found')
224
+ }
225
+ }
226
+
227
+ 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
@@ -6,6 +6,7 @@ import { createAgent, getSessionEventBus, agentEvents, persistSessionState, abor
6
6
  import { agentProfileSnapshot, getAgentProfile } from '../agent-profiles.mjs'
7
7
  import { projectContextFromId, readProjectConfig } from '../project-config.mjs'
8
8
  import { logger } from '../utils/logger.mjs'
9
+ import { resolveModelBinding } from '../model-catalog.mjs'
9
10
  import {
10
11
  dayMs,
11
12
  formatLocalDateTime,
@@ -133,10 +134,14 @@ async function getApiKey(provider) {
133
134
  }
134
135
  }
135
136
 
136
- async function parseScheduledTaskInstructionWithAi(instruction, model, thinkingLevel = 'off') {
137
+ async function parseScheduledTaskInstructionWithAi(instruction, modelInput, thinkingLevel = 'off', context = {}) {
137
138
  const text = String(instruction || '').trim()
138
139
  if (!text) return { needMoreInfo: true, question: '请输入要创建的定时任务。' }
139
- if (!model) return { needMoreInfo: true, question: '请先选择用于解析任务的大模型。' }
140
+ if (!modelInput) return { needMoreInfo: true, question: '请先选择用于解析任务的大模型。' }
141
+ const { model } = await resolveModelBinding(
142
+ modelInput?.modelRef || modelInput?.model ? modelInput : { model: modelInput },
143
+ { context, legacySnapshot: modelInput?.model || modelInput },
144
+ )
140
145
 
141
146
  const now = new Date()
142
147
  const systemPrompt = `你是定时任务解析器。把用户的中文自然语言定时任务解析为 JSON。
@@ -529,6 +534,18 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
529
534
 
530
535
  try {
531
536
  const executionProject = await resolveExecutionProject(task)
537
+ const binding = task.modelRef || task.model
538
+ ? await resolveModelBinding(
539
+ task.modelRef ? { modelRef: task.modelRef } : { model: task.model },
540
+ {
541
+ context: { source: 'scheduled', allowCloud: true },
542
+ currentModel: task.model,
543
+ allowCurrentHidden: true,
544
+ forExecution: true,
545
+ legacySnapshot: task.model,
546
+ },
547
+ )
548
+ : null
532
549
  if (task.agentId) {
533
550
  executionAgent = await getAgentProfile(task.agentId, { projectId: executionProject?.id || null, workspaceRoot: executionProject?.path })
534
551
  if (!executionAgent) agentWarning = `Configured agent not found: ${task.agentId}`
@@ -541,7 +558,9 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
541
558
  scope: executionProject ? 'project' : 'global',
542
559
  projectId: executionProject?.id || null,
543
560
  yoloMode,
544
- model: task.model,
561
+ model: binding?.model || task.model,
562
+ modelRef: binding?.modelRef || task.modelRef || null,
563
+ modelAccessContext: { source: 'scheduled', allowCloud: true },
545
564
  thinkingLevel: task.thinkingLevel,
546
565
  title: `[定时任务] ${task.title}`,
547
566
  agentProfile: executionAgent,
@@ -577,7 +596,7 @@ async function executeTask(task, trigger = 'schedule', onStarted) {
577
596
  onStarted?.({ taskId: task.id, runId, sessionId })
578
597
 
579
598
  const eventBus = getSessionEventBus(sessionId)
580
- const runtimeLimitMs = Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs || 30 * 60 * 1000), 30 * 60 * 1000))
599
+ const runtimeLimitMs = Math.max(1000, Math.min(Number(executionAgent?.maxRuntimeMs || 60 * 60 * 1000), 60 * 60 * 1000))
581
600
  let timeout = null
582
601
  let handler = null
583
602
  let timedOut = false
@@ -770,12 +789,12 @@ export function stopScheduledTaskRunner() {
770
789
  runningTaskRunIds.clear()
771
790
  }
772
791
 
773
- export async function handleScheduledTasksApi(req, res, url) {
792
+ export async function handleScheduledTasksApi(req, res, url, context = {}) {
774
793
  const parts = url.pathname.split('/').filter(Boolean)
775
794
 
776
795
  if (req.method === 'POST' && url.pathname === '/api/scheduled-tasks/parse') {
777
796
  const body = await readJsonBody(req)
778
- sendJson(res, 200, await parseScheduledTaskInstructionWithAi(body?.instruction, body?.model, body?.thinkingLevel))
797
+ sendJson(res, 200, await parseScheduledTaskInstructionWithAi(body?.instruction, body?.modelRef ? body : body?.model, body?.thinkingLevel, context))
779
798
  return
780
799
  }
781
800
 
@@ -796,12 +815,14 @@ export async function handleScheduledTasksApi(req, res, url) {
796
815
  const now = new Date().toISOString()
797
816
  const normalized = normalizeTaskInput(parsed)
798
817
  const enabled = parsed.enabled !== false && parsed.status !== 'paused'
818
+ const binding = await resolveModelBinding(body, { context, legacySnapshot: body?.model })
799
819
  const task = {
800
820
  id: createId(),
801
821
  ...normalized,
802
822
  scheduleRule: normalized.scheduleRule || scheduleRuleFor(normalized),
803
823
  executionMode: normalized.executionMode || 'serial',
804
- model: body?.model,
824
+ model: binding.model,
825
+ modelRef: binding.modelRef,
805
826
  thinkingLevel: body?.thinkingLevel || (body?.model?.reasoning ? 'medium' : 'off'),
806
827
  projectId: body?.projectId || null,
807
828
  projectName: body?.projectName || null,
@@ -830,6 +851,14 @@ export async function handleScheduledTasksApi(req, res, url) {
830
851
  const parsed = body?.task
831
852
  if (!parsed) throw requestError('Missing task')
832
853
  const normalized = normalizeTaskInput(parsed, existing)
854
+ const binding = body?.modelRef || body?.model
855
+ ? await resolveModelBinding(body, {
856
+ context,
857
+ currentModel: existing.model,
858
+ allowCurrentHidden: true,
859
+ legacySnapshot: body?.model,
860
+ })
861
+ : null
833
862
  const now = new Date().toISOString()
834
863
  const hasProject = Object.prototype.hasOwnProperty.call(body, 'projectId')
835
864
  const task = await updateTask(taskId, (current) => ({
@@ -837,7 +866,8 @@ export async function handleScheduledTasksApi(req, res, url) {
837
866
  ...normalized,
838
867
  scheduleRule: normalized.scheduleRule || scheduleRuleFor(normalized),
839
868
  executionMode: normalized.executionMode || 'serial',
840
- model: body?.model ?? current.model,
869
+ model: binding?.model ?? current.model,
870
+ modelRef: binding?.modelRef ?? current.modelRef,
841
871
  thinkingLevel: body?.thinkingLevel ?? current.thinkingLevel,
842
872
  projectId: hasProject ? (body.projectId || null) : current.projectId,
843
873
  projectName: hasProject ? (body.projectName || null) : current.projectName,
@@ -1,10 +1,12 @@
1
1
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
2
- import { readSessionValue, readStore } from '../storage.mjs'
2
+ import { readSessionValue } from '../storage.mjs'
3
+ import { listModelCatalog, resolveModelBinding } from '../model-catalog.mjs'
3
4
  import { sendSessionAsset } from './session-assets.mjs'
4
5
  import { abortRun, restoreAgent, runPrompt, getSessionState, getSessionEventBus, updateSessionModel, updateSessionThinkingLevel } from '../agent-manager.mjs'
5
6
  import {
6
7
  assertShareActive,
7
8
  issueConversationShareToken,
9
+ onConversationShareInvalidated,
8
10
  parseCookies,
9
11
  readConversationShare,
10
12
  rollbackSharedSessionMessages,
@@ -49,6 +51,7 @@ function publicSharePayload(record) {
49
51
  scope: record.scope,
50
52
  projectId: record.projectId,
51
53
  hasPassword: Boolean(record.passwordHash),
54
+ allowCloudUsage: record.allowCloudUsage === true,
52
55
  }
53
56
  }
54
57
 
@@ -101,13 +104,6 @@ function sanitizeModel(model) {
101
104
  }
102
105
  }
103
106
 
104
- function sanitizeIncomingModel(model) {
105
- if (!model || typeof model !== 'object') return null
106
- const sanitized = sanitizeModel(model)
107
- if (!sanitized.id || !sanitized.provider) return null
108
- return sanitized
109
- }
110
-
111
107
  function sanitizeEvent(event) {
112
108
  if (!event || typeof event !== 'object') return event
113
109
  const next = { ...event }
@@ -147,6 +143,20 @@ async function handleSharedEvents(req, res, record) {
147
143
 
148
144
  writeSseEvent(res, 'state', await sharedSessionPayload(record))
149
145
 
146
+ let cleanedUp = false
147
+ let expirationTimeout
148
+ let removeShareInvalidationListener = () => undefined
149
+
150
+ const cleanup = () => {
151
+ if (cleanedUp) return
152
+ cleanedUp = true
153
+ clearInterval(keepAlive)
154
+ if (expirationTimeout) clearTimeout(expirationTimeout)
155
+ removeShareInvalidationListener()
156
+ eventBus.removeListener('agent_event', onAgentEvent)
157
+ if (!res.writableEnded) res.end()
158
+ }
159
+
150
160
  const keepAlive = setInterval(() => {
151
161
  try {
152
162
  res.write(': ping\n\n')
@@ -164,11 +174,19 @@ async function handleSharedEvents(req, res, record) {
164
174
  }
165
175
  }
166
176
 
167
- const cleanup = () => {
168
- clearInterval(keepAlive)
169
- eventBus.removeListener('agent_event', onAgentEvent)
170
- if (!res.writableEnded) res.end()
177
+ removeShareInvalidationListener = onConversationShareInvalidated(({ shareId }) => {
178
+ if (shareId === record.id) cleanup()
179
+ })
180
+ const scheduleExpiration = () => {
181
+ if (!record.expiresAt || cleanedUp) return
182
+ const delay = Date.parse(record.expiresAt) - Date.now()
183
+ if (delay <= 0) {
184
+ cleanup()
185
+ return
186
+ }
187
+ expirationTimeout = setTimeout(scheduleExpiration, Math.min(delay, 2_147_483_647))
171
188
  }
189
+ scheduleExpiration()
172
190
 
173
191
  eventBus.on('agent_event', onAgentEvent)
174
192
  req.on('close', cleanup)
@@ -269,42 +287,7 @@ async function sharedSessionPayload(record) {
269
287
  return sanitizeSession(session, record)
270
288
  }
271
289
 
272
- function sanitizeProvider(provider) {
273
- if (!provider || typeof provider !== 'object') return null
274
- const models = Array.isArray(provider.models)
275
- ? provider.models.map(sanitizeModel).filter((model) => model?.id && model?.provider && model?.api)
276
- : []
277
- if (!models.length) return null
278
- return {
279
- id: provider.id || provider.name || models[0].provider,
280
- name: provider.name || models[0].provider,
281
- type: provider.type || models[0].api,
282
- baseUrl: provider.baseUrl,
283
- models,
284
- }
285
- }
286
-
287
- async function listSharedModelProviders() {
288
- const providers = await readStore('custom-providers')
289
- return Object.values(providers || {}).map(sanitizeProvider).filter(Boolean)
290
- }
291
-
292
- async function readConfiguredModel(model) {
293
- if (!model || typeof model !== 'object') return null
294
- const providers = await readStore('custom-providers')
295
- for (const provider of Object.values(providers || {})) {
296
- if (!provider || typeof provider !== 'object') continue
297
- const matched = Array.isArray(provider.models)
298
- ? provider.models.find((candidate) => {
299
- return candidate?.id === model.id && candidate?.provider === model.provider && candidate?.api === model.api
300
- })
301
- : undefined
302
- if (matched) return matched
303
- }
304
- return null
305
- }
306
-
307
- export async function handleSharedConversationApi(req, res, url) {
290
+ export async function handleSharedConversationApi(req, res, url, context = {}) {
308
291
  const parts = url.pathname.split('/').filter(Boolean)
309
292
  const shareId = decodeSegment(parts[2])
310
293
  const action = parts[3]
@@ -360,7 +343,21 @@ export async function handleSharedConversationApi(req, res, url) {
360
343
 
361
344
  if (req.method === 'GET' && action === 'models') {
362
345
  assertOperate(record)
363
- sendJson(res, 200, { providers: await listSharedModelProviders() })
346
+ const session = await sharedSessionPayload(record)
347
+ const models = await listModelCatalog({
348
+ context: { ...context, allowCloud: record.allowCloudUsage === true },
349
+ currentModel: session.model,
350
+ })
351
+ const providers = []
352
+ for (const model of models) {
353
+ let provider = providers.find((item) => item.id === model.provider)
354
+ if (!provider) {
355
+ provider = { id: model.provider, name: model.provider, type: model.api, baseUrl: model.baseUrl, models: [] }
356
+ providers.push(provider)
357
+ }
358
+ provider.models.push(sanitizeModel(model))
359
+ }
360
+ sendJson(res, 200, { providers })
364
361
  return
365
362
  }
366
363
 
@@ -373,7 +370,14 @@ export async function handleSharedConversationApi(req, res, url) {
373
370
  assertOperate(record)
374
371
  const body = await readJsonBody(req)
375
372
  await restoreAgent(record.sessionId)
376
- const result = await runPrompt(record.sessionId, messageFromBody(body, record, req), [], body?.command)
373
+ const result = await runPrompt(
374
+ record.sessionId,
375
+ messageFromBody(body, record, req),
376
+ [],
377
+ body?.command,
378
+ null,
379
+ { ...context, allowCloud: record.allowCloudUsage === true, source: 'shared' },
380
+ )
377
381
  sendJson(res, 200, result)
378
382
  return
379
383
  }
@@ -381,15 +385,15 @@ export async function handleSharedConversationApi(req, res, url) {
381
385
  if (req.method === 'POST' && action === 'model') {
382
386
  assertOperate(record)
383
387
  const body = await readJsonBody(req)
384
- const model = sanitizeIncomingModel(body?.model)
385
- if (!model) {
386
- const error = new Error('Missing model in request body')
387
- error.statusCode = 400
388
- throw error
389
- }
390
388
  await restoreAgent(record.sessionId)
391
- const configured = await readConfiguredModel(model)
392
- sendJson(res, 200, updateSessionModel(record.sessionId, configured ? sanitizeModel(configured) : model))
389
+ const currentModel = getSessionState(record.sessionId)?.model
390
+ const binding = await resolveModelBinding(body, {
391
+ context: { ...context, allowCloud: record.allowCloudUsage === true, source: 'shared' },
392
+ currentModel,
393
+ allowCurrentHidden: true,
394
+ legacySnapshot: body?.model,
395
+ })
396
+ sendJson(res, 200, updateSessionModel(record.sessionId, sanitizeModel(binding.model), binding.modelRef))
393
397
  return
394
398
  }
395
399
 
@@ -1,10 +1,14 @@
1
1
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
2
2
  import { readSessionValue } from '../storage.mjs'
3
- import { getLanUrls } from '../utils/network.mjs'
3
+ import { getLanUrls, isTailscaleAddress } from '../utils/network.mjs'
4
4
  import {
5
5
  createConversationShare,
6
+ deleteConversationShare,
6
7
  listConversationShares,
8
+ restoreConversationShare,
7
9
  revokeConversationShare,
10
+ updateConversationShare,
11
+ updateConversationShareExpiration,
8
12
  } from '../share-store.mjs'
9
13
 
10
14
  function localBaseUrl(req, port) {
@@ -46,6 +50,13 @@ export async function handleSharesApi(req, res, url, context = {}) {
46
50
  const passwordProvided = typeof body?.password === 'string'
47
51
  const password = passwordProvided ? body.password.trim() : undefined
48
52
  const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
53
+ const allowCloudUsage = permission === 'operate' && body?.allowCloudUsage === true
54
+ if (allowCloudUsage && context.isLocalRequest === false
55
+ && !(context.remoteAuthorized === true && isTailscaleAddress(context.remoteAddress))) {
56
+ const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
57
+ error.statusCode = 403
58
+ throw error
59
+ }
49
60
 
50
61
  const session = sessionId ? await readSessionValue(sessionId) : null
51
62
  if (!session) {
@@ -59,6 +70,7 @@ export async function handleSharesApi(req, res, url, context = {}) {
59
70
  permission,
60
71
  password: passwordProvided ? password : undefined,
61
72
  expiresAt,
73
+ allowCloudUsage,
62
74
  titleSnapshot: session.title,
63
75
  scope: session.scope,
64
76
  projectId: session.projectId,
@@ -77,6 +89,69 @@ export async function handleSharesApi(req, res, url, context = {}) {
77
89
  return
78
90
  }
79
91
 
92
+ if (parts.length === 4 && parts[0] === 'api' && parts[1] === 'shares') {
93
+ const shareId = decodeSegment(parts[2])
94
+ const action = parts[3]
95
+
96
+ if (req.method === 'POST' && action === 'disable') {
97
+ const share = await revokeConversationShare(shareId)
98
+ sendJson(res, 200, { ok: true, share })
99
+ return
100
+ }
101
+
102
+ if (req.method === 'POST' && action === 'restore') {
103
+ const body = await readJsonBody(req)
104
+ const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
105
+ const currentShares = await listConversationShares()
106
+ const current = currentShares.find((share) => share.id === shareId)
107
+ if (!current) {
108
+ const error = new Error('Share not found')
109
+ error.statusCode = 404
110
+ throw error
111
+ }
112
+ const session = current.sessionId ? await readSessionValue(current.sessionId) : null
113
+ if (!session) {
114
+ const error = new Error('Session not found')
115
+ error.statusCode = 404
116
+ throw error
117
+ }
118
+ const share = await restoreConversationShare(shareId, expiresAt)
119
+ sendJson(res, 200, { ok: true, share })
120
+ return
121
+ }
122
+
123
+ if (req.method === 'POST' && action === 'expiration') {
124
+ const body = await readJsonBody(req)
125
+ const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
126
+ const share = await updateConversationShareExpiration(shareId, expiresAt)
127
+ sendJson(res, 200, { ok: true, share })
128
+ return
129
+ }
130
+
131
+ if (req.method === 'POST' && action === 'update') {
132
+ const body = await readJsonBody(req)
133
+ const permission = body?.permission
134
+ const password = typeof body?.password === 'string' ? body.password : undefined
135
+ const expiresAt = typeof body?.expiresAt === 'string' && body.expiresAt ? body.expiresAt : undefined
136
+ const allowCloudUsage = body?.allowCloudUsage
137
+ if (allowCloudUsage === true && context.isLocalRequest === false
138
+ && !(context.remoteAuthorized === true && isTailscaleAddress(context.remoteAddress))) {
139
+ const error = new Error('QuickForge Cloud sharing can only be enabled locally or from an authorized Tailscale client.')
140
+ error.statusCode = 403
141
+ throw error
142
+ }
143
+ const share = await updateConversationShare(shareId, { permission, password, expiresAt, allowCloudUsage })
144
+ sendJson(res, 200, { ok: true, share })
145
+ return
146
+ }
147
+
148
+ if (req.method === 'DELETE' && action === 'permanent') {
149
+ const share = await deleteConversationShare(shareId)
150
+ sendJson(res, 200, { ok: true, share })
151
+ return
152
+ }
153
+ }
154
+
80
155
  if (req.method === 'DELETE' && parts.length === 3 && parts[0] === 'api' && parts[1] === 'shares') {
81
156
  const shareId = decodeSegment(parts[2])
82
157
  const share = await revokeConversationShare(shareId)