@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
@@ -4,7 +4,7 @@ import { SocksClient } from 'socks'
4
4
  import { atomicUpdate, readStore } from './storage.mjs'
5
5
 
6
6
  const SETTINGS_KEY = 'network-proxy'
7
- const VALID_MODES = new Set(['direct', 'system', 'manual'])
7
+ const VALID_MODES = new Set(['direct', 'system', 'manual', 'pac'])
8
8
  const PROXY_ENV_KEYS = [
9
9
  'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
10
10
  'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
@@ -60,6 +60,40 @@ export function validateManualProxyUrl(value) {
60
60
  return `${url.protocol}//${url.host}`
61
61
  }
62
62
 
63
+ export function validatePacUrl(value) {
64
+ let url
65
+ try {
66
+ url = new URL(String(value || '').trim())
67
+ } catch {
68
+ const error = new Error('PAC URL must be a valid HTTP or HTTPS URL')
69
+ error.statusCode = 400
70
+ throw error
71
+ }
72
+
73
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
74
+ const error = new Error('Only HTTP and HTTPS PAC URLs are supported')
75
+ error.statusCode = 400
76
+ throw error
77
+ }
78
+ if (!url.hostname) {
79
+ const error = new Error('PAC URL must include a host')
80
+ error.statusCode = 400
81
+ throw error
82
+ }
83
+ if (url.username || url.password) {
84
+ const error = new Error('Credentials are not supported in the PAC URL')
85
+ error.statusCode = 400
86
+ throw error
87
+ }
88
+ if (url.hash) {
89
+ const error = new Error('PAC URL cannot include a fragment')
90
+ error.statusCode = 400
91
+ throw error
92
+ }
93
+
94
+ return url.href
95
+ }
96
+
63
97
  function isLoopbackUrl(input) {
64
98
  let url
65
99
  try {
@@ -251,9 +285,27 @@ class SystemProxyDispatcher extends Dispatcher {
251
285
  }
252
286
  }
253
287
 
288
+ class UnsupportedProxyDispatcher extends Dispatcher {
289
+ constructor(message) {
290
+ super()
291
+ this.error = new Error(message)
292
+ this.error.code = 'QUICKFORGE_PROXY_UNSUPPORTED'
293
+ }
294
+
295
+ dispatch(_options, handler) {
296
+ queueMicrotask(() => handler.onResponseError?.(null, this.error))
297
+ return true
298
+ }
299
+
300
+ async close() {}
301
+
302
+ async destroy() {}
303
+ }
304
+
254
305
  async function createNetworkDispatcher(config) {
255
306
  if (config.mode === 'manual') return new ProxyAgent(config.proxyUrl)
256
307
  if (config.mode === 'system') return new SystemProxyDispatcher(await getNativeResolver())
308
+ if (config.mode === 'pac') return new UnsupportedProxyDispatcher('Custom PAC URLs are not supported by this runtime')
257
309
  return new Agent()
258
310
  }
259
311
 
@@ -291,6 +343,7 @@ async function persistConfig(config) {
291
343
  async function applyConfig(config) {
292
344
  const normalized = normalizeNetworkProxyConfig(config)
293
345
  if (normalized.mode === 'manual') normalized.proxyUrl = validateManualProxyUrl(normalized.proxyUrl)
346
+ if (normalized.mode === 'pac') normalized.proxyUrl = validatePacUrl(normalized.proxyUrl)
294
347
 
295
348
  if (hostRuntime) await hostRuntime.apply(normalized)
296
349
  else await replaceNetworkDispatcher(normalized)
@@ -310,6 +363,7 @@ async function nativeStatus() {
310
363
  source,
311
364
  features: {
312
365
  pac: config.configuredPac?.state !== 'unsupported',
366
+ pacUrl: false,
313
367
  wpad: config.wpadDns?.state !== 'unsupported' || config.wpadDhcp?.state !== 'unsupported',
314
368
  httpProxy: true,
315
369
  httpsProxy: true,
@@ -320,7 +374,7 @@ async function nativeStatus() {
320
374
  return {
321
375
  supported: false,
322
376
  source: 'none',
323
- features: { pac: false, wpad: false, httpProxy: false, httpsProxy: false, socks: false },
377
+ features: { pac: false, pacUrl: false, wpad: false, httpProxy: false, httpsProxy: false, socks: false },
324
378
  error: error instanceof Error ? error.message : String(error),
325
379
  }
326
380
  }
@@ -331,7 +385,9 @@ export async function getNetworkProxyStatus() {
331
385
  ? await hostRuntime.getStatus()
332
386
  : await nativeStatus()
333
387
  const configuredMode = currentConfig.mode
334
- const modeSupported = configuredMode !== 'system' || runtimeStatus.supported
388
+ const modeSupported = configuredMode === 'system'
389
+ ? runtimeStatus.supported
390
+ : configuredMode !== 'pac' || runtimeStatus.features?.pacUrl === true
335
391
  return {
336
392
  configuredMode,
337
393
  effectiveMode: modeSupported ? configuredMode : 'unsupported',
@@ -361,6 +417,15 @@ export async function updateNetworkProxyConfig(value) {
361
417
  const previous = currentConfig
362
418
  const next = normalizeNetworkProxyConfig(value)
363
419
  if (next.mode === 'manual') next.proxyUrl = validateManualProxyUrl(next.proxyUrl)
420
+ if (next.mode === 'pac') {
421
+ next.proxyUrl = validatePacUrl(next.proxyUrl)
422
+ const runtimeStatus = hostRuntime ? await hostRuntime.getStatus() : await nativeStatus()
423
+ if (runtimeStatus.features?.pacUrl !== true) {
424
+ const error = new Error('Custom PAC URLs are not supported by this runtime')
425
+ error.statusCode = 400
426
+ throw error
427
+ }
428
+ }
364
429
 
365
430
  await applyConfig(next)
366
431
  try {
@@ -16,6 +16,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
16
16
  const bundledPluginDir = path.resolve(__dirname, '..', '..', 'plugins')
17
17
  const globalPluginDir = path.join(dataDir, 'plugins')
18
18
  const legacyGlobalPluginDir = path.join(os.homedir(), '.agents', 'plugins')
19
+ const PLUGIN_STORE_SCHEMA_VERSION = 2
20
+ const LEGACY_BUNDLED_PLUGIN_NAMES = {
21
+ 'openai-documents': 'documents',
22
+ 'openai-presentations': 'presentations',
23
+ 'openai-spreadsheets': 'spreadsheets',
24
+ }
25
+ export const DEFAULT_PLUGIN_TOOL_TIMEOUT_MS = 2 * 60 * 1000
19
26
  let cachedCatalog = null
20
27
  let cachedCatalogKey = null
21
28
  let catalogDirty = false
@@ -29,6 +36,56 @@ function isPlainObject(value) {
29
36
  return Boolean(value && typeof value === 'object' && !Array.isArray(value))
30
37
  }
31
38
 
39
+ function withTimeout(promise, timeoutMs, message, onTimeout, signal) {
40
+ let timer
41
+ let timedOut = false
42
+ let abortHandler
43
+ const guardedPromise = promise.catch((error) => {
44
+ if (timedOut) return new Promise(() => {})
45
+ throw error
46
+ })
47
+ const timeoutPromise = new Promise((_, reject) => {
48
+ const rejectForAbort = () => {
49
+ timedOut = true
50
+ reject(new Error('Plugin tool call aborted'))
51
+ }
52
+ if (signal?.aborted) {
53
+ rejectForAbort()
54
+ return
55
+ }
56
+ if (signal) {
57
+ abortHandler = rejectForAbort
58
+ signal.addEventListener('abort', abortHandler, { once: true })
59
+ }
60
+ timer = setTimeout(() => {
61
+ timedOut = true
62
+ try {
63
+ onTimeout?.()
64
+ } finally {
65
+ reject(new Error(message))
66
+ }
67
+ }, timeoutMs)
68
+ })
69
+ return Promise.race([guardedPromise, timeoutPromise]).finally(() => {
70
+ clearTimeout(timer)
71
+ if (abortHandler) signal?.removeEventListener('abort', abortHandler)
72
+ })
73
+ }
74
+
75
+ function combineAbortSignals(parentSignal, timeoutSignal) {
76
+ if (!parentSignal) return timeoutSignal
77
+ if (typeof AbortSignal.any === 'function') return AbortSignal.any([parentSignal, timeoutSignal])
78
+
79
+ const controller = new AbortController()
80
+ const abort = () => controller.abort()
81
+ if (parentSignal.aborted || timeoutSignal.aborted) controller.abort()
82
+ else {
83
+ parentSignal.addEventListener('abort', abort, { once: true })
84
+ timeoutSignal.addEventListener('abort', abort, { once: true })
85
+ }
86
+ return controller.signal
87
+ }
88
+
32
89
  async function exists(filePath) {
33
90
  try {
34
91
  await fs.access(filePath)
@@ -57,13 +114,31 @@ async function listPluginDirs(root) {
57
114
  function normalizePluginStore(store) {
58
115
  const source = isPlainObject(store) ? store : {}
59
116
  return {
60
- enabled: isPlainObject(source.enabled) ? source.enabled : {},
61
- config: isPlainObject(source.config) ? source.config : {},
117
+ schemaVersion: Number.isInteger(source.schemaVersion) ? source.schemaVersion : 1,
118
+ enabled: isPlainObject(source.enabled) ? { ...source.enabled } : {},
119
+ config: isPlainObject(source.config) ? { ...source.config } : {},
62
120
  }
63
121
  }
64
122
 
123
+ function migratePluginStore(store) {
124
+ const next = normalizePluginStore(store)
125
+ if (next.schemaVersion >= PLUGIN_STORE_SCHEMA_VERSION) return { store: next, changed: false }
126
+
127
+ for (const [legacyName, name] of Object.entries(LEGACY_BUNDLED_PLUGIN_NAMES)) {
128
+ if (!(name in next.enabled) && legacyName in next.enabled) next.enabled[name] = next.enabled[legacyName]
129
+ if (!(name in next.config) && legacyName in next.config) next.config[name] = next.config[legacyName]
130
+ delete next.enabled[legacyName]
131
+ delete next.config[legacyName]
132
+ }
133
+ next.schemaVersion = PLUGIN_STORE_SCHEMA_VERSION
134
+ return { store: next, changed: true }
135
+ }
136
+
65
137
  async function readPluginStore() {
66
- return normalizePluginStore(await readStore('plugins'))
138
+ const current = await readStore('plugins')
139
+ const migration = migratePluginStore(current)
140
+ if (!migration.changed) return migration.store
141
+ return atomicUpdate('plugins', (store) => migratePluginStore(store).store)
67
142
  }
68
143
 
69
144
  function contributionSummary(contribution) {
@@ -241,7 +316,7 @@ export async function getPluginStatus(projectContext = null) {
241
316
 
242
317
  export async function setPluginEnabled(name, enabled) {
243
318
  await atomicUpdate('plugins', (store) => {
244
- const next = normalizePluginStore(store)
319
+ const next = migratePluginStore(store).store
245
320
  next.enabled[name] = enabled === true
246
321
  return next
247
322
  })
@@ -250,7 +325,7 @@ export async function setPluginEnabled(name, enabled) {
250
325
 
251
326
  export async function setPluginConfig(name, config) {
252
327
  await atomicUpdate('plugins', (store) => {
253
- const next = normalizePluginStore(store)
328
+ const next = migratePluginStore(store).store
254
329
  next.config[name] = isPlainObject(config) ? config : {}
255
330
  return next
256
331
  })
@@ -308,10 +383,19 @@ export async function callPluginTool(toolName, params = {}, toolContext = {}) {
308
383
  throw error
309
384
  }
310
385
 
311
- const result = await handler.callTool(tool.name, params, {
312
- ...toolContext,
313
- plugin: { name: plugin.name, dir: plugin.dir, config: plugin.config },
314
- })
386
+ const timeoutController = new AbortController()
387
+ const signal = combineAbortSignals(toolContext.signal, timeoutController.signal)
388
+ const result = await withTimeout(
389
+ Promise.resolve().then(() => handler.callTool(tool.name, params, {
390
+ ...toolContext,
391
+ signal,
392
+ plugin: { name: plugin.name, dir: plugin.dir, config: plugin.config },
393
+ })),
394
+ DEFAULT_PLUGIN_TOOL_TIMEOUT_MS,
395
+ `Plugin tool ${toolName} timed out after ${DEFAULT_PLUGIN_TOOL_TIMEOUT_MS}ms`,
396
+ () => timeoutController.abort(),
397
+ toolContext.signal,
398
+ )
315
399
 
316
400
  return {
317
401
  isError: Boolean(result.isError),
@@ -1,6 +1,7 @@
1
1
  import { spawn } from 'node:child_process'
2
2
  import path from 'node:path'
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url'
4
+ import { createNodeProcessEnv } from './utils/process-env.mjs'
4
5
 
5
6
  const __filename = fileURLToPath(import.meta.url)
6
7
  const __dirname = path.dirname(__filename)
@@ -83,13 +84,16 @@ function buildEnv(options = {}) {
83
84
  if (options.workspaceDir) env.QUICKFORGE_WORKSPACE_DIR = path.resolve(options.workspaceDir)
84
85
  if (options.vitePort) env.QUICKFORGE_VITE_PORT = String(options.vitePort)
85
86
  if (options.terminal === false) env.QUICKFORGE_TERMINAL = '0'
86
- if (process.versions.electron) env.ELECTRON_RUN_AS_NODE = '1'
87
87
  if (options.allowRemote || shareLan) env.QUICKFORGE_ALLOW_REMOTE = '1'
88
+ delete env.ELECTRON_RUN_AS_NODE
89
+ delete env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
88
90
 
89
91
  return env
90
92
  }
91
93
 
92
94
  export function prepareQuickForgeEnv(options = {}) {
95
+ delete process.env.ELECTRON_RUN_AS_NODE
96
+ delete process.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE
93
97
  Object.assign(process.env, buildEnv(options))
94
98
  }
95
99
 
@@ -191,7 +195,7 @@ export async function startQuickForge(options = {}) {
191
195
  stdio: options.stdio || 'ignore',
192
196
  windowsHide: true,
193
197
  shell: false,
194
- env: buildEnv(options),
198
+ env: createNodeProcessEnv(buildEnv(options)),
195
199
  })
196
200
 
197
201
  let exitInfo = null
@@ -1,8 +1,38 @@
1
- import { streamSimple } from '@earendil-works/pi-ai/compat'
1
+ import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
2
2
  import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
3
3
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
4
4
  import { readStore } from '../storage.mjs'
5
5
  import { logger } from '../utils/logger.mjs'
6
+ import { resolveModelBinding } from '../model-catalog.mjs'
7
+
8
+ function profileModelReference(model) {
9
+ if (!model || model.mode !== 'fixed') return null
10
+ if (model.source === 'cloud') return { version: 1, source: 'cloud', catalogId: model.catalogId || model.modelId }
11
+ if (model.source === 'custom') return { version: 1, source: 'custom', providerId: model.providerId, modelId: model.modelId }
12
+ return {
13
+ version: 1,
14
+ source: 'legacy-custom',
15
+ provider: model.provider,
16
+ modelId: model.modelId,
17
+ ...(model.api ? { api: model.api } : {}),
18
+ ...(model.baseUrl ? { baseUrl: model.baseUrl } : {}),
19
+ }
20
+ }
21
+
22
+ async function canonicalizeProfileModel(body, current, context) {
23
+ if (!body || !Object.prototype.hasOwnProperty.call(body, 'model')) return body
24
+ const model = body.model
25
+ if (!model || model.mode !== 'fixed') return body
26
+ if (current?.model && JSON.stringify(current.model) === JSON.stringify(model)) return body
27
+ const binding = await resolveModelBinding({ modelRef: profileModelReference(model) }, { context })
28
+ const ref = binding.modelRef
29
+ return {
30
+ ...body,
31
+ model: ref.source === 'cloud'
32
+ ? { mode: 'fixed', source: 'cloud', catalogId: ref.catalogId }
33
+ : { mode: 'fixed', source: 'custom', providerId: ref.providerId, modelId: ref.modelId },
34
+ }
35
+ }
6
36
  import {
7
37
  agentProfileSnapshot,
8
38
  createCustomAgentProfile,
@@ -65,10 +95,14 @@ function normalizeGeneratedAgentProfile(value) {
65
95
  return { name, label, description, systemPrompt }
66
96
  }
67
97
 
68
- async function generateAgentProfileWithAi(instruction, model, thinkingLevel = 'off') {
98
+ async function generateAgentProfileWithAi(instruction, modelInput, thinkingLevel = 'off', context = {}) {
69
99
  const text = String(instruction || '').trim()
70
100
  if (!text) throw requestError('Please describe the agent you want to create')
71
- if (!model) throw requestError('Please configure a default model first')
101
+ if (!modelInput) throw requestError('Please configure a default model first')
102
+ const { model } = await resolveModelBinding(
103
+ modelInput?.modelRef || modelInput?.model ? modelInput : { model: modelInput },
104
+ { context, legacySnapshot: modelInput?.model || modelInput },
105
+ )
72
106
 
73
107
  const systemPrompt = `You are a QuickForge Agent Profile generator.
74
108
  Generate only the basic definition fields for a custom Agent Profile from the user's request.
@@ -90,7 +124,7 @@ Rules:
90
124
  - If the user requests Chinese, write label, description, and systemPrompt in Chinese; otherwise match the user's language.`
91
125
 
92
126
  try {
93
- const stream = streamSimple(
127
+ const stream = streamSimpleWithAiHttpLogging(
94
128
  model,
95
129
  {
96
130
  systemPrompt,
@@ -120,7 +154,7 @@ Rules:
120
154
  }
121
155
  }
122
156
 
123
- export async function handleAgentProfilesApi(req, res, url) {
157
+ export async function handleAgentProfilesApi(req, res, url, context = {}) {
124
158
  const parts = url.pathname.split('/').filter(Boolean)
125
159
 
126
160
  if (req.method === 'GET' && url.pathname === '/api/agent-profiles') {
@@ -136,13 +170,13 @@ export async function handleAgentProfilesApi(req, res, url) {
136
170
 
137
171
  if (req.method === 'POST' && url.pathname === '/api/agent-profiles/ai-fill') {
138
172
  const body = await readJsonBody(req)
139
- sendJson(res, 200, { agent: await generateAgentProfileWithAi(body?.instruction, body?.model, body?.thinkingLevel) })
173
+ sendJson(res, 200, { agent: await generateAgentProfileWithAi(body?.instruction, body?.modelRef ? body : body?.model, body?.thinkingLevel, context) })
140
174
  return
141
175
  }
142
176
 
143
177
  if (req.method === 'POST' && url.pathname === '/api/agent-profiles') {
144
178
  const body = await readJsonBody(req)
145
- sendJson(res, 200, { agent: await createCustomAgentProfile(body || {}) })
179
+ sendJson(res, 200, { agent: await createCustomAgentProfile(await canonicalizeProfileModel(body || {}, null, context)) })
146
180
  return
147
181
  }
148
182
 
@@ -162,11 +196,11 @@ export async function handleAgentProfilesApi(req, res, url) {
162
196
  if (current?.builtin) {
163
197
  const keys = Object.keys(body || {})
164
198
  if (keys.length !== 1 || keys[0] !== 'model') throw requestError('Built-in agents only allow model updates', 403)
165
- sendJson(res, 200, { agent: agentProfileSnapshot(await updateBuiltinAgentModelOverride(id, body.model)) })
199
+ sendJson(res, 200, { agent: agentProfileSnapshot(await updateBuiltinAgentModelOverride(id, (await canonicalizeProfileModel(body, current, context)).model)) })
166
200
  return
167
201
  }
168
202
  if (current?.readonly) throw requestError('Read-only agents cannot be modified from the API', 403)
169
- sendJson(res, 200, { agent: await updateCustomAgentProfile(id, body || {}) })
203
+ sendJson(res, 200, { agent: await updateCustomAgentProfile(id, await canonicalizeProfileModel(body || {}, current, context)) })
170
204
  return
171
205
  }
172
206
 
@@ -1,4 +1,6 @@
1
1
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
2
+ import { logger } from '../utils/logger.mjs'
3
+ import { resolveModelBinding } from '../model-catalog.mjs'
2
4
  import {
3
5
  createAgent,
4
6
  runPrompt,
@@ -30,7 +32,7 @@ import {
30
32
  agentEvents,
31
33
  } from '../agent-manager.mjs'
32
34
 
33
- export async function handleAgentApi(req, res, url) {
35
+ export async function handleAgentApi(req, res, url, context = {}) {
34
36
  const pathname = url.pathname
35
37
  const parts = pathname.split('/').filter(Boolean)
36
38
 
@@ -84,7 +86,7 @@ export async function handleAgentApi(req, res, url) {
84
86
  error.statusCode = 400
85
87
  throw error
86
88
  }
87
- const result = await runPrompt(sessionId, message, body?.selectedCapabilities, body?.command)
89
+ const result = await runPrompt(sessionId, message, body?.selectedCapabilities, body?.command, null, context)
88
90
  sendJson(res, 200, result)
89
91
  return
90
92
  }
@@ -96,6 +98,30 @@ export async function handleAgentApi(req, res, url) {
96
98
  return
97
99
  }
98
100
 
101
+ // POST /api/agents/:sessionId/restore — restore once and return the authoritative state
102
+ if (req.method === 'POST' && subPath === 'restore') {
103
+ const startedAt = performance.now()
104
+ const session = await restoreAgent(sessionId)
105
+ if (!session) {
106
+ const error = new Error('Session not found')
107
+ error.statusCode = 404
108
+ throw error
109
+ }
110
+ const state = getSessionState(sessionId)
111
+ if (!state) {
112
+ const error = new Error('Session not found')
113
+ error.statusCode = 404
114
+ throw error
115
+ }
116
+ logger.debug(`Restored session ${sessionId} for client`, {
117
+ sessionId,
118
+ messageCount: state.messages?.length ?? 0,
119
+ durationMs: Math.round((performance.now() - startedAt) * 10) / 10,
120
+ })
121
+ sendJson(res, 200, state)
122
+ return
123
+ }
124
+
99
125
  // GET /api/agents/:sessionId/state — get session state
100
126
  if (req.method === 'GET' && subPath === 'state') {
101
127
  let state = getSessionState(sessionId)
@@ -135,12 +161,22 @@ export async function handleAgentApi(req, res, url) {
135
161
  // POST /api/agents/:sessionId — create/ensure agent
136
162
  if (req.method === 'POST' && parts.length === 3) {
137
163
  const body = await readJsonBody(req)
138
- const session = await createAgent(sessionId, body)
164
+ let config
165
+ if (body?.modelRef || body?.model) {
166
+ const binding = await resolveModelBinding(body, { context, legacySnapshot: body?.model })
167
+ config = { ...body, model: binding.model, modelRef: binding.modelRef, modelAccessContext: context, resolvePersistedModel: true }
168
+ } else {
169
+ config = { ...body, modelAccessContext: context, resolvePersistedModel: true }
170
+ }
171
+ const session = await createAgent(sessionId, config)
139
172
  sendJson(res, 200, {
140
173
  sessionId: session.sessionId,
141
174
  status: session.status,
142
175
  scope: session.scope,
143
176
  title: session.title,
177
+ source: session.source || undefined,
178
+ channelId: session.channelId || undefined,
179
+ channelName: session.channelName || undefined,
144
180
  accessMode: session.accessMode,
145
181
  yoloMode: session.yoloMode,
146
182
  })
@@ -181,13 +217,14 @@ export async function handleAgentApi(req, res, url) {
181
217
  // POST /api/agents/:sessionId/model — update session model
182
218
  if (req.method === 'POST' && subPath === 'model') {
183
219
  const body = await readJsonBody(req)
184
- const model = body?.model
185
- if (!model) {
186
- const error = new Error('Missing model in request body')
187
- error.statusCode = 400
188
- throw error
189
- }
190
- const result = updateSessionModel(sessionId, model)
220
+ const currentModel = getSessionState(sessionId)?.model
221
+ const binding = await resolveModelBinding(body, {
222
+ context,
223
+ currentModel,
224
+ allowCurrentHidden: true,
225
+ legacySnapshot: body?.model,
226
+ })
227
+ const result = updateSessionModel(sessionId, binding.model, binding.modelRef)
191
228
  sendJson(res, 200, result)
192
229
  return
193
230
  }
@@ -216,7 +253,7 @@ export async function handleAgentApi(req, res, url) {
216
253
 
217
254
  // POST /api/agents/:sessionId/continue — continue generation from last message (retry)
218
255
  if (req.method === 'POST' && subPath === 'continue') {
219
- const result = await continueSession(sessionId)
256
+ const result = await continueSession(sessionId, context)
220
257
  sendJson(res, 200, result)
221
258
  return
222
259
  }
@@ -580,7 +580,13 @@ async function restoreValidatedBackup(backup, mode = 'replace') {
580
580
  return summary
581
581
  }
582
582
 
583
- export async function handleBackupApi(req, res, url) {
583
+ export async function handleBackupApi(req, res, url, context = {}) {
584
+ if (context.isLocalRequest === false) {
585
+ const error = new Error('Backup import and export are available only on this computer.')
586
+ error.statusCode = 403
587
+ error.errorCode = 'backup_local_only'
588
+ throw error
589
+ }
584
590
  if (req.method === 'GET' && url.pathname === '/api/backup/export') {
585
591
  await ensureStorage()
586
592
  sendJson(res, 200, await buildBackup(url.searchParams.get('scope'), {
@@ -1,4 +1,8 @@
1
+ import { promises as fs } from 'node:fs'
2
+ import { logger } from '../utils/logger.mjs'
1
3
  import { sendJson, decodeSegment, readJsonBody } from '../utils/response.mjs'
4
+ import { channelLogsDirectory } from '../channels/channel-logs.mjs'
5
+ import { openPathInFileManager } from '../utils/platform.mjs'
2
6
  import {
3
7
  channelEvents,
4
8
  getChannelStatus,
@@ -25,6 +29,24 @@ function assertActionHeader(req) {
25
29
  }
26
30
  }
27
31
 
32
+ function assertEventHeader(req) {
33
+ if (req.headers['x-quickforge-action'] !== 'channel-event') {
34
+ const error = new Error('Forbidden event')
35
+ error.statusCode = 403
36
+ throw error
37
+ }
38
+ }
39
+
40
+ async function readChannelEvent(req) {
41
+ const event = await readJsonBody(req, 256 * 1024)
42
+ if (!event || event.type !== 'sessions-changed' || typeof event.sessionId !== 'string' || !event.sessionId.trim()) {
43
+ const error = new Error('Invalid channel event')
44
+ error.statusCode = 400
45
+ throw error
46
+ }
47
+ return event
48
+ }
49
+
28
50
  async function readActionOptions(req) {
29
51
  if (!['POST', 'PUT', 'PATCH'].includes(req.method || '')) return {}
30
52
  const contentType = String(req.headers['content-type'] || '')
@@ -46,6 +68,15 @@ export async function handleChannelsApi(req, res, url, context = {}) {
46
68
  return
47
69
  }
48
70
 
71
+ if (req.method === 'POST' && pathname === '/api/channels/events') {
72
+ assertLocal(context)
73
+ assertEventHeader(req)
74
+ const event = await readChannelEvent(req)
75
+ channelEvents.emit('channel_event', event)
76
+ sendJson(res, 202, { ok: true })
77
+ return
78
+ }
79
+
49
80
  if (parts.length < 3 || parts[0] !== 'api' || parts[1] !== 'channels') {
50
81
  const error = new Error('Not found')
51
82
  error.statusCode = 404
@@ -67,6 +98,22 @@ export async function handleChannelsApi(req, res, url, context = {}) {
67
98
 
68
99
  assertLocal(context)
69
100
 
101
+ if (req.method === 'POST' && subPath.length === 1 && subPath[0] === 'open-logs') {
102
+ assertActionHeader(req)
103
+ getChannelStatus(channelId)
104
+ if (!context.logsDir) {
105
+ const error = new Error('Channel log directory is unavailable')
106
+ error.statusCode = 500
107
+ throw error
108
+ }
109
+ const directory = channelLogsDirectory(context.logsDir, channelId)
110
+ await fs.mkdir(directory, { recursive: true })
111
+ await (context.openPathInFileManager ?? openPathInFileManager)(directory)
112
+ logger.info('Opened channel log directory.', { channelId })
113
+ sendJson(res, 200, { ok: true })
114
+ return
115
+ }
116
+
70
117
  if (req.method === 'POST' && subPath[0] === 'start') {
71
118
  assertActionHeader(req)
72
119
  sendJson(res, 202, await startChannel(channelId, await readActionOptions(req)))