@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
@@ -6,7 +6,7 @@ import { logger } from './utils/logger.mjs'
6
6
  import { firstOptionalBoolean, firstString, parseFrontmatter, splitDelimitedList } from './frontmatter.mjs'
7
7
  import { normalizeAgentProfileThinkingLevel, normalizeCapabilityPolicy, normalizeModelReference, validateAgentProfileTools, validateModelReference } from './agent-profile-schema.mjs'
8
8
 
9
- const DEFAULT_MAX_RUNTIME_MS = 30 * 60 * 1000
9
+ const DEFAULT_MAX_RUNTIME_MS = 60 * 60 * 1000
10
10
  const DEFAULT_MAX_TOOL_CALLS = 300
11
11
  const nameRegex = /^[a-z][a-z0-9_-]{1,39}$/
12
12
  const allowedToolNames = new Set(['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command'])
@@ -66,14 +66,18 @@ function quoteFrontmatter(value) {
66
66
 
67
67
  function formatModelFrontmatter(model) {
68
68
  if (!model || model.mode !== 'fixed') return 'model:\n mode: inherit'
69
- return [
69
+ const result = [
70
70
  'model:',
71
71
  ' mode: fixed',
72
- ` provider: ${quoteFrontmatter(model.provider)}`,
73
- ` modelId: ${quoteFrontmatter(model.modelId)}`,
72
+ model.source && model.source !== 'legacy-custom' ? ` source: ${quoteFrontmatter(model.source)}` : '',
73
+ model.providerId ? ` providerId: ${quoteFrontmatter(model.providerId)}` : '',
74
+ model.catalogId ? ` catalogId: ${quoteFrontmatter(model.catalogId)}` : '',
75
+ model.provider ? ` provider: ${quoteFrontmatter(model.provider)}` : '',
76
+ model.modelId ? ` modelId: ${quoteFrontmatter(model.modelId)}` : '',
74
77
  model.api ? ` api: ${quoteFrontmatter(model.api)}` : '',
75
78
  model.baseUrl ? ` baseUrl: ${quoteFrontmatter(model.baseUrl)}` : '',
76
79
  ].filter(Boolean).join('\n')
80
+ return result
77
81
  }
78
82
 
79
83
  export function userAgentProfileFilePath(name) {
@@ -1,3 +1,5 @@
1
+ import { resolveModelBinding } from './model-catalog.mjs'
2
+
1
3
  export const AGENT_PROFILE_TOOL_NAMES = ['read_file', 'grep_files', 'write_file', 'edit_file', 'run_command']
2
4
  export const AGENT_PROFILE_MODEL_INHERIT = { mode: 'inherit' }
3
5
  export const AGENT_PROFILE_THINKING_LEVELS = ['inherit', 'off', 'low', 'medium', 'high', 'xhigh']
@@ -41,10 +43,6 @@ function normalizeString(value) {
41
43
  return typeof value === 'string' && value.trim() ? value.trim() : undefined
42
44
  }
43
45
 
44
- function sameBaseUrl(a, b) {
45
- return String(a || '').trim().replace(/\/$/, '') === String(b || '').trim().replace(/\/$/, '')
46
- }
47
-
48
46
  function readModelField(value, ...keys) {
49
47
  if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
50
48
  for (const key of keys) {
@@ -90,10 +88,16 @@ export function normalizeModelReference(input, metadata = {}) {
90
88
  const flatModelId = normalizeString(metadata['model-id'] ?? metadata.model_id ?? metadata.modelId)
91
89
  const flatApi = normalizeString(metadata['model-api'] ?? metadata.modelApi ?? metadata.api)
92
90
  const flatBaseUrl = normalizeString(metadata['model-base-url'] ?? metadata.modelBaseUrl ?? metadata.baseUrl)
91
+ const flatSource = normalizeString(metadata['model-source'] ?? metadata.modelSource)?.toLowerCase()
92
+ const flatProviderId = normalizeString(metadata['model-provider-id'] ?? metadata.modelProviderId ?? metadata.providerId)
93
+ const flatCatalogId = normalizeString(metadata['model-catalog-id'] ?? metadata.modelCatalogId ?? metadata.catalogId)
93
94
 
94
- if (flatMode === 'fixed' || flatProvider || flatModelId) {
95
+ if (flatMode === 'fixed' || flatSource || flatProvider || flatModelId || flatProviderId || flatCatalogId) {
95
96
  return {
96
97
  mode: 'fixed',
98
+ source: flatSource,
99
+ providerId: flatProviderId,
100
+ catalogId: flatCatalogId,
97
101
  provider: flatProvider,
98
102
  modelId: flatModelId,
99
103
  api: flatApi,
@@ -123,6 +127,9 @@ export function normalizeModelReference(input, metadata = {}) {
123
127
 
124
128
  return {
125
129
  mode: 'fixed',
130
+ source: readModelField(modelValue, 'source'),
131
+ providerId: readModelField(modelValue, 'providerId', 'provider-id', 'provider_id'),
132
+ catalogId: readModelField(modelValue, 'catalogId', 'catalog-id', 'catalog_id', 'quickforgeCatalogId'),
126
133
  provider: readModelField(modelValue, 'provider'),
127
134
  modelId: readModelField(modelValue, 'modelId', 'model-id', 'model_id', 'id', 'model'),
128
135
  api: readModelField(modelValue, 'api'),
@@ -133,9 +140,19 @@ export function normalizeModelReference(input, metadata = {}) {
133
140
  export function validateModelReference(modelRef) {
134
141
  const ref = modelRef || AGENT_PROFILE_MODEL_INHERIT
135
142
  if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
143
+ if (ref.source === 'cloud') {
144
+ const catalogId = ref.catalogId || ref.modelId
145
+ if (!catalogId) throw requestError('Fixed Cloud agent model requires catalogId')
146
+ return { mode: 'fixed', source: 'cloud', catalogId }
147
+ }
148
+ if (ref.source === 'custom') {
149
+ if (!ref.providerId || !ref.modelId) throw requestError('Fixed custom agent model requires providerId and modelId')
150
+ return { mode: 'fixed', source: 'custom', providerId: ref.providerId, modelId: ref.modelId }
151
+ }
136
152
  if (!ref.provider || !ref.modelId) throw requestError('Fixed agent model requires provider and modelId')
137
153
  return {
138
154
  mode: 'fixed',
155
+ source: 'legacy-custom',
139
156
  provider: ref.provider,
140
157
  modelId: ref.modelId,
141
158
  api: ref.api,
@@ -164,14 +181,17 @@ export function modelReferenceSnapshot(modelRef) {
164
181
  if (ref.mode !== 'fixed') return { ...AGENT_PROFILE_MODEL_INHERIT }
165
182
  return {
166
183
  mode: 'fixed',
167
- provider: ref.provider,
168
- modelId: ref.modelId,
184
+ ...(ref.source && ref.source !== 'legacy-custom' ? { source: ref.source } : {}),
185
+ ...(ref.providerId ? { providerId: ref.providerId } : {}),
186
+ ...(ref.catalogId ? { catalogId: ref.catalogId } : {}),
187
+ ...(ref.provider ? { provider: ref.provider } : {}),
188
+ ...(ref.modelId ? { modelId: ref.modelId } : {}),
169
189
  ...(ref.api ? { api: ref.api } : {}),
170
190
  ...(ref.baseUrl ? { baseUrl: ref.baseUrl } : {}),
171
191
  }
172
192
  }
173
193
 
174
- export async function resolveAgentProfileModel(profile, parentModel, readStore) {
194
+ export async function resolveAgentProfileModel(profile, parentModel, _readStore, context = {}) {
175
195
  const ref = profile?.model || AGENT_PROFILE_MODEL_INHERIT
176
196
  if (!ref || ref.mode !== 'fixed') {
177
197
  if (!parentModel) throw requestError('No active model is configured for the parent session.')
@@ -179,19 +199,23 @@ export async function resolveAgentProfileModel(profile, parentModel, readStore)
179
199
  }
180
200
 
181
201
  const validRef = validateModelReference(ref)
182
- const store = await readStore('custom-providers').catch(() => ({}))
183
- const providers = Array.isArray(store) ? store : Object.values(store || {})
184
- const configuredModels = providers
185
- .flatMap((provider) => Array.isArray(provider?.models) ? provider.models : [])
186
- .filter((model) => model && typeof model === 'object')
187
- const model = configuredModels.find((candidate) => (
188
- candidate.provider === validRef.provider &&
189
- candidate.id === validRef.modelId &&
190
- (!validRef.api || candidate.api === validRef.api) &&
191
- (!validRef.baseUrl || sameBaseUrl(candidate.baseUrl, validRef.baseUrl))
192
- ))
193
- if (!model) {
194
- throw requestError(`Agent profile ${profile?.name || ''} requested model ${validRef.provider}/${validRef.modelId}, but it is not configured.`)
195
- }
196
- return { model, info: { ...modelReferenceSnapshot(validRef), inherited: false } }
202
+ const modelRef = validRef.source === 'cloud'
203
+ ? { version: 1, source: 'cloud', catalogId: validRef.catalogId }
204
+ : validRef.source === 'custom'
205
+ ? { version: 1, source: 'custom', providerId: validRef.providerId, modelId: validRef.modelId }
206
+ : {
207
+ version: 1,
208
+ source: 'legacy-custom',
209
+ provider: validRef.provider,
210
+ modelId: validRef.modelId,
211
+ ...(validRef.api ? { api: validRef.api } : {}),
212
+ ...(validRef.baseUrl ? { baseUrl: validRef.baseUrl } : {}),
213
+ }
214
+ const binding = await resolveModelBinding({ modelRef }, {
215
+ context,
216
+ currentModel: parentModel,
217
+ allowCurrentHidden: true,
218
+ forExecution: true,
219
+ })
220
+ return { model: binding.model, modelRef: binding.modelRef, info: { ...modelReferenceSnapshot(validRef), inherited: false } }
197
221
  }
@@ -27,7 +27,7 @@ const BUILTIN_OVERRIDES_STORE = 'agent-profile-overrides'
27
27
  const RESERVED_NAMES = new Set(subagentDefinitions.map((definition) => definition.name))
28
28
  const allowedToolNames = new Set(AGENT_PROFILE_TOOL_NAMES)
29
29
  const nameRegex = /^[a-z][a-z0-9_-]{1,39}$/
30
- const DEFAULT_MAX_RUNTIME_MS = 30 * 60 * 1000
30
+ const DEFAULT_MAX_RUNTIME_MS = 60 * 60 * 1000
31
31
  const DEFAULT_MAX_TOOL_CALLS = 300
32
32
  const MIGRATION_MARKER_FILE = path.join(userAgentsDir, '.custom-agents-migrated.json')
33
33
  let customAgentMigrationPromise = null
@@ -75,7 +75,7 @@ function builtinProfileFromSubagent(definition, override = null) {
75
75
  allowedTools: [...definition.allowedTools],
76
76
  capabilityPolicy: definition.capabilityPolicy || inferCapabilityPolicy(definition.allowedTools),
77
77
  model: modelReferenceSnapshot(override?.model || definition.model),
78
- thinkingLevel: 'inherit',
78
+ thinkingLevel: normalizeAgentProfileThinkingLevel(override?.thinkingLevel || 'inherit'),
79
79
  lifecycle: 'builtin',
80
80
  managed: true,
81
81
  maxRuntimeMs: definition.maxRuntimeMs || DEFAULT_MAX_RUNTIME_MS,
@@ -308,17 +308,36 @@ export async function updateCustomAgentProfile(id, patch) {
308
308
  return { ...next, filePath: file, relativePath: `~/.quickforge/agents/${next.name}.md` }
309
309
  }
310
310
 
311
- export async function updateBuiltinAgentModelOverride(id, modelInput) {
311
+ export async function updateBuiltinAgentOverrides(id, patch = {}) {
312
312
  const current = await getAgentProfile(id)
313
313
  if (!current) throw requestError('Agent not found', 404)
314
- if (!current.builtin) throw requestError('Only built-in agents support model overrides', 400)
315
- const model = validateModelReference(normalizeModelReference(modelInput))
314
+ if (!current.builtin) throw requestError('Only built-in agents support overrides', 400)
316
315
  const overrides = await readStore(BUILTIN_OVERRIDES_STORE).catch(() => ({}))
317
- const next = { ...(overrides && typeof overrides === 'object' ? overrides : {}) }
318
- if (model.mode === 'fixed') next[current.name] = { model }
319
- else delete next[current.name]
316
+ const base = overrides && typeof overrides === 'object' ? overrides : {}
317
+ const next = { ...base }
318
+ const entry = { ...(base[current.name] && typeof base[current.name] === 'object' ? base[current.name] : {}) }
319
+
320
+ if (Object.prototype.hasOwnProperty.call(patch, 'model')) {
321
+ const model = validateModelReference(normalizeModelReference(patch.model))
322
+ if (model.mode === 'fixed') entry.model = model
323
+ else delete entry.model
324
+ }
325
+ if (Object.prototype.hasOwnProperty.call(patch, 'thinkingLevel')) {
326
+ const thinkingLevel = normalizeAgentProfileThinkingLevel(patch.thinkingLevel)
327
+ if (thinkingLevel === 'inherit') delete entry.thinkingLevel
328
+ else entry.thinkingLevel = thinkingLevel
329
+ }
330
+
331
+ if (Object.keys(entry).length === 0) delete next[current.name]
332
+ else next[current.name] = entry
320
333
  await writeStore(BUILTIN_OVERRIDES_STORE, next)
321
- return { ...current, model: modelReferenceSnapshot(model) }
334
+
335
+ const definition = subagentDefinitions.find((item) => item.name === current.name)
336
+ return builtinProfileFromSubagent(definition, next[current.name] || null)
337
+ }
338
+
339
+ export async function updateBuiltinAgentModelOverride(id, modelInput) {
340
+ return updateBuiltinAgentOverrides(id, { model: modelInput })
322
341
  }
323
342
 
324
343
  export async function deleteCustomAgentProfile(id) {
@@ -3,6 +3,8 @@ import path from 'node:path'
3
3
  import { AsyncLocalStorage } from 'node:async_hooks'
4
4
  import { randomUUID } from 'node:crypto'
5
5
  import { streamSimple } from '@earendil-works/pi-ai/compat'
6
+ import { resolveManagedCloudProvider } from './cloud/runtime.mjs'
7
+ import { ensureCloudChatIdempotencyKey } from './cloud/chat-idempotency-store.mjs'
6
8
  import {
7
9
  DEFAULT_AI_STREAM_DEADLINE_MS,
8
10
  withDefaultAiProviderOptions,
@@ -45,14 +47,27 @@ function writeAiHttpRecord(record) {
45
47
  })
46
48
  }
47
49
 
48
- function headersToRecord(headers) {
50
+ const SENSITIVE_HEADER_NAMES = new Set([
51
+ 'authorization',
52
+ 'proxy-authorization',
53
+ 'x-api-key',
54
+ 'api-key',
55
+ 'idempotency-key',
56
+ 'cookie',
57
+ 'set-cookie',
58
+ ])
59
+
60
+ export function sanitizeHttpHeaders(headers) {
49
61
  const result = {}
50
62
  if (!headers) return result
51
63
 
52
64
  try {
53
65
  const iterable = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers)
54
66
  for (const [key, value] of iterable) {
55
- result[String(key)] = Array.isArray(value) ? value.join(', ') : String(value)
67
+ const name = String(key)
68
+ result[name] = SENSITIVE_HEADER_NAMES.has(name.toLowerCase())
69
+ ? '[REDACTED]'
70
+ : Array.isArray(value) ? value.join(', ') : String(value)
56
71
  }
57
72
  } catch {
58
73
  // ignore malformed headers
@@ -61,6 +76,10 @@ function headersToRecord(headers) {
61
76
  return result
62
77
  }
63
78
 
79
+ function headersToRecord(headers) {
80
+ return sanitizeHttpHeaders(headers)
81
+ }
82
+
64
83
  function isRequest(value) {
65
84
  return typeof Request !== 'undefined' && value instanceof Request
66
85
  }
@@ -253,6 +272,63 @@ function wrapStreamWithDeadline(stream, deadlineController, deadlineMs) {
253
272
  }
254
273
  }
255
274
 
275
+ function lazyStream(streamPromise) {
276
+ return {
277
+ result: () => streamPromise.then((stream) => stream.result()),
278
+ [Symbol.asyncIterator]() {
279
+ let iteratorPromise
280
+ const iterator = () => {
281
+ iteratorPromise ||= streamPromise.then((stream) => stream[Symbol.asyncIterator]())
282
+ return iteratorPromise
283
+ }
284
+ return {
285
+ next: async () => (await iterator()).next(),
286
+ return: async () => {
287
+ const resolved = await iterator()
288
+ if (typeof resolved.return === 'function') return resolved.return()
289
+ return { value: undefined, done: true }
290
+ },
291
+ }
292
+ },
293
+ }
294
+ }
295
+
296
+ function createProviderStream(model, context, options) {
297
+ return aiHttpLogEnabled
298
+ ? aiHttpContext.run({
299
+ traceId: randomUUID(),
300
+ sessionId: options.sessionId,
301
+ purpose: options.metadata?.quickforgePurpose || 'chat',
302
+ provider: model?.provider,
303
+ api: model?.api,
304
+ model: model?.id,
305
+ }, () => streamSimple(model, context, options))
306
+ : streamSimple(model, context, options)
307
+ }
308
+
309
+ const CLIENT_MESSAGE_ID_FIELD = 'quickforgeClientMessageId'
310
+
311
+ function logicalMessageIdFromContext(context) {
312
+ const messages = Array.isArray(context?.messages) ? context.messages : []
313
+ for (let index = messages.length - 1; index >= 0; index--) {
314
+ const message = messages[index]
315
+ if (message?.role !== 'user' && message?.role !== 'user-with-attachments') continue
316
+ const metadata = message.metadata
317
+ const messageId = metadata && typeof metadata === 'object' && !Array.isArray(metadata)
318
+ ? metadata[CLIENT_MESSAGE_ID_FIELD]
319
+ : undefined
320
+ return typeof messageId === 'string' && messageId ? messageId : undefined
321
+ }
322
+ return undefined
323
+ }
324
+
325
+ async function managedCloudIdempotencyKey(context, options) {
326
+ const messageId = logicalMessageIdFromContext(context)
327
+ return options.sessionId && messageId
328
+ ? ensureCloudChatIdempotencyKey(options.sessionId, messageId)
329
+ : randomUUID()
330
+ }
331
+
256
332
  export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
257
333
  const providerOptions = withDefaultAiProviderOptions(options)
258
334
  const deadlineMs = Number.isFinite(providerOptions.deadlineMs)
@@ -265,16 +341,26 @@ export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
265
341
  }
266
342
  delete effectiveOptions.deadlineMs
267
343
 
268
- const stream = aiHttpLogEnabled
269
- ? aiHttpContext.run({
270
- traceId: randomUUID(),
271
- sessionId: effectiveOptions.sessionId,
272
- purpose: effectiveOptions.metadata?.quickforgePurpose || 'chat',
273
- provider: model?.provider,
274
- api: model?.api,
275
- model: model?.id,
276
- }, () => streamSimple(model, context, effectiveOptions))
277
- : streamSimple(model, context, effectiveOptions)
344
+ const managedCloud = model?.provider === 'quickforge-cloud' && model?.quickforgeModelSource === 'cloud'
345
+ const stream = managedCloud
346
+ ? lazyStream(Promise.all([
347
+ resolveManagedCloudProvider(model, effectiveOptions.signal),
348
+ managedCloudIdempotencyKey(context, effectiveOptions),
349
+ ]).then(([resolved, idempotencyKey]) => createProviderStream(
350
+ {
351
+ ...resolved.model,
352
+ headers: {
353
+ ...(resolved.model.headers || {}),
354
+ 'Idempotency-Key': idempotencyKey,
355
+ },
356
+ },
357
+ context,
358
+ {
359
+ ...effectiveOptions,
360
+ apiKey: resolved.apiKey,
361
+ },
362
+ )))
363
+ : createProviderStream(model, context, effectiveOptions)
278
364
 
279
365
  return wrapStreamWithDeadline(stream, deadlineController, deadlineMs)
280
366
  }
@@ -18,6 +18,14 @@ export const DEFAULT_AUTO_COMPACT_SETTINGS = {
18
18
  requireConfirmation: true,
19
19
  }
20
20
 
21
+ /**
22
+ * 保留尾部(最近回合)的字符预算上限。当会话以工具消息为主、user 回合
23
+ * 稀疏时,按 keepRecentTurns 从尾部回溯会跨越大量工具消息,导致压缩
24
+ * 范围过小甚至为空(tailStart 与 compactedUpToIndex 重合)。达到该预算
25
+ * 时按当前用户轮次起点截断,将更早的完整工具链历史并入压缩范围。
26
+ */
27
+ export const DEFAULT_MAX_TAIL_CHARS = 40000
28
+
21
29
  const AUTO_COMPACT_MIN_INTERVAL_MS = 30_000
22
30
  const AUTO_COMPACT_REJECTION_SUPPRESS_MS = 10 * 60_000
23
31
 
@@ -64,11 +72,13 @@ function contentToText(content) {
64
72
  }).filter(Boolean).join('\n')
65
73
  }
66
74
 
75
+ function messageChars(message) {
76
+ if (!message || typeof message !== 'object') return 0
77
+ return [message.role || '', contentToText(message.content), safeJson(message.attachments)].join('\n').length
78
+ }
79
+
67
80
  function estimateMessagesChars(messages) {
68
- return (Array.isArray(messages) ? messages : []).reduce((total, message) => {
69
- if (!message || typeof message !== 'object') return total
70
- return total + [message.role || '', contentToText(message.content), safeJson(message.attachments)].join('\n').length
71
- }, 0)
81
+ return (Array.isArray(messages) ? messages : []).reduce((total, message) => total + messageChars(message), 0)
72
82
  }
73
83
 
74
84
  function isUserMessage(message) {
@@ -76,13 +86,26 @@ function isUserMessage(message) {
76
86
  && !isCompactSummaryMessage(message)
77
87
  }
78
88
 
79
- function tailStartForRecentTurns(messages, keepRecentTurns) {
89
+ function isToolResultMessage(message) {
90
+ return message?.role === 'toolResult'
91
+ }
92
+
93
+ export function tailStartForRecentTurns(messages, keepRecentTurns, maxTailChars = DEFAULT_MAX_TAIL_CHARS) {
80
94
  const source = Array.isArray(messages) ? messages : []
81
95
  let seenUserTurns = 0
96
+ let tailChars = 0
82
97
  for (let index = source.length - 1; index >= 0; index--) {
83
- if (!isUserMessage(source[index])) continue
84
- seenUserTurns += 1
98
+ if (isUserMessage(source[index])) seenUserTurns += 1
99
+ tailChars += messageChars(source[index])
85
100
  if (seenUserTurns >= keepRecentTurns) return index
101
+ // 保留的尾部超过预算:仍按完整用户轮次切分,避免把当前运行中的
102
+ // assistant/toolResult 链从中间截断,导致压缩线落在一轮对话内部。
103
+ if (tailChars >= maxTailChars) {
104
+ for (let turnStart = index; turnStart >= 0; turnStart--) {
105
+ if (isUserMessage(source[turnStart])) return turnStart
106
+ }
107
+ return 0
108
+ }
86
109
  }
87
110
  return 0
88
111
  }
@@ -145,7 +168,11 @@ export function buildAutoCompactLoopMessages(session, messages) {
145
168
  if (!summaryMessage) return messages
146
169
  const source = Array.isArray(messages) ? messages : []
147
170
  const compactedUpToIndex = Math.min(source.length, Math.max(0, Number(session.contextCompaction?.compactedUpToIndex) || 0))
148
- return [summaryMessage, ...source.slice(compactedUpToIndex)]
171
+ // 防御旧持久化数据:compactedUpToIndex 可能指向 toolResult,跳过开头的孤儿,
172
+ // 避免压缩后的消息序列以 tool 消息开头(LLM API 会拒绝孤立 tool 消息)
173
+ let tailStart = compactedUpToIndex
174
+ while (tailStart < source.length && isToolResultMessage(source[tailStart])) tailStart += 1
175
+ return [summaryMessage, ...source.slice(tailStart)]
149
176
  }
150
177
 
151
178
  export async function compactSessionInPlace({
@@ -0,0 +1,96 @@
1
+ import { createHash, randomUUID } from 'node:crypto'
2
+ import { promises as fs } from 'node:fs'
3
+ import path from 'node:path'
4
+ import { ensureStorage, storageDir } from '../storage.mjs'
5
+
6
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
7
+ const MESSAGE_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/
8
+
9
+ function digest(value) {
10
+ return createHash('sha256').update(value).digest('hex')
11
+ }
12
+
13
+ function assertIdentifier(value, label, pattern) {
14
+ const normalized = typeof value === 'string' ? value.trim() : ''
15
+ if (!pattern.test(normalized)) throw new Error(`Invalid ${label}.`)
16
+ return normalized
17
+ }
18
+
19
+ export function createCloudChatIdempotencyStore({
20
+ directory = path.join(storageDir, 'security', 'cloud-chat-idempotency'),
21
+ ensureBaseStorage = ensureStorage,
22
+ } = {}) {
23
+ function entryPath(sessionId, messageId) {
24
+ return path.join(directory, digest(sessionId), `${digest(messageId)}.json`)
25
+ }
26
+
27
+ async function ensureDirectory(file) {
28
+ await ensureBaseStorage()
29
+ await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 })
30
+ }
31
+
32
+ async function readEntry(file, sessionId, messageId) {
33
+ try {
34
+ const raw = await fs.readFile(file, 'utf8')
35
+ const entry = raw.trim() ? JSON.parse(raw) : null
36
+ if (entry?.sessionId !== sessionId || entry?.messageId !== messageId || !UUID_PATTERN.test(entry?.idempotencyKey || '')) {
37
+ throw new Error('Invalid QuickForge Cloud chat idempotency record.')
38
+ }
39
+ return entry
40
+ } catch (error) {
41
+ if (error?.code === 'ENOENT') return null
42
+ throw error
43
+ }
44
+ }
45
+
46
+ async function ensure(sessionIdInput, messageIdInput) {
47
+ const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
48
+ const messageId = assertIdentifier(messageIdInput, 'logical message ID', MESSAGE_ID_PATTERN)
49
+ const file = entryPath(sessionId, messageId)
50
+ await ensureDirectory(file)
51
+
52
+ const existing = await readEntry(file, sessionId, messageId)
53
+ if (existing) return existing.idempotencyKey
54
+
55
+ const entry = {
56
+ schemaVersion: 1,
57
+ sessionId,
58
+ messageId,
59
+ idempotencyKey: randomUUID(),
60
+ createdAt: new Date().toISOString(),
61
+ }
62
+
63
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`
64
+ await fs.writeFile(temporary, `${JSON.stringify(entry, null, 2)}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 })
65
+ try { await fs.chmod(temporary, 0o600) } catch { /* best effort on Windows */ }
66
+ try {
67
+ await fs.link(temporary, file)
68
+ try { await fs.chmod(file, 0o600) } catch { /* best effort on Windows */ }
69
+ return entry.idempotencyKey
70
+ } catch (error) {
71
+ if (error?.code !== 'EEXIST') throw error
72
+ const concurrent = await readEntry(file, sessionId, messageId)
73
+ if (!concurrent) throw error
74
+ return concurrent.idempotencyKey
75
+ } finally {
76
+ await fs.rm(temporary, { force: true })
77
+ }
78
+ }
79
+
80
+ async function removeSession(sessionIdInput) {
81
+ const sessionId = assertIdentifier(sessionIdInput, 'session ID', /^.{1,512}$/)
82
+ await fs.rm(path.join(directory, digest(sessionId)), { recursive: true, force: true })
83
+ }
84
+
85
+ return { directory, ensure, removeSession }
86
+ }
87
+
88
+ const defaultStore = createCloudChatIdempotencyStore()
89
+
90
+ export function ensureCloudChatIdempotencyKey(sessionId, messageId) {
91
+ return defaultStore.ensure(sessionId, messageId)
92
+ }
93
+
94
+ export function deleteCloudChatIdempotencySession(sessionId) {
95
+ return defaultStore.removeSession(sessionId)
96
+ }
@@ -0,0 +1,99 @@
1
+ import { cloudEndpoint } from './config.mjs'
2
+
3
+ export class CloudApiError extends Error {
4
+ constructor(message, { status = 0, code = 'cloud_request_failed', retryable = false, details } = {}) {
5
+ super(message)
6
+ this.name = 'CloudApiError'
7
+ this.status = status
8
+ this.code = code
9
+ this.retryable = retryable
10
+ this.details = details
11
+ }
12
+ }
13
+
14
+ async function parseError(response) {
15
+ let payload
16
+ try { payload = await response.json() } catch { payload = undefined }
17
+ const error = payload?.error
18
+ const structured = error && typeof error === 'object' ? error : undefined
19
+ const oauthCode = typeof error === 'string' ? error : undefined
20
+ return new CloudApiError(
21
+ structured?.message || payload?.error_description || `QuickForge Cloud request failed (${response.status}).`,
22
+ {
23
+ status: response.status,
24
+ code: structured?.code || payload?.code || oauthCode || 'cloud_request_failed',
25
+ retryable: Boolean(structured?.retryable || payload?.retryable),
26
+ details: structured?.details || payload?.details,
27
+ },
28
+ )
29
+ }
30
+
31
+ function timeoutSignal(timeoutMs, outerSignal) {
32
+ const timeout = AbortSignal.timeout(timeoutMs)
33
+ return outerSignal ? AbortSignal.any([outerSignal, timeout]) : timeout
34
+ }
35
+
36
+ export class CloudClient {
37
+ constructor({ baseUrl, timeoutMs = 10_000, fetchImpl = fetch } = {}) {
38
+ if (!(baseUrl instanceof URL)) throw new Error('CloudClient requires a configured base URL.')
39
+ this.baseUrl = baseUrl
40
+ this.timeoutMs = timeoutMs
41
+ this.fetch = fetchImpl
42
+ }
43
+
44
+ async request(pathname, { method = 'GET', token, body, headers, signal, raw = false, timeout = true } = {}) {
45
+ const requestHeaders = { Accept: 'application/json', ...headers }
46
+ if (body !== undefined) requestHeaders['Content-Type'] = 'application/json'
47
+ if (token) requestHeaders.Authorization = `Bearer ${token}`
48
+ const response = await this.fetch(cloudEndpoint(this.baseUrl, pathname), {
49
+ method,
50
+ headers: requestHeaders,
51
+ body: body === undefined ? undefined : JSON.stringify(body),
52
+ signal: timeout ? timeoutSignal(this.timeoutMs, signal) : signal,
53
+ redirect: 'error',
54
+ })
55
+ if (!response.ok) throw await parseError(response)
56
+ if (raw) return response
57
+ if (response.status === 204) return undefined
58
+ return response.json()
59
+ }
60
+
61
+ health(signal) { return this.request('healthz', { signal }) }
62
+ ready(signal) { return this.request('readyz', { signal }) }
63
+ refresh(refreshToken, signal) {
64
+ return this.request('oauth/token', {
65
+ method: 'POST', body: { grantType: 'refresh_token', refreshToken }, signal,
66
+ })
67
+ }
68
+ authorizeDevice({ installationId, clientId = 'quickforge-desktop', publicKey, installationName, platform, clientVersion, signal } = {}) {
69
+ return this.request('oauth/device_authorization', {
70
+ method: 'POST',
71
+ body: { installationId, clientId, publicKey, installationName, platform, clientVersion },
72
+ signal,
73
+ })
74
+ }
75
+ exchangeDeviceCode(deviceCode, clientId = 'quickforge-desktop', signal) {
76
+ return this.request('oauth/token', {
77
+ method: 'POST',
78
+ body: {
79
+ grantType: 'urn:ietf:params:oauth:grant-type:device_code',
80
+ deviceCode,
81
+ clientId,
82
+ },
83
+ signal,
84
+ })
85
+ }
86
+ me(token, signal) { return this.request('v1/me', { token, signal }) }
87
+ models(token, signal) { return this.request('v1/models', { token, signal }) }
88
+ usage(token, signal) { return this.request('v1/usage', { token, signal }) }
89
+ installations(token, signal) { return this.request('v1/installations', { token, signal }) }
90
+ revokeInstallation(token, installationId, signal) {
91
+ return this.request(`v1/installations/${encodeURIComponent(installationId)}`, { method: 'DELETE', token, signal })
92
+ }
93
+ chat(token, payload, idempotencyKey, signal) {
94
+ return this.request('v1/chat/completions', {
95
+ method: 'POST', token, body: payload,
96
+ headers: { 'Idempotency-Key': idempotencyKey }, signal, raw: true, timeout: false,
97
+ })
98
+ }
99
+ }