@shawnstack/quickforge 1.7.3 → 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 (267) hide show
  1. package/README.md +1 -1
  2. package/bin/quickforge.mjs +0 -21
  3. package/dist/assets/AgentProfilesPage-XNjuqJs7.js +1 -0
  4. package/dist/assets/ChatPanelHost-CuF-PZpc.js +292 -0
  5. package/dist/assets/CloudAccountSettingsPage-GJSoi6sO.js +1 -0
  6. package/dist/assets/PluginsPage-BQgp0qN-.js +1 -0
  7. package/dist/assets/ScheduledTasksPage-DdekATzq.js +2 -0
  8. package/dist/assets/SettingsWorkspacePage-Cb54Ic91.js +1639 -0
  9. package/dist/assets/ShareLinksSettingsPage-C75WJpaF.js +1 -0
  10. package/dist/assets/SharedConversationPage-CjMtSQTW.js +1 -0
  11. package/dist/assets/TerminalDock-D4zJBJc4.js +2 -0
  12. package/dist/assets/WorkspaceInspector-D9kdnLDd.js +36 -0
  13. package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-CMhVoYUh.js} +1 -1
  14. package/dist/assets/{arc-Bhg4c0ht.js → arc-w21O0TLn.js} +1 -1
  15. package/dist/assets/architecture-TIHT7OUA-DMwFMHxq.js +1 -0
  16. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-C52qL6Hg.js} +1 -1
  17. package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-CM3XbLol.js} +1 -1
  18. package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-D7Bh1ui1.js} +1 -1
  19. package/dist/assets/channel-Duu1CuH9.js +1 -0
  20. package/dist/assets/chunk-2Q5K7J3B-B6rHWc4S.js +1 -0
  21. package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-xgrtbT8e.js} +1 -1
  22. package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-CDbaHw80.js} +1 -1
  23. package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-CcSvVbPc.js} +1 -1
  24. package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-erFpzzgx.js} +1 -1
  25. package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-rEhN9F6G.js} +1 -1
  26. package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-D8L-QTwD.js} +1 -1
  27. package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-CjlfMBbM.js} +1 -1
  28. package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-CCcsI9XE.js} +1 -1
  29. package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-FSqFbmb3.js} +1 -1
  30. package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-ZMLUvfLy.js} +1 -1
  31. package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-DZuBpk25.js} +1 -1
  32. package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-CB26wsG0.js} +1 -1
  33. package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BbsM8oRw.js} +1 -1
  34. package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-BcQhaqt7.js} +1 -1
  35. package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-DrnrdrSu.js} +2 -2
  36. package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-tdc-Z9eW.js} +1 -1
  37. package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-Cd7igVGt.js} +1 -1
  38. package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-iXXGW8nf.js} +1 -1
  39. package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-BZ87mXEv.js} +1 -1
  40. package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-CLTjV5ov.js} +1 -1
  41. package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-B3SCBrRH.js} +1 -1
  42. package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-TgvkXkFX.js} +1 -1
  43. package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-B9E2E5v1.js} +1 -1
  44. package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-C5GMVYup.js} +1 -1
  45. package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-fo5-HsqY.js} +1 -1
  46. package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Dwfg8sKc.js} +1 -1
  47. package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-DpBrv4ni.js} +1 -1
  48. package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-DzzmTl11.js} +1 -1
  49. package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-Dw6aviKa.js} +1 -1
  50. package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-CEDazV6w.js} +1 -1
  51. package/dist/assets/chunk-VAUOI2AC-CWYjg8zE.js +1 -0
  52. package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-C_-eEj-9.js} +1 -1
  53. package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-NF-MsJJR.js} +3 -3
  54. package/dist/assets/chunk-XXDRQBXY-DzTwx8ZH.js +1 -0
  55. package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-DqzqtdII.js} +1 -1
  56. package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CRrAYi4g.js} +1 -1
  57. package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-Dc1s5bLM.js} +1 -1
  58. package/dist/assets/classDiagram-OUVF2IWQ-Cc63fLWP.js +1 -0
  59. package/dist/assets/classDiagram-v2-EOCWNBFH-CoXU0qT6.js +1 -0
  60. package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-cL247pHg.js} +1 -1
  61. package/dist/assets/cynefin-VYW2F7L2-C8yCvH_o.js +1 -0
  62. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-D4sZ7LYI.js} +1 -1
  63. package/dist/assets/{dagre-CY3D84Cl.js → dagre-DmHb3Ctt.js} +1 -1
  64. package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-BlsnSh-Q.js} +1 -1
  65. package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-DW1yQiRO.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-BTUAsFjb.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-BcaomqSd.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DDLX7Xwr.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-CIave0An.js} +1 -1
  70. package/dist/assets/dist-DLKC_odd.js +1 -0
  71. package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-Cr1Y6lPu.js} +1 -1
  72. package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-D3EgiVVH.js} +1 -1
  73. package/dist/assets/esm-Df4edDpj.js +2 -0
  74. package/dist/assets/eventmodeling-45OFAUF4-DO1vRTM0.js +1 -0
  75. package/dist/assets/flowDiagram-23GEKE2U-BS7KXcqn.js +1 -0
  76. package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-B0oyDIqK.js} +1 -1
  77. package/dist/assets/gitGraph-TEB2WS4Q-m5F5ef09.js +1 -0
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-uQtr6IqK.js} +1 -1
  79. package/dist/assets/icons-CXPP1fFT.js +1 -0
  80. package/dist/assets/index-_ZhK6bvp.js +65 -0
  81. package/dist/assets/index-iVV2bP_f.css +3 -0
  82. package/dist/assets/info-DKCQHKI2-CZxPlQJ0.js +1 -0
  83. package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B_GzlLIM.js} +1 -1
  84. package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKQIsSdU.js} +1 -1
  85. package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-DrrvzZL1.js} +1 -1
  86. package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-CJQRTdXz.js} +1 -1
  87. package/dist/assets/{line-ikKkQiFp.js → line-B4DxX7tC.js} +1 -1
  88. package/dist/assets/{linear-GqPyxQkN.js → linear-U-nKUhyW.js} +1 -1
  89. package/dist/assets/{map-CJwjCvwr.js → map-BneTM-ri.js} +1 -1
  90. package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-BUSPvaqI.js} +2 -2
  91. package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-BlKWrEdZ.js} +2 -2
  92. package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-jEbqFaTe.js} +2 -2
  93. package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DWpShXCs.js} +3 -3
  94. package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-CEVwZD78.js} +1 -1
  95. package/dist/assets/model-identity-CgA6_Pbm.js +1 -0
  96. package/dist/assets/model-reference-o41fJ6jQ.js +1 -0
  97. package/dist/assets/{monaco-BTsVCDWS.js → monaco-D6OhXgjg.js} +1 -1
  98. package/dist/assets/{ordinal-CibZU3PM.js → ordinal-B2r5L1v4.js} +1 -1
  99. package/dist/assets/{otel-BNGM50C-.js → otel-BFnphzjt.js} +1 -1
  100. package/dist/assets/packet-7NZHBO7P-zIioIZVN.js +1 -0
  101. package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CBLkBdnQ.js} +1 -1
  102. package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-Cr2ee4a0.js} +2 -2
  103. package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-DV5jKjSg.js} +1 -1
  104. package/dist/assets/pie-RZYD4A2V-GGWijBvB.js +1 -0
  105. package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-8tCnwtWS.js} +1 -1
  106. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-CWd28cHF.js} +1 -1
  107. package/dist/assets/radar-I7S5WNFK-yvwFH3RV.js +1 -0
  108. package/dist/assets/railroad-3IZDKUUU-BVadlswB.js +1 -0
  109. package/dist/assets/railroad-abnf-AHOZXSZD-B7R-p_2-.js +1 -0
  110. package/dist/assets/railroad-ebnf-EBAXGLYW-BnbJxPug.js +1 -0
  111. package/dist/assets/railroad-peg-LSFZ7HO6-Ci3DcEuQ.js +1 -0
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-CI4ouOBs.js} +1 -1
  113. package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-1KC5PyRx.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE-DtFz7sw4.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB--VeOwwk9.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-B8AHk5Oe.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-vV_ArM8D.js} +1 -1
  118. package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-BThnIUBs.js} +3 -1
  119. package/dist/assets/{src-B4Tvg3gT.js → src-PYsDme0X.js} +1 -1
  120. package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-etXnJ8X8.js} +1 -1
  121. package/dist/assets/stateDiagram-v2-6OUMAXLB-Dms3pICj.js +1 -0
  122. package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-pabIKAmn.js} +2 -2
  123. package/dist/assets/swimlanesDiagram-G3AALYLV-B14aWjv6.js +8 -0
  124. package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-C55MKm3L.js} +1 -1
  125. package/dist/assets/treeView-QDETBFTQ-Cru9B2zv.js +1 -0
  126. package/dist/assets/treemap-6X3UGDF4-DNOVHFaJ.js +1 -0
  127. package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CgNf1Q9W.js} +1 -1
  128. package/dist/assets/wardley-OPB4EBWU-E3m1BOJQ.js +1 -0
  129. package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-Drt5ytec.js} +1 -1
  130. package/dist/assets/web-DoV8MSMn.js +1 -0
  131. package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-hhoEo_Ar.js} +1 -1
  132. package/dist/index.html +13 -11
  133. package/dist/mobile-error.html +24 -0
  134. package/package.json +6 -1
  135. package/plugins/{openai-documents → documents}/plugin.json +2 -2
  136. package/plugins/{openai-presentations → presentations}/plugin.json +2 -2
  137. package/plugins/{openai-spreadsheets → spreadsheets}/plugin.json +2 -2
  138. package/server/acp/server.mjs +50 -35
  139. package/server/agent-manager.mjs +343 -46
  140. package/server/agent-profile-files.mjs +8 -4
  141. package/server/agent-profile-schema.mjs +47 -23
  142. package/server/agent-profiles.mjs +1 -1
  143. package/server/ai-http-logger.mjs +156 -13
  144. package/server/ai-provider-options.mjs +3 -0
  145. package/server/channels/channel-logs.mjs +13 -0
  146. package/server/channels/event-relay.mjs +33 -0
  147. package/server/channels/process-channel.mjs +29 -0
  148. package/server/channels/providers/wechat.mjs +9 -3
  149. package/server/channels/registry.mjs +5 -1
  150. package/server/cloud/chat-idempotency-store.mjs +96 -0
  151. package/server/cloud/client.mjs +102 -0
  152. package/server/cloud/config.mjs +53 -0
  153. package/server/cloud/credential-store.mjs +193 -0
  154. package/server/cloud/identity.mjs +355 -0
  155. package/server/cloud/index.mjs +7 -0
  156. package/server/cloud/models.mjs +80 -0
  157. package/server/cloud/runtime.mjs +129 -0
  158. package/server/cloud/service-config.mjs +77 -0
  159. package/server/index.mjs +63 -33
  160. package/server/lan-access-store.mjs +93 -3
  161. package/server/model-catalog.mjs +262 -0
  162. package/server/network-proxy.mjs +68 -3
  163. package/server/plugins/registry.mjs +93 -9
  164. package/server/public-api.mjs +6 -2
  165. package/server/routes/agent-profiles.mjs +43 -9
  166. package/server/routes/agent.mjs +48 -11
  167. package/server/routes/backup.mjs +7 -1
  168. package/server/routes/channels.mjs +47 -0
  169. package/server/routes/cloud.mjs +227 -0
  170. package/server/routes/lan-access.mjs +246 -21
  171. package/server/routes/models.mjs +13 -4
  172. package/server/routes/project.mjs +19 -1
  173. package/server/routes/scheduled-tasks.mjs +40 -10
  174. package/server/routes/shared-conversation.mjs +62 -58
  175. package/server/routes/shares.mjs +76 -1
  176. package/server/routes/static.mjs +26 -1
  177. package/server/routes/storage.mjs +24 -1
  178. package/server/routes/system.mjs +11 -0
  179. package/server/routes/terminal.mjs +7 -2
  180. package/server/routes/workspace.mjs +66 -13
  181. package/server/session-utils.mjs +2 -2
  182. package/server/share-store.mjs +168 -13
  183. package/server/skills.mjs +30 -3
  184. package/server/storage.mjs +7 -1
  185. package/server/subagents.mjs +4 -4
  186. package/server/terminal/terminal-manager.mjs +2 -4
  187. package/server/tools/definitions.mjs +3 -3
  188. package/server/tools/index.mjs +7 -5
  189. package/server/utils/network.mjs +9 -0
  190. package/server/utils/package-update.mjs +70 -47
  191. package/server/utils/process-env.mjs +7 -0
  192. package/server/utils/response.mjs +10 -3
  193. package/skills/skill-creator/LICENSE.txt +202 -0
  194. package/skills/skill-creator/SKILL.md +485 -0
  195. package/skills/skill-creator/UPSTREAM.md +6 -0
  196. package/skills/skill-creator/agents/analyzer.md +274 -0
  197. package/skills/skill-creator/agents/comparator.md +202 -0
  198. package/skills/skill-creator/agents/grader.md +223 -0
  199. package/skills/skill-creator/assets/eval_review.html +146 -0
  200. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  201. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  202. package/skills/skill-creator/references/schemas.md +430 -0
  203. package/skills/skill-creator/scripts/__init__.py +0 -0
  204. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  205. package/skills/skill-creator/scripts/generate_report.py +326 -0
  206. package/skills/skill-creator/scripts/improve_description.py +247 -0
  207. package/skills/skill-creator/scripts/package_skill.py +136 -0
  208. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  209. package/skills/skill-creator/scripts/run_eval.py +310 -0
  210. package/skills/skill-creator/scripts/run_loop.py +328 -0
  211. package/skills/skill-creator/scripts/utils.py +47 -0
  212. package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
  213. package/dist/assets/ChatPanelHost-wfGCaCzD.js +0 -288
  214. package/dist/assets/PluginsPage-NAGroBm3.js +0 -1
  215. package/dist/assets/ScheduledTasksPage-Bp0MNfQD.js +0 -2
  216. package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
  217. package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
  218. package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
  219. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
  220. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
  221. package/dist/assets/channel-Rz20dKTK.js +0 -1
  222. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
  223. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
  224. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
  225. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
  226. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
  227. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
  228. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
  229. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
  230. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
  231. package/dist/assets/icons-BP8YOS-Z.js +0 -1
  232. package/dist/assets/index-C6k5taeb.js +0 -66
  233. package/dist/assets/index-CzN8NSKC.css +0 -3
  234. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
  235. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
  236. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
  237. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
  238. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
  239. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
  240. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
  241. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
  242. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
  243. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
  244. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
  245. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
  246. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
  247. /package/dist/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
  248. /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-jrz1pRUS.js} +0 -0
  249. /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-D6N-CrJv.js} +0 -0
  250. /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
  251. /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-DyfqweF2.js} +0 -0
  252. /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/assets/{dist-DelZBZpK.js → dist-CLg13NtV.js} +0 -0
  254. /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-DJkmx0Cq.js} +0 -0
  255. /package/dist/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
  256. /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
  257. /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
  258. /package/dist/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
  259. /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-Uu2xAB4d.js} +0 -0
  260. /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-Bud4g4fh.js} +0 -0
  261. /package/dist/assets/{xterm-CGiQzDiR.js → xterm-Bm_IbozX.js} +0 -0
  262. /package/plugins/{openai-documents → documents}/commands/document.md +0 -0
  263. /package/plugins/{openai-documents → documents}/skills/documents/SKILL.md +0 -0
  264. /package/plugins/{openai-presentations → presentations}/commands/presentation.md +0 -0
  265. /package/plugins/{openai-presentations → presentations}/skills/presentations/SKILL.md +0 -0
  266. /package/plugins/{openai-spreadsheets → spreadsheets}/commands/spreadsheet.md +0 -0
  267. /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
@@ -3,7 +3,12 @@ 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 { withDefaultAiProviderOptions } from './ai-provider-options.mjs'
6
+ import { resolveManagedCloudProvider } from './cloud/runtime.mjs'
7
+ import { ensureCloudChatIdempotencyKey } from './cloud/chat-idempotency-store.mjs'
8
+ import {
9
+ DEFAULT_AI_STREAM_DEADLINE_MS,
10
+ withDefaultAiProviderOptions,
11
+ } from './ai-provider-options.mjs'
7
12
  import { logsDir } from './storage.mjs'
8
13
 
9
14
  const PATCH_MARKER = Symbol.for('quickforge.aiHttpLogger.fetchPatched')
@@ -42,14 +47,27 @@ function writeAiHttpRecord(record) {
42
47
  })
43
48
  }
44
49
 
45
- 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) {
46
61
  const result = {}
47
62
  if (!headers) return result
48
63
 
49
64
  try {
50
65
  const iterable = typeof headers.entries === 'function' ? headers.entries() : Object.entries(headers)
51
66
  for (const [key, value] of iterable) {
52
- 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)
53
71
  }
54
72
  } catch {
55
73
  // ignore malformed headers
@@ -58,6 +76,10 @@ function headersToRecord(headers) {
58
76
  return result
59
77
  }
60
78
 
79
+ function headersToRecord(headers) {
80
+ return sanitizeHttpHeaders(headers)
81
+ }
82
+
61
83
  function isRequest(value) {
62
84
  return typeof Request !== 'undefined' && value instanceof Request
63
85
  }
@@ -206,18 +228,139 @@ export function installAiHttpLogger() {
206
228
  globalThis[PATCH_MARKER] = true
207
229
  }
208
230
 
231
+ function combineAbortSignals(parentSignal, deadlineSignal) {
232
+ if (!parentSignal) return deadlineSignal
233
+ if (typeof AbortSignal.any === 'function') return AbortSignal.any([parentSignal, deadlineSignal])
234
+
235
+ const controller = new AbortController()
236
+ const abort = () => controller.abort()
237
+ if (parentSignal.aborted || deadlineSignal.aborted) {
238
+ controller.abort()
239
+ } else {
240
+ parentSignal.addEventListener('abort', abort, { once: true })
241
+ deadlineSignal.addEventListener('abort', abort, { once: true })
242
+ }
243
+ return controller.signal
244
+ }
245
+
246
+ function wrapStreamWithDeadline(stream, deadlineController, deadlineMs) {
247
+ let timer
248
+ const deadlinePromise = new Promise((_, reject) => {
249
+ timer = setTimeout(() => {
250
+ deadlineController.abort()
251
+ reject(new Error(`AI stream timed out after ${deadlineMs}ms`))
252
+ }, deadlineMs)
253
+ timer.unref?.()
254
+ })
255
+ const resultPromise = Promise.race([stream.result(), deadlinePromise])
256
+ .finally(() => clearTimeout(timer))
257
+ resultPromise.catch(() => {})
258
+
259
+ return {
260
+ result: () => resultPromise,
261
+ [Symbol.asyncIterator]() {
262
+ const iterator = stream[Symbol.asyncIterator]()
263
+ return {
264
+ next: () => Promise.race([iterator.next(), deadlinePromise]),
265
+ return: async () => {
266
+ clearTimeout(timer)
267
+ if (typeof iterator.return === 'function') return iterator.return()
268
+ return { value: undefined, done: true }
269
+ },
270
+ }
271
+ },
272
+ }
273
+ }
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
+
209
332
  export function streamSimpleWithAiHttpLogging(model, context, options = {}) {
210
333
  const providerOptions = withDefaultAiProviderOptions(options)
211
- if (!aiHttpLogEnabled) return streamSimple(model, context, providerOptions)
212
-
213
- const traceContext = {
214
- traceId: randomUUID(),
215
- sessionId: providerOptions.sessionId,
216
- purpose: providerOptions.metadata?.quickforgePurpose || 'chat',
217
- provider: model?.provider,
218
- api: model?.api,
219
- model: model?.id,
334
+ const deadlineMs = Number.isFinite(providerOptions.deadlineMs)
335
+ ? Math.max(1, providerOptions.deadlineMs)
336
+ : DEFAULT_AI_STREAM_DEADLINE_MS
337
+ const deadlineController = new AbortController()
338
+ const effectiveOptions = {
339
+ ...providerOptions,
340
+ signal: combineAbortSignals(providerOptions.signal, deadlineController.signal),
220
341
  }
342
+ delete effectiveOptions.deadlineMs
343
+
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)
221
364
 
222
- return aiHttpContext.run(traceContext, () => streamSimple(model, context, providerOptions))
365
+ return wrapStreamWithDeadline(stream, deadlineController, deadlineMs)
223
366
  }
@@ -1,8 +1,11 @@
1
1
  export const DEFAULT_AI_MAX_RETRIES = 3
2
+ export const DEFAULT_AI_HTTP_TIMEOUT_MS = 2 * 60 * 1000
3
+ export const DEFAULT_AI_STREAM_DEADLINE_MS = 5 * 60 * 1000
2
4
 
3
5
  export function withDefaultAiProviderOptions(options = {}) {
4
6
  return {
5
7
  maxRetries: DEFAULT_AI_MAX_RETRIES,
8
+ timeoutMs: DEFAULT_AI_HTTP_TIMEOUT_MS,
6
9
  ...options,
7
10
  }
8
11
  }
@@ -0,0 +1,13 @@
1
+ import path from 'node:path'
2
+
3
+ function safeChannelId(channelId) {
4
+ const safeId = String(channelId || '')
5
+ .trim()
6
+ .replace(/[^a-zA-Z0-9_-]+/g, '-')
7
+ .replace(/^-+|-+$/g, '')
8
+ return safeId || 'channel'
9
+ }
10
+
11
+ export function channelLogsDirectory(logsDir, channelId) {
12
+ return path.join(path.resolve(logsDir), 'channels', safeChannelId(channelId))
13
+ }
@@ -0,0 +1,33 @@
1
+ const CHANNEL_EVENTS_URL_ENV = 'QUICKFORGE_CHANNEL_EVENTS_URL'
2
+ const CHANNEL_EVENT_ACTION = 'channel-event'
3
+ const RELAY_TIMEOUT_MS = 3000
4
+
5
+ export async function publishChannelSessionChanged(event) {
6
+ const url = process.env[CHANNEL_EVENTS_URL_ENV]
7
+ if (!url) return false
8
+
9
+ const controller = new AbortController()
10
+ const timer = setTimeout(() => controller.abort(), RELAY_TIMEOUT_MS)
11
+ timer.unref?.()
12
+
13
+ try {
14
+ const response = await fetch(url, {
15
+ method: 'POST',
16
+ headers: {
17
+ 'content-type': 'application/json',
18
+ 'x-quickforge-action': CHANNEL_EVENT_ACTION,
19
+ },
20
+ body: JSON.stringify({
21
+ ...event,
22
+ type: 'sessions-changed',
23
+ timestamp: event.timestamp || new Date().toISOString(),
24
+ }),
25
+ signal: controller.signal,
26
+ })
27
+ return response.ok
28
+ } catch {
29
+ return false
30
+ } finally {
31
+ clearTimeout(timer)
32
+ }
33
+ }
@@ -1,7 +1,11 @@
1
1
  /* eslint-disable no-control-regex -- ANSI escape matching intentionally includes ESC. */
2
2
  import { spawn } from 'node:child_process'
3
+ import { promises as fs } from 'node:fs'
4
+ import path from 'node:path'
3
5
  import { EventEmitter } from 'node:events'
4
6
  import { setTimeout as delay } from 'node:timers/promises'
7
+ import { channelLogsDirectory } from './channel-logs.mjs'
8
+ import { logger } from '../utils/logger.mjs'
5
9
 
6
10
  const DEFAULT_LOG_LIMIT = 300
7
11
  const STOP_TIMEOUT_MS = 5000
@@ -55,6 +59,9 @@ export class ProcessChannelProvider extends EventEmitter {
55
59
  super()
56
60
  this.definition = definition
57
61
  this.logLimit = options.logLimit || DEFAULT_LOG_LIMIT
62
+ this.logsDir = options.logsDir
63
+ this.logWriteErrorReported = false
64
+ this.logWritePromise = Promise.resolve()
58
65
  this.status = 'stopped'
59
66
  this.process = null
60
67
  this.pid = null
@@ -118,6 +125,27 @@ export class ProcessChannelProvider extends EventEmitter {
118
125
  this.emitEvent('status', { status, snapshot: this.snapshot() })
119
126
  }
120
127
 
128
+ persistLog(entry) {
129
+ if (!this.logsDir) return
130
+ const directory = channelLogsDirectory(this.logsDir, this.definition.id)
131
+ const filePath = path.join(directory, `channel-${entry.time.slice(0, 10)}.log`)
132
+ const line = `${JSON.stringify({ time: entry.time, stream: entry.stream, text: entry.text })}\n`
133
+ this.logWritePromise = this.logWritePromise
134
+ .then(() => fs.mkdir(directory, { recursive: true }))
135
+ .then(() => fs.appendFile(filePath, line, 'utf8'))
136
+ .then(() => {
137
+ this.logWriteErrorReported = false
138
+ })
139
+ .catch((error) => {
140
+ if (this.logWriteErrorReported) return
141
+ this.logWriteErrorReported = true
142
+ logger.warn('Failed to persist channel log.', {
143
+ channelId: this.definition.id,
144
+ error: error instanceof Error ? error.message : String(error),
145
+ })
146
+ })
147
+ }
148
+
121
149
  addLog(stream, text) {
122
150
  if (!text) return
123
151
  const entry = {
@@ -131,6 +159,7 @@ export class ProcessChannelProvider extends EventEmitter {
131
159
  this.logs.splice(0, this.logs.length - this.logLimit)
132
160
  }
133
161
  this.emitEvent('log', { log: entry })
162
+ this.persistLog(entry)
134
163
  }
135
164
 
136
165
  addProcessText(stream, text) {
@@ -4,6 +4,7 @@ import { promises as fs } from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { ProcessChannelProvider } from '../process-channel.mjs'
6
6
  import { getDefaultWorkspaceRoot, readProjectConfig } from '../../project-config.mjs'
7
+ import { createNodeProcessEnv } from '../../utils/process-env.mjs'
7
8
 
8
9
  const WEIXIN_ACP_PACKAGE = 'weixin-acp'
9
10
  const MIN_NODE_MAJOR = 22
@@ -134,7 +135,7 @@ function runCommand(command, args, options = {}) {
134
135
  }
135
136
 
136
137
  export class WechatChannelProvider extends ProcessChannelProvider {
137
- constructor({ projectRoot }) {
138
+ constructor({ projectRoot, channelEventsUrl, logsDir }) {
138
139
  super({
139
140
  id: 'wechat',
140
141
  name: '微信',
@@ -149,8 +150,9 @@ export class WechatChannelProvider extends ProcessChannelProvider {
149
150
  { id: 'relogin', label: '重新登录' },
150
151
  ],
151
152
  requirements: [`Node.js >= ${MIN_NODE_MAJOR}`, 'npm/npx 可用', '首次启动需要微信扫码登录'],
152
- })
153
+ }, { logsDir })
153
154
  this.projectRoot = projectRoot
155
+ this.channelEventsUrl = channelEventsUrl
154
156
  this.launchWorkspace = null
155
157
  }
156
158
 
@@ -179,7 +181,11 @@ export class WechatChannelProvider extends ProcessChannelProvider {
179
181
  command: npxCommand(),
180
182
  args: ['-y', WEIXIN_ACP_PACKAGE, 'start', '--', acpCommand, ...acpArgs],
181
183
  cwd: this.launchWorkspace?.path || getDefaultWorkspaceRoot() || this.projectRoot,
182
- env: process.env,
184
+ env: createNodeProcessEnv(process.env, {
185
+ QUICKFORGE_ACP_CHANNEL_ID: this.definition.id,
186
+ QUICKFORGE_ACP_CHANNEL_NAME: this.definition.name,
187
+ ...(this.channelEventsUrl ? { QUICKFORGE_CHANNEL_EVENTS_URL: this.channelEventsUrl } : {}),
188
+ }),
183
189
  shell: shouldUseShellForNpx(),
184
190
  }
185
191
  }
@@ -16,7 +16,11 @@ function attachProvider(provider) {
16
16
  export function initializeChannels(options = {}) {
17
17
  if (initialized) return
18
18
  initialized = true
19
- attachProvider(createWechatChannelProvider({ projectRoot: options.projectRoot }))
19
+ attachProvider(createWechatChannelProvider({
20
+ projectRoot: options.projectRoot,
21
+ channelEventsUrl: options.channelEventsUrl,
22
+ logsDir: options.logsDir,
23
+ }))
20
24
  }
21
25
 
22
26
  function requireProvider(id) {
@@ -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
+ }