@shawnstack/quickforge 1.7.3 → 1.7.4

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 (210) hide show
  1. package/README.md +1 -1
  2. package/bin/quickforge.mjs +0 -21
  3. package/dist/assets/AgentProfilesPage-B84w-r9S.js +1 -0
  4. package/dist/assets/{ChatPanelHost-wfGCaCzD.js → ChatPanelHost-CyO8fEoj.js} +91 -91
  5. package/dist/assets/{PluginsPage-NAGroBm3.js → PluginsPage-D2862wul.js} +1 -1
  6. package/dist/assets/{ScheduledTasksPage-Bp0MNfQD.js → ScheduledTasksPage-GqbiJDPX.js} +2 -2
  7. package/dist/assets/SettingsWorkspacePage-B53_eDf2.js +1628 -0
  8. package/dist/assets/SharedConversationPage-Cw2QFnbC.js +1 -0
  9. package/dist/assets/TerminalDock-Dgg-_ZmU.js +2 -0
  10. package/dist/assets/WorkspaceInspector-3uLiu6Bv.js +13 -0
  11. package/dist/assets/{abnfDiagram-VRR7QNED-Dl_Fvlqg.js → abnfDiagram-VRR7QNED-Dy7veffk.js} +1 -1
  12. package/dist/assets/{arc-Bhg4c0ht.js → arc-Bo3p2lGy.js} +1 -1
  13. package/dist/assets/architecture-TIHT7OUA-7FQo5GbP.js +1 -0
  14. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BuojN27B.js → architectureDiagram-ZJ3FMSHR-Cwx8qOvu.js} +1 -1
  15. package/dist/assets/{blockDiagram-677ZJIJ3-alDLbrR1.js → blockDiagram-677ZJIJ3-BiuaAOFa.js} +1 -1
  16. package/dist/assets/{c4Diagram-LMCZKHZV-D1lKa4VT.js → c4Diagram-LMCZKHZV-BTcGfhK2.js} +1 -1
  17. package/dist/assets/channel-DToA8d-4.js +1 -0
  18. package/dist/assets/chunk-2Q5K7J3B-D_Wxpk-T.js +1 -0
  19. package/dist/assets/{chunk-32BRIVSS-CwWLNjMZ.js → chunk-32BRIVSS-B9PuF_RF.js} +1 -1
  20. package/dist/assets/{chunk-52WLFC77-Be569m7d.js → chunk-52WLFC77-3zGItfIM.js} +1 -1
  21. package/dist/assets/{chunk-5FCAYU7R-BSvoiWV1.js → chunk-5FCAYU7R-DQ66qlmt.js} +1 -1
  22. package/dist/assets/{chunk-5HE753X5-CRgsy8WS.js → chunk-5HE753X5-CLqTO-VF.js} +1 -1
  23. package/dist/assets/{chunk-5JV3BV7I-CdW477UR.js → chunk-5JV3BV7I-Csxv1jr6.js} +1 -1
  24. package/dist/assets/{chunk-5TONJI2A-CdtKzSmp.js → chunk-5TONJI2A-BCQttpBr.js} +1 -1
  25. package/dist/assets/{chunk-5VM5RSS4-BKjjssLZ.js → chunk-5VM5RSS4-DEWJGSTb.js} +1 -1
  26. package/dist/assets/{chunk-7BUUIJ7U-ByAXiXdn.js → chunk-7BUUIJ7U-DNorm9MM.js} +1 -1
  27. package/dist/assets/{chunk-BIQX33UG-DeG8SCT1.js → chunk-BIQX33UG-DgvY_BOT.js} +1 -1
  28. package/dist/assets/{chunk-C7G6YPKG-D_zHT68Q.js → chunk-C7G6YPKG-DMvn9IPs.js} +1 -1
  29. package/dist/assets/{chunk-CQNSW5MT-CHpHoUSu.js → chunk-CQNSW5MT-Dq2duUgr.js} +1 -1
  30. package/dist/assets/{chunk-CYSBUYHQ-CRP50Vov.js → chunk-CYSBUYHQ-BovYyc4Y.js} +1 -1
  31. package/dist/assets/{chunk-EMLP6XTP-DX6uQQ2v.js → chunk-EMLP6XTP-BMDOpuYN.js} +1 -1
  32. package/dist/assets/{chunk-EX3LRPZG-DAJRNkHR.js → chunk-EX3LRPZG-6eP2UPOf.js} +1 -1
  33. package/dist/assets/{chunk-FWX5IMBZ-XbM5yomC.js → chunk-FWX5IMBZ-D4q-wTSy.js} +2 -2
  34. package/dist/assets/{chunk-HOUHSVGY-Ci0ljnb1.js → chunk-HOUHSVGY-XjIcuzH7.js} +1 -1
  35. package/dist/assets/{chunk-ICXQ74PX-Dy2WJ71z.js → chunk-ICXQ74PX-YBXbanq9.js} +1 -1
  36. package/dist/assets/{chunk-JG7HCLWE-2PrkzHkt.js → chunk-JG7HCLWE-CcN3uNXF.js} +1 -1
  37. package/dist/assets/{chunk-JWPE2WC7-D32uacSM.js → chunk-JWPE2WC7-Cwg6AUAt.js} +1 -1
  38. package/dist/assets/{chunk-MOJQB5TN-DIAFLtiS.js → chunk-MOJQB5TN-NNspcGIU.js} +1 -1
  39. package/dist/assets/{chunk-MOZMSUNE-BbscOaEz.js → chunk-MOZMSUNE-pMJ_jMos.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-ivBVxzKe.js → chunk-OGEWGWER-CJy-3Ldv.js} +1 -1
  41. package/dist/assets/{chunk-OSBZ3O6U-frD0hkdx.js → chunk-OSBZ3O6U-D7cLHHOA.js} +1 -1
  42. package/dist/assets/{chunk-PUDLZKDR-B-37Rxae.js → chunk-PUDLZKDR-D5YBi1Pi.js} +1 -1
  43. package/dist/assets/{chunk-Q4XR5HBZ-Y90m2RTe.js → chunk-Q4XR5HBZ-DYmqQdIf.js} +1 -1
  44. package/dist/assets/{chunk-QBLGF6JB-DixBqslB.js → chunk-QBLGF6JB-Bfcx00X0.js} +1 -1
  45. package/dist/assets/{chunk-R7FJI6CG-DXS1UXn_.js → chunk-R7FJI6CG-oXzadpZY.js} +1 -1
  46. package/dist/assets/{chunk-RYQCIY6F-n4JxhNd0.js → chunk-RYQCIY6F-Dt3-8yUh.js} +1 -1
  47. package/dist/assets/{chunk-U6XO7XAA-yHlgxMmn.js → chunk-U6XO7XAA-B55eSvpx.js} +1 -1
  48. package/dist/assets/{chunk-V7JOEXUC-Z6kuZfbl.js → chunk-V7JOEXUC-Btk4JwCt.js} +1 -1
  49. package/dist/assets/chunk-VAUOI2AC-CIZseNLi.js +1 -0
  50. package/dist/assets/{chunk-VR4S4FIN-Cs_SIQma.js → chunk-VR4S4FIN-DgaSiVOY.js} +1 -1
  51. package/dist/assets/{chunk-WYO6CB5R-CGExYbV-.js → chunk-WYO6CB5R-rRDZ_Tta.js} +3 -3
  52. package/dist/assets/chunk-XXDRQBXY-Do-XYxat.js +1 -0
  53. package/dist/assets/{chunk-YOTPTUD7-DopPXWSl.js → chunk-YOTPTUD7-BnFxj5o4.js} +1 -1
  54. package/dist/assets/{chunk-ZGVPDNZ5-CJHOeqMb.js → chunk-ZGVPDNZ5-CrnlPlmJ.js} +1 -1
  55. package/dist/assets/{chunk-ZIRB5QZD-BBVc5mf6.js → chunk-ZIRB5QZD-BYTiATIH.js} +1 -1
  56. package/dist/assets/classDiagram-OUVF2IWQ-Bu24Sy-k.js +1 -0
  57. package/dist/assets/classDiagram-v2-EOCWNBFH-DKhh5HSW.js +1 -0
  58. package/dist/assets/{cose-bilkent-JH36ORCC-DLjV-dTm.js → cose-bilkent-JH36ORCC-BORKJRwF.js} +1 -1
  59. package/dist/assets/cynefin-VYW2F7L2-oyiAgYC4.js +1 -0
  60. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfH3zVS-.js → cynefinDiagram-TSTJHNR4-BXKeaSl8.js} +1 -1
  61. package/dist/assets/{dagre-CY3D84Cl.js → dagre-CrA0XVwJ.js} +1 -1
  62. package/dist/assets/{dagre-VKFMJZFB-BIhwZCIZ.js → dagre-VKFMJZFB-ouq_lA1a.js} +1 -1
  63. package/dist/assets/{diagram-FQU43EPY-BHnzNM2w.js → diagram-FQU43EPY-BxR5kUiQ.js} +1 -1
  64. package/dist/assets/{diagram-G47NLZAW-C7-ZlmFT.js → diagram-G47NLZAW-DWonk1sL.js} +1 -1
  65. package/dist/assets/{diagram-NH7WQ7WH-C-P9Y7IR.js → diagram-NH7WQ7WH-fmV_wV5e.js} +1 -1
  66. package/dist/assets/{diagram-OA4YK3LP-Ci5lZ_T5.js → diagram-OA4YK3LP-DvOSRRiV.js} +1 -1
  67. package/dist/assets/{diagram-WEI45ONY-C1xuK2bs.js → diagram-WEI45ONY-Bp-xb3uS.js} +1 -1
  68. package/dist/assets/dist-DLKC_odd.js +1 -0
  69. package/dist/assets/{ebnfDiagram-CCIWWBDH-DKQ5NWMD.js → ebnfDiagram-CCIWWBDH-BTlnwneA.js} +1 -1
  70. package/dist/assets/{erDiagram-Q63AITRT-HAWySadP.js → erDiagram-Q63AITRT-C3fFWGCm.js} +1 -1
  71. package/dist/assets/esm-oa69b5u4.js +2 -0
  72. package/dist/assets/eventmodeling-45OFAUF4-YB_0MefX.js +1 -0
  73. package/dist/assets/flowDiagram-23GEKE2U-Bc73xdBd.js +1 -0
  74. package/dist/assets/{ganttDiagram-NO4QXBWP-C1hwIcOA.js → ganttDiagram-NO4QXBWP-DzAiojNi.js} +1 -1
  75. package/dist/assets/gitGraph-TEB2WS4Q-Bvp27L0n.js +1 -0
  76. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DYUYxs1R.js → gitGraphDiagram-IHSO6WYX-Cv57oNEA.js} +1 -1
  77. package/dist/assets/icons-cTkqD_Gi.js +1 -0
  78. package/dist/assets/index-B48prgLL.js +65 -0
  79. package/dist/assets/index-DQhwHHer.css +3 -0
  80. package/dist/assets/info-DKCQHKI2-BsLOivgq.js +1 -0
  81. package/dist/assets/{infoDiagram-FWYZ7A6U-CnIL6tv4.js → infoDiagram-FWYZ7A6U-B5Z_O3Ro.js} +1 -1
  82. package/dist/assets/{ishikawaDiagram-FXEZZL3T-B7WXNycQ.js → ishikawaDiagram-FXEZZL3T-CKTK_TKV.js} +1 -1
  83. package/dist/assets/{journeyDiagram-5HDEW3XC-D4HvioyY.js → journeyDiagram-5HDEW3XC-Cm9k4ywW.js} +1 -1
  84. package/dist/assets/{kanban-definition-HUTT4EX6-CDs763eS.js → kanban-definition-HUTT4EX6-DzNteLwx.js} +1 -1
  85. package/dist/assets/{line-ikKkQiFp.js → line-BG78c_WJ.js} +1 -1
  86. package/dist/assets/{linear-GqPyxQkN.js → linear-1X20_EGu.js} +1 -1
  87. package/dist/assets/{map-CJwjCvwr.js → map-CCI8WZkS.js} +1 -1
  88. package/dist/assets/{mcp-servers-dialog-BQbFywVL.js → mcp-servers-dialog-GXXD9K0U.js} +2 -2
  89. package/dist/assets/{mermaid-parser.core-CoE8cccr.js → mermaid-parser.core-Bb8HI_OS.js} +2 -2
  90. package/dist/assets/{mermaid-renderer-BmbSAmpG.js → mermaid-renderer-PIaNiXAz.js} +2 -2
  91. package/dist/assets/{mermaid.core-DlFSL90j.js → mermaid.core-DUHOIwL5.js} +3 -3
  92. package/dist/assets/{mindmap-definition-LN4V7U3C-DLdq0K9C.js → mindmap-definition-LN4V7U3C-Tnbszo7b.js} +1 -1
  93. package/dist/assets/{monaco-BTsVCDWS.js → monaco-BVtCspYZ.js} +1 -1
  94. package/dist/assets/{ordinal-CibZU3PM.js → ordinal-v4prQcuq.js} +1 -1
  95. package/dist/assets/{otel-BNGM50C-.js → otel-D1LswXpM.js} +1 -1
  96. package/dist/assets/packet-7NZHBO7P-BKQkXRtk.js +1 -0
  97. package/dist/assets/{pegDiagram-2B236MQR-Cm1kPRC2.js → pegDiagram-2B236MQR-CX824Kd0.js} +1 -1
  98. package/dist/assets/{pi-ai-Cr3rpCXT.js → pi-ai-DAJlO9ZB.js} +2 -2
  99. package/dist/assets/{pi-web-ui-DoRGtREc.js → pi-web-ui-B-RJSxjR.js} +1 -1
  100. package/dist/assets/pie-RZYD4A2V-BTiTN5Ge.js +1 -0
  101. package/dist/assets/{pieDiagram-ENE6RG2P-Df05wjjo.js → pieDiagram-ENE6RG2P-BT04VWPQ.js} +1 -1
  102. package/dist/assets/{quadrantDiagram-ABIIQ3AL-BjCwGxJC.js → quadrantDiagram-ABIIQ3AL-BvOZxkBr.js} +1 -1
  103. package/dist/assets/radar-I7S5WNFK-BU-oVjKM.js +1 -0
  104. package/dist/assets/railroad-3IZDKUUU-7H2hxNbL.js +1 -0
  105. package/dist/assets/railroad-abnf-AHOZXSZD-Mx6wf6XQ.js +1 -0
  106. package/dist/assets/railroad-ebnf-EBAXGLYW-BgXSKhYy.js +1 -0
  107. package/dist/assets/railroad-peg-LSFZ7HO6-DTEl2rAC.js +1 -0
  108. package/dist/assets/{railroadDiagram-RFXS5EU6-CTUvpKkv.js → railroadDiagram-RFXS5EU6-DnTU--BV.js} +1 -1
  109. package/dist/assets/{react-vendor-Dr5xvL-e.js → react-vendor-eWH0QhNU.js} +1 -1
  110. package/dist/assets/{requirementDiagram-TGXJPOKE-BU1kvEku.js → requirementDiagram-TGXJPOKE--MZX_yEv.js} +1 -1
  111. package/dist/assets/{sankeyDiagram-HTMAVEWB-BHmvxVqH.js → sankeyDiagram-HTMAVEWB-SEZkHhYn.js} +1 -1
  112. package/dist/assets/{sequenceDiagram-DBY2YBRQ-CTpwICgg.js → sequenceDiagram-DBY2YBRQ-BhC2OHiz.js} +1 -1
  113. package/dist/assets/{sizeCapture-X5ZJPWSS-BbLhGaa4.js → sizeCapture-X5ZJPWSS-Ci5LeToV.js} +1 -1
  114. package/dist/assets/{skills-dialog-REBeTFSH.js → skills-dialog-DDh1h8eH.js} +3 -1
  115. package/dist/assets/{src-B4Tvg3gT.js → src-CovulcRC.js} +1 -1
  116. package/dist/assets/{stateDiagram-2N3HPSRC-BxuDwIgT.js → stateDiagram-2N3HPSRC-DKK5tFT4.js} +1 -1
  117. package/dist/assets/stateDiagram-v2-6OUMAXLB-D8x3mkni.js +1 -0
  118. package/dist/assets/{swimlanes-5IMT3BWC-EkZsiylV.js → swimlanes-5IMT3BWC-DxxGCk67.js} +2 -2
  119. package/dist/assets/swimlanesDiagram-G3AALYLV-C4nOBNp8.js +8 -0
  120. package/dist/assets/{timeline-definition-FHXFAJF6-C9ewOCTa.js → timeline-definition-FHXFAJF6-BPXB3Sue.js} +1 -1
  121. package/dist/assets/treeView-QDETBFTQ-DOE6NSdf.js +1 -0
  122. package/dist/assets/treemap-6X3UGDF4-Ca7QD_b4.js +1 -0
  123. package/dist/assets/{vennDiagram-L72KCM5P-CR49Y4Lp.js → vennDiagram-L72KCM5P-CxcBCKye.js} +1 -1
  124. package/dist/assets/wardley-OPB4EBWU-D9O4RKRm.js +1 -0
  125. package/dist/assets/{wardleyDiagram-EHGQE667-Cn0GgwWi.js → wardleyDiagram-EHGQE667-DEL6Yes5.js} +1 -1
  126. package/dist/assets/web-RgX7H-OH.js +1 -0
  127. package/dist/assets/{xychartDiagram-FW5EYKEG-DlmPNAvd.js → xychartDiagram-FW5EYKEG-N8cPbcjC.js} +1 -1
  128. package/dist/index.html +12 -11
  129. package/dist/mobile-error.html +24 -0
  130. package/package.json +4 -1
  131. package/server/acp/server.mjs +14 -9
  132. package/server/agent-manager.mjs +215 -20
  133. package/server/ai-http-logger.mjs +68 -11
  134. package/server/ai-provider-options.mjs +3 -0
  135. package/server/channels/channel-logs.mjs +13 -0
  136. package/server/channels/event-relay.mjs +33 -0
  137. package/server/channels/process-channel.mjs +29 -0
  138. package/server/channels/providers/wechat.mjs +9 -3
  139. package/server/channels/registry.mjs +5 -1
  140. package/server/index.mjs +36 -23
  141. package/server/lan-access-store.mjs +93 -3
  142. package/server/network-proxy.mjs +68 -3
  143. package/server/plugins/registry.mjs +64 -4
  144. package/server/public-api.mjs +6 -2
  145. package/server/routes/agent-profiles.mjs +2 -2
  146. package/server/routes/agent.mjs +28 -0
  147. package/server/routes/channels.mjs +47 -0
  148. package/server/routes/lan-access.mjs +246 -21
  149. package/server/routes/models.mjs +6 -3
  150. package/server/routes/project.mjs +19 -1
  151. package/server/routes/scheduled-tasks.mjs +2 -2
  152. package/server/routes/static.mjs +26 -1
  153. package/server/routes/system.mjs +11 -0
  154. package/server/routes/terminal.mjs +7 -2
  155. package/server/routes/workspace.mjs +60 -9
  156. package/server/session-utils.mjs +2 -2
  157. package/server/subagents.mjs +1 -1
  158. package/server/terminal/terminal-manager.mjs +2 -4
  159. package/server/tools/definitions.mjs +2 -2
  160. package/server/tools/index.mjs +5 -3
  161. package/server/utils/package-update.mjs +70 -47
  162. package/server/utils/process-env.mjs +7 -0
  163. package/server/utils/response.mjs +2 -2
  164. package/dist/assets/AgentProfilesPage-D7pbg8nm.js +0 -1
  165. package/dist/assets/SettingsWorkspacePage-BtJZMIsZ.js +0 -1576
  166. package/dist/assets/SharedConversationPage-Ce3KoVZt.js +0 -1
  167. package/dist/assets/TerminalDock-4eUuXP7X.js +0 -2
  168. package/dist/assets/WorkspaceInspector-kM3BRPjY.js +0 -13
  169. package/dist/assets/architecture-TIHT7OUA-BgP-IMeo.js +0 -1
  170. package/dist/assets/channel-Rz20dKTK.js +0 -1
  171. package/dist/assets/chunk-2Q5K7J3B-Cb14ZF_k.js +0 -1
  172. package/dist/assets/chunk-VAUOI2AC-BOZvd2Sn.js +0 -1
  173. package/dist/assets/chunk-XXDRQBXY-CD_EGCtT.js +0 -1
  174. package/dist/assets/classDiagram-OUVF2IWQ-C_ozTKCt.js +0 -1
  175. package/dist/assets/classDiagram-v2-EOCWNBFH-FOfFbbZF.js +0 -1
  176. package/dist/assets/cynefin-VYW2F7L2-BzPhLtd5.js +0 -1
  177. package/dist/assets/eventmodeling-45OFAUF4-BcgGbVJ-.js +0 -1
  178. package/dist/assets/flowDiagram-23GEKE2U-dtkzcC8E.js +0 -1
  179. package/dist/assets/gitGraph-TEB2WS4Q-VByWboBU.js +0 -1
  180. package/dist/assets/icons-BP8YOS-Z.js +0 -1
  181. package/dist/assets/index-C6k5taeb.js +0 -66
  182. package/dist/assets/index-CzN8NSKC.css +0 -3
  183. package/dist/assets/info-DKCQHKI2-CZeDLZxx.js +0 -1
  184. package/dist/assets/packet-7NZHBO7P-decrrxyX.js +0 -1
  185. package/dist/assets/pie-RZYD4A2V-J1CasQZl.js +0 -1
  186. package/dist/assets/radar-I7S5WNFK-B-sZytUA.js +0 -1
  187. package/dist/assets/railroad-3IZDKUUU-BSaqNPm8.js +0 -1
  188. package/dist/assets/railroad-abnf-AHOZXSZD-lzZR7PxT.js +0 -1
  189. package/dist/assets/railroad-ebnf-EBAXGLYW-DQtz1Fh9.js +0 -1
  190. package/dist/assets/railroad-peg-LSFZ7HO6-CpMsuGY3.js +0 -1
  191. package/dist/assets/stateDiagram-v2-6OUMAXLB-BGLshxcY.js +0 -1
  192. package/dist/assets/swimlanesDiagram-G3AALYLV-CqdqxA-b.js +0 -8
  193. package/dist/assets/treeView-QDETBFTQ-Byb71heW.js +0 -1
  194. package/dist/assets/treemap-6X3UGDF4-CMV1SJsq.js +0 -1
  195. package/dist/assets/wardley-OPB4EBWU-DtCHtr2V.js +0 -1
  196. /package/dist/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
  197. /package/dist/assets/{chunk-KEIR6QF5-77hsebd7.js → chunk-KEIR6QF5-s7Qq-R3i.js} +0 -0
  198. /package/dist/assets/{chunk-Y2CYZVJY-CQltPwYk.js → chunk-Y2CYZVJY-BgISNSiw.js} +0 -0
  199. /package/dist/assets/{css-utils-rkE68RDy.js → css-utils-31qzCXsk.js} +0 -0
  200. /package/dist/assets/{cytoscape.esm-DwdZnGOm.js → cytoscape.esm-iekjSZ2n.js} +0 -0
  201. /package/dist/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
  202. /package/dist/assets/{dist-DelZBZpK.js → dist-zzfqk5hx.js} +0 -0
  203. /package/dist/assets/{graphlib-CAISHZh4.js → graphlib-CIz_CfxW.js} +0 -0
  204. /package/dist/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
  205. /package/dist/assets/{lit-vendor-Dr3cpBGF.js → lit-vendor-DvaQX_UQ.js} +0 -0
  206. /package/dist/assets/{logger-B65Akg8A.js → logger-8REuLPGK.js} +0 -0
  207. /package/dist/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
  208. /package/dist/assets/{plugin-api-BbUkYm3Y.js → plugin-api-C8hMT5W6.js} +0 -0
  209. /package/dist/assets/{rough.esm-Djo4Abte.js → rough.esm-BMOtDaaS.js} +0 -0
  210. /package/dist/assets/{xterm-CGiQzDiR.js → xterm-BFzZ1FH4.js} +0 -0
@@ -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) {
package/server/index.mjs CHANGED
@@ -5,7 +5,7 @@ import path from 'node:path'
5
5
  import { randomUUID } from 'node:crypto'
6
6
  import { fileURLToPath } from 'node:url'
7
7
  import { sendJson, sendError } from './utils/response.mjs'
8
- import { openBrowser } from './utils/platform.mjs'
8
+ import { openBrowser, openPathInFileManager } from './utils/platform.mjs'
9
9
  import { ensureStorage, dataDir, configDir, storageDir, cacheDir, logsDir } from './storage.mjs'
10
10
  import { getNetworkProxyConfig, initializeNetworkProxy, refreshSystemProxy, updateNetworkProxyConfig } from './network-proxy.mjs'
11
11
  import { setDefaultWorkspaceRoot, initializeActiveProject, readProjectConfig, getActiveProject, readTerminalShellSetting, updateTerminalShellSetting, readTerminalShellConfig, updateTerminalShellConfig } from './project-config.mjs'
@@ -44,6 +44,7 @@ import { shutdown as shutdownAgentManager, resetStaleTaskStatuses } from './agen
44
44
  import { initializeChannels, shutdownChannels } from './channels/registry.mjs'
45
45
  import { shutdownMcpConnections } from './mcp/registry.mjs'
46
46
  import { shutdownTerminalSessions } from './terminal/terminal-manager.mjs'
47
+ import { createNodeProcessEnv } from './utils/process-env.mjs'
47
48
 
48
49
  const __filename = fileURLToPath(import.meta.url)
49
50
  const __dirname = path.dirname(__filename)
@@ -71,10 +72,11 @@ function getRestartSupport() {
71
72
  return { supported: true, reason: null }
72
73
  }
73
74
 
74
- async function getSystemStatus() {
75
+ async function getSystemStatus({ isLocalRequest = true } = {}) {
75
76
  const config = await readProjectConfig()
76
77
  const restartSupport = getRestartSupport()
77
78
  const packageInfo = await getPackageInfo(projectRoot)
79
+ const restartSupported = isLocalRequest && restartSupport.supported
78
80
  return {
79
81
  ok: true,
80
82
  mode: isDev ? 'development' : 'production',
@@ -83,8 +85,14 @@ async function getSystemStatus() {
83
85
  startedAt,
84
86
  version: packageInfo.version,
85
87
  package: packageInfo,
86
- restartSupported: restartSupport.supported,
87
- restartUnsupportedReason: restartSupport.reason,
88
+ isLocalRequest,
89
+ capabilities: {
90
+ restart: restartSupported,
91
+ openLocalApps: isLocalRequest,
92
+ terminal: isLocalRequest,
93
+ },
94
+ restartSupported,
95
+ restartUnsupportedReason: isLocalRequest ? restartSupport.reason : 'Restart is only allowed from this computer',
88
96
  dataDir,
89
97
  configDir,
90
98
  storageDir,
@@ -113,10 +121,9 @@ function spawnRestartSupervisor() {
113
121
  stdio: 'ignore',
114
122
  windowsHide: true,
115
123
  shell: false,
116
- env: {
117
- ...process.env,
124
+ env: createNodeProcessEnv(process.env, {
118
125
  QUICKFORGE_NO_OPEN: '1',
119
- },
126
+ }),
120
127
  })
121
128
 
122
129
  child.once('error', reject)
@@ -206,10 +213,9 @@ function spawnUpdateSupervisor(update) {
206
213
  stdio: 'ignore',
207
214
  windowsHide: true,
208
215
  shell: false,
209
- env: {
210
- ...process.env,
216
+ env: createNodeProcessEnv(process.env, {
211
217
  QUICKFORGE_NO_OPEN: '1',
212
- },
218
+ }),
213
219
  })
214
220
 
215
221
  child.once('error', reject)
@@ -277,7 +283,7 @@ async function updateQuickForge() {
277
283
  }
278
284
 
279
285
  // --- Route dispatching ---
280
- async function handleApi(req, res, url) {
286
+ async function handleApi(req, res, url, requestContext = {}) {
281
287
  const pathname = url.pathname
282
288
  const parts = pathname.split('/').filter(Boolean)
283
289
 
@@ -297,22 +303,22 @@ async function handleApi(req, res, url) {
297
303
  return
298
304
  }
299
305
 
300
- if (pathname === '/api/lan-access/status' || pathname === '/api/lan-access/settings' || pathname === '/api/lan-access/unlock' || pathname === '/api/lan-access/logout' || pathname === '/api/lan-access/revoke-all') {
306
+ if (pathname.startsWith('/api/lan-access/')) {
301
307
  await handleLanAccessApi(req, res, url, {
302
308
  port,
303
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
309
+ isLocalRequest: requestContext.isLocalRequest === true,
304
310
  })
305
311
  return
306
312
  }
307
313
 
308
314
  // Health check
309
315
  if (req.method === 'GET' && pathname === '/api/health') {
310
- sendJson(res, 200, await getSystemStatus())
316
+ sendJson(res, 200, await getSystemStatus(requestContext))
311
317
  return
312
318
  }
313
319
 
314
320
  if (req.method === 'GET' && pathname === '/api/project/commands') {
315
- await handleProjectApi(req, res, url)
321
+ await handleProjectApi(req, res, url, requestContext)
316
322
  return
317
323
  }
318
324
 
@@ -355,7 +361,9 @@ async function handleApi(req, res, url) {
355
361
  // Channels
356
362
  if (pathname === '/api/channels' || pathname.startsWith('/api/channels/')) {
357
363
  await handleChannelsApi(req, res, url, {
358
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
364
+ isLocalRequest: requestContext.isLocalRequest === true,
365
+ logsDir,
366
+ openPathInFileManager,
359
367
  })
360
368
  return
361
369
  }
@@ -368,13 +376,13 @@ async function handleApi(req, res, url) {
368
376
 
369
377
  // Project routes
370
378
  if (pathname === '/api/project' || pathname.startsWith('/api/project/')) {
371
- await handleProjectApi(req, res, url)
379
+ await handleProjectApi(req, res, url, requestContext)
372
380
  return
373
381
  }
374
382
 
375
383
  // Project workspace inspector routes
376
384
  if (pathname === '/api/workspace/tree' || pathname === '/api/workspace/file' || pathname === '/api/workspace/resolve-path' || pathname === '/api/workspace/open-external' || pathname.startsWith('/api/workspace/preview/')) {
377
- await handleWorkspaceApi(req, res, url)
385
+ await handleWorkspaceApi(req, res, url, requestContext)
378
386
  return
379
387
  }
380
388
 
@@ -422,13 +430,13 @@ async function handleApi(req, res, url) {
422
430
  // System routes
423
431
  if (pathname === '/api/system/status' || pathname === '/api/system/restart' || pathname === '/api/system/network' || pathname === '/api/system/network-proxy' || pathname === '/api/system/network-proxy/refresh' || pathname === '/api/system/terminal-shell' || pathname === '/api/system/about' || pathname === '/api/system/update/check' || pathname === '/api/system/update/desktop' || pathname === '/api/system/update') {
424
432
  await handleSystemApi(req, res, url, {
425
- getSystemStatus,
433
+ getSystemStatus: () => getSystemStatus(requestContext),
426
434
  requestRestart,
427
435
  getPackageInfo: () => getPackageInfo(projectRoot),
428
436
  checkForUpdates: () => checkForUpdates(projectRoot),
429
437
  checkDesktopRelease: () => checkDesktopRelease(projectRoot),
430
438
  updateQuickForge,
431
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
439
+ isLocalRequest: requestContext.isLocalRequest === true,
432
440
  getTerminalShellSetting: readTerminalShellSetting,
433
441
  updateTerminalShellSetting,
434
442
  getTerminalShellConfig: readTerminalShellConfig,
@@ -446,7 +454,7 @@ async function handleApi(req, res, url) {
446
454
  // Terminal routes (local-only; real shell access)
447
455
  if (pathname === '/api/terminal/capabilities' || pathname === '/api/terminal/sessions' || pathname.startsWith('/api/terminal/sessions/')) {
448
456
  await handleTerminalApi(req, res, url, {
449
- isLocalRequest: isLoopbackAddress(req.socket.remoteAddress),
457
+ isLocalRequest: requestContext.isLocalRequest === true,
450
458
  })
451
459
  return
452
460
  }
@@ -532,6 +540,7 @@ function isLanAccessBootstrapPath(pathname) {
532
540
 
533
541
  function isStaticAssetPath(pathname) {
534
542
  return pathname.startsWith('/assets/')
543
+ || pathname.startsWith('/downloads/')
535
544
  || pathname === '/favicon.svg'
536
545
  || pathname === '/vite.svg'
537
546
  || pathname === '/manifest.webmanifest'
@@ -610,7 +619,7 @@ const server = createServer(async (req, res) => {
610
619
  }
611
620
 
612
621
  if (url.pathname.startsWith('/api/')) {
613
- await handleApi(req, res, url)
622
+ await handleApi(req, res, url, { isLocalRequest: !isRemoteRequest })
614
623
  return
615
624
  }
616
625
 
@@ -674,7 +683,11 @@ server.on('upgrade', (req, socket, head) => {
674
683
  await ensureStorage()
675
684
  await initializeNetworkProxy()
676
685
  installAiHttpLogger()
677
- initializeChannels({ projectRoot })
686
+ initializeChannels({
687
+ projectRoot,
688
+ channelEventsUrl: `http://127.0.0.1:${port}/api/channels/events`,
689
+ logsDir,
690
+ })
678
691
  await resetStaleTaskStatuses()
679
692
  await initializeActiveProject()
680
693
  setActiveWorkspaceRootForFilesystem(getWorkspaceRoot())
@@ -80,13 +80,41 @@ async function writeLanAccessFile(config) {
80
80
  await fs.writeFile(LAN_ACCESS_FILE, `${JSON.stringify(normalizeConfig(config), null, 2)}\n`, 'utf8')
81
81
  }
82
82
 
83
+ function normalizeText(value, maxLength) {
84
+ if (typeof value !== 'string') return undefined
85
+ const text = value.trim()
86
+ return text ? text.slice(0, maxLength) : undefined
87
+ }
88
+
89
+ function normalizeRemoteAddress(value) {
90
+ const address = normalizeText(value, 128)
91
+ return address?.startsWith('::ffff:') ? address.slice(7) : address
92
+ }
93
+
94
+ function tokenRecordId(tokenRecord) {
95
+ const id = normalizeText(tokenRecord?.id, 128)
96
+ return id || `legacy_${sha256Base64Url(tokenRecord?.tokenHash || '').slice(0, 24)}`
97
+ }
98
+
99
+ function publicTokenRecord(tokenRecord) {
100
+ return {
101
+ id: tokenRecordId(tokenRecord),
102
+ address: normalizeRemoteAddress(tokenRecord.remoteAddress),
103
+ userAgent: normalizeText(tokenRecord.userAgent, 300),
104
+ issuedAt: tokenRecord.issuedAt,
105
+ expiresAt: tokenRecord.expiresAt,
106
+ }
107
+ }
108
+
83
109
  function publicStatus(config) {
110
+ const activeTokens = pruneTokens(config.tokens)
84
111
  return {
85
112
  enabled: Boolean(config.enabled),
86
113
  hasPassword: Boolean(config.passwordHash),
87
114
  sessionTtlHours: config.sessionTtlHours,
88
115
  authVersion: config.authVersion || 1,
89
- activeTokenCount: pruneTokens(config.tokens).length,
116
+ activeTokenCount: activeTokens.length,
117
+ activeDevices: activeTokens.map(publicTokenRecord),
90
118
  updatedAt: config.updatedAt,
91
119
  }
92
120
  }
@@ -98,6 +126,12 @@ function pruneTokens(tokens, now = Date.now()) {
98
126
  if (!tokenRecord.expiresAt) return true
99
127
  return Date.parse(tokenRecord.expiresAt) > now
100
128
  })
129
+ .map((tokenRecord) => ({
130
+ ...tokenRecord,
131
+ id: tokenRecordId(tokenRecord),
132
+ remoteAddress: normalizeRemoteAddress(tokenRecord.remoteAddress),
133
+ userAgent: normalizeText(tokenRecord.userAgent, 300),
134
+ }))
101
135
  .slice(-LAN_TOKEN_MAX_COUNT)
102
136
  }
103
137
 
@@ -162,11 +196,58 @@ export async function revokeLanAccessTokens() {
162
196
  })
163
197
  }
164
198
 
199
+ export async function revokeLanAccessTokenById(id) {
200
+ const normalizedId = normalizeText(id, 128)
201
+ if (!normalizedId) {
202
+ const error = new Error('LAN access session id is required.')
203
+ error.statusCode = 400
204
+ throw error
205
+ }
206
+ return enqueueWrite(writeQueueName, async () => {
207
+ const current = await readLanAccessFile()
208
+ const tokens = pruneTokens(current.tokens)
209
+ const nextTokens = tokens.filter((tokenRecord) => tokenRecordId(tokenRecord) !== normalizedId)
210
+ if (nextTokens.length === tokens.length) {
211
+ const error = new Error('LAN access session not found.')
212
+ error.statusCode = 404
213
+ throw error
214
+ }
215
+ const next = normalizeConfig({
216
+ ...current,
217
+ tokens: nextTokens,
218
+ updatedAt: new Date().toISOString(),
219
+ })
220
+ await writeLanAccessFile(next)
221
+ return publicStatus(next)
222
+ })
223
+ }
224
+
225
+ export async function revokeLanAccessToken(token) {
226
+ if (!token || typeof token !== 'string') return false
227
+ const [versionText, secret] = token.split('.')
228
+ if (!secret) return false
229
+ const actualHash = sha256Base64Url(secret)
230
+ return enqueueWrite(writeQueueName, async () => {
231
+ const current = await readLanAccessFile()
232
+ if (Number(versionText) !== (current.authVersion || 1)) return false
233
+ const tokens = pruneTokens(current.tokens)
234
+ const nextTokens = tokens.filter((tokenRecord) => !safeHashEqual(tokenRecord.tokenHash, actualHash))
235
+ if (nextTokens.length === tokens.length) return false
236
+ const next = normalizeConfig({
237
+ ...current,
238
+ tokens: nextTokens,
239
+ updatedAt: new Date().toISOString(),
240
+ })
241
+ await writeLanAccessFile(next)
242
+ return true
243
+ })
244
+ }
245
+
165
246
  export function lanAccessCookieName() {
166
247
  return 'qf_lan_access'
167
248
  }
168
249
 
169
- export async function issueLanAccessToken(password) {
250
+ export async function issueLanAccessToken(password, metadata = {}) {
170
251
  return enqueueWrite(writeQueueName, async () => {
171
252
  const current = await readLanAccessFile()
172
253
  if (!current.enabled || !current.passwordHash) {
@@ -185,11 +266,20 @@ export async function issueLanAccessToken(password) {
185
266
  const issuedAt = new Date().toISOString()
186
267
  const ttlMs = normalizeSessionTtlHours(current.sessionTtlHours) * 60 * 60 * 1000
187
268
  const expiresAt = new Date(Date.now() + ttlMs).toISOString()
269
+ const tokenRecord = {
270
+ id: createRandomToken(18),
271
+ tokenHash,
272
+ issuedAt,
273
+ expiresAt,
274
+ authVersion: current.authVersion || 1,
275
+ remoteAddress: normalizeRemoteAddress(metadata.remoteAddress),
276
+ userAgent: normalizeText(metadata.userAgent, 300),
277
+ }
188
278
  const next = normalizeConfig({
189
279
  ...current,
190
280
  tokens: [
191
281
  ...pruneTokens(current.tokens),
192
- { tokenHash, issuedAt, expiresAt, authVersion: current.authVersion || 1 },
282
+ tokenRecord,
193
283
  ].slice(-LAN_TOKEN_MAX_COUNT),
194
284
  updatedAt: issuedAt,
195
285
  })
@@ -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,7 @@ 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
+ export const DEFAULT_PLUGIN_TOOL_TIMEOUT_MS = 2 * 60 * 1000
19
20
  let cachedCatalog = null
20
21
  let cachedCatalogKey = null
21
22
  let catalogDirty = false
@@ -29,6 +30,56 @@ function isPlainObject(value) {
29
30
  return Boolean(value && typeof value === 'object' && !Array.isArray(value))
30
31
  }
31
32
 
33
+ function withTimeout(promise, timeoutMs, message, onTimeout, signal) {
34
+ let timer
35
+ let timedOut = false
36
+ let abortHandler
37
+ const guardedPromise = promise.catch((error) => {
38
+ if (timedOut) return new Promise(() => {})
39
+ throw error
40
+ })
41
+ const timeoutPromise = new Promise((_, reject) => {
42
+ const rejectForAbort = () => {
43
+ timedOut = true
44
+ reject(new Error('Plugin tool call aborted'))
45
+ }
46
+ if (signal?.aborted) {
47
+ rejectForAbort()
48
+ return
49
+ }
50
+ if (signal) {
51
+ abortHandler = rejectForAbort
52
+ signal.addEventListener('abort', abortHandler, { once: true })
53
+ }
54
+ timer = setTimeout(() => {
55
+ timedOut = true
56
+ try {
57
+ onTimeout?.()
58
+ } finally {
59
+ reject(new Error(message))
60
+ }
61
+ }, timeoutMs)
62
+ })
63
+ return Promise.race([guardedPromise, timeoutPromise]).finally(() => {
64
+ clearTimeout(timer)
65
+ if (abortHandler) signal?.removeEventListener('abort', abortHandler)
66
+ })
67
+ }
68
+
69
+ function combineAbortSignals(parentSignal, timeoutSignal) {
70
+ if (!parentSignal) return timeoutSignal
71
+ if (typeof AbortSignal.any === 'function') return AbortSignal.any([parentSignal, timeoutSignal])
72
+
73
+ const controller = new AbortController()
74
+ const abort = () => controller.abort()
75
+ if (parentSignal.aborted || timeoutSignal.aborted) controller.abort()
76
+ else {
77
+ parentSignal.addEventListener('abort', abort, { once: true })
78
+ timeoutSignal.addEventListener('abort', abort, { once: true })
79
+ }
80
+ return controller.signal
81
+ }
82
+
32
83
  async function exists(filePath) {
33
84
  try {
34
85
  await fs.access(filePath)
@@ -308,10 +359,19 @@ export async function callPluginTool(toolName, params = {}, toolContext = {}) {
308
359
  throw error
309
360
  }
310
361
 
311
- const result = await handler.callTool(tool.name, params, {
312
- ...toolContext,
313
- plugin: { name: plugin.name, dir: plugin.dir, config: plugin.config },
314
- })
362
+ const timeoutController = new AbortController()
363
+ const signal = combineAbortSignals(toolContext.signal, timeoutController.signal)
364
+ const result = await withTimeout(
365
+ Promise.resolve().then(() => handler.callTool(tool.name, params, {
366
+ ...toolContext,
367
+ signal,
368
+ plugin: { name: plugin.name, dir: plugin.dir, config: plugin.config },
369
+ })),
370
+ DEFAULT_PLUGIN_TOOL_TIMEOUT_MS,
371
+ `Plugin tool ${toolName} timed out after ${DEFAULT_PLUGIN_TOOL_TIMEOUT_MS}ms`,
372
+ () => timeoutController.abort(),
373
+ toolContext.signal,
374
+ )
315
375
 
316
376
  return {
317
377
  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,4 +1,4 @@
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'
@@ -90,7 +90,7 @@ Rules:
90
90
  - If the user requests Chinese, write label, description, and systemPrompt in Chinese; otherwise match the user's language.`
91
91
 
92
92
  try {
93
- const stream = streamSimple(
93
+ const stream = streamSimpleWithAiHttpLogging(
94
94
  model,
95
95
  {
96
96
  systemPrompt,