@shawnstack/quickforge 1.7.2 → 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 -0
  2. package/bin/quickforge.mjs +2 -21
  3. package/dist/assets/AgentProfilesPage-B84w-r9S.js +1 -0
  4. package/dist/assets/{ChatPanelHost-Cld9HcVz.js → ChatPanelHost-CyO8fEoj.js} +98 -98
  5. package/dist/assets/{PluginsPage-DEMqwhOA.js → PluginsPage-D2862wul.js} +1 -1
  6. package/dist/assets/{ScheduledTasksPage-DTP_gedp.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-Bp6kbIup.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-DiJAUfvW.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 +7 -1
  131. package/server/acp/server.mjs +14 -9
  132. package/server/agent-manager.mjs +215 -20
  133. package/server/ai-http-logger.mjs +69 -12
  134. package/server/ai-provider-options.mjs +11 -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 +43 -25
  141. package/server/lan-access-store.mjs +93 -3
  142. package/server/network-proxy.mjs +449 -0
  143. package/server/plugins/registry.mjs +64 -4
  144. package/server/public-api.mjs +10 -2
  145. package/server/routes/agent-profiles.mjs +4 -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 +7 -3
  150. package/server/routes/project.mjs +19 -1
  151. package/server/routes/scheduled-tasks.mjs +4 -2
  152. package/server/routes/static.mjs +26 -1
  153. package/server/routes/system.mjs +37 -0
  154. package/server/routes/terminal.mjs +7 -2
  155. package/server/routes/workspace.mjs +62 -9
  156. package/server/session-utils.mjs +4 -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-BFgyFa5e.js +0 -1
  165. package/dist/assets/SettingsWorkspacePage-Dr9zT5Bs.js +0 -1495
  166. package/dist/assets/SharedConversationPage-op3DRwcw.js +0 -1
  167. package/dist/assets/TerminalDock-BL_UovwU.js +0 -2
  168. package/dist/assets/WorkspaceInspector-73dhAime.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-Cu2bBHLv.css +0 -3
  182. package/dist/assets/index-yV1wtqTr.js +0 -66
  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
@@ -0,0 +1,449 @@
1
+ import tls from 'node:tls'
2
+ import { Agent, DecoratorHandler, Dispatcher, ProxyAgent } from 'undici'
3
+ import { SocksClient } from 'socks'
4
+ import { atomicUpdate, readStore } from './storage.mjs'
5
+
6
+ const SETTINGS_KEY = 'network-proxy'
7
+ const VALID_MODES = new Set(['direct', 'system', 'manual', 'pac'])
8
+ const PROXY_ENV_KEYS = [
9
+ 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
10
+ 'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy',
11
+ ]
12
+ const ORIGINAL_FETCH = globalThis.fetch.bind(globalThis)
13
+ const FETCH_PATCH_MARKER = Symbol.for('quickforge.networkProxy.fetchPatched')
14
+
15
+ let currentConfig = { mode: 'direct', proxyUrl: '' }
16
+ let hostRuntime = null
17
+ let nativeResolver = null
18
+ let nativeResolverError = null
19
+ let networkDispatcher = null
20
+ let initialized = false
21
+ let lastAppliedAt = null
22
+
23
+ export function normalizeNetworkProxyConfig(value) {
24
+ const mode = VALID_MODES.has(value?.mode) ? value.mode : 'direct'
25
+ const proxyUrl = typeof value?.proxyUrl === 'string' ? value.proxyUrl.trim() : ''
26
+ return { mode, proxyUrl }
27
+ }
28
+
29
+ export function validateManualProxyUrl(value) {
30
+ let url
31
+ try {
32
+ url = new URL(String(value || '').trim())
33
+ } catch {
34
+ const error = new Error('Proxy address must be a valid HTTP or HTTPS URL')
35
+ error.statusCode = 400
36
+ throw error
37
+ }
38
+
39
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
40
+ const error = new Error('Only HTTP and HTTPS proxy addresses are supported')
41
+ error.statusCode = 400
42
+ throw error
43
+ }
44
+ if (!url.hostname || !url.port) {
45
+ const error = new Error('Proxy address must include a host and port')
46
+ error.statusCode = 400
47
+ throw error
48
+ }
49
+ if (url.username || url.password) {
50
+ const error = new Error('Proxy credentials are not supported in the proxy address')
51
+ error.statusCode = 400
52
+ throw error
53
+ }
54
+ if ((url.pathname && url.pathname !== '/') || url.search || url.hash) {
55
+ const error = new Error('Proxy address cannot include a path, query, or fragment')
56
+ error.statusCode = 400
57
+ throw error
58
+ }
59
+
60
+ return `${url.protocol}//${url.host}`
61
+ }
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
+
97
+ function isLoopbackUrl(input) {
98
+ let url
99
+ try {
100
+ if (typeof Request !== 'undefined' && input instanceof Request) url = new URL(input.url)
101
+ else url = new URL(input instanceof URL ? input.href : String(input))
102
+ } catch {
103
+ return false
104
+ }
105
+ return ['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(url.hostname)
106
+ }
107
+
108
+ function withoutProxyEnvironment(callback) {
109
+ const previous = new Map()
110
+ for (const key of PROXY_ENV_KEYS) {
111
+ previous.set(key, process.env[key])
112
+ delete process.env[key]
113
+ }
114
+ try {
115
+ return callback()
116
+ } finally {
117
+ for (const [key, value] of previous) {
118
+ if (value === undefined) delete process.env[key]
119
+ else process.env[key] = value
120
+ }
121
+ }
122
+ }
123
+
124
+ async function getNativeResolver() {
125
+ if (nativeResolver) return nativeResolver
126
+ if (nativeResolverError) throw nativeResolverError
127
+
128
+ try {
129
+ const { ProxyResolver } = await import('@vscode/os-proxy-resolver')
130
+ nativeResolver = withoutProxyEnvironment(() => new ProxyResolver())
131
+ return nativeResolver
132
+ } catch (error) {
133
+ nativeResolverError = error instanceof Error ? error : new Error(String(error))
134
+ throw nativeResolverError
135
+ }
136
+ }
137
+
138
+ function parseProxyHost(value) {
139
+ const raw = String(value || '').trim()
140
+ const url = new URL(raw.includes('://') ? raw : `http://${raw}`)
141
+ return {
142
+ host: url.hostname,
143
+ port: Number(url.port || (url.protocol === 'https:' ? 443 : 80)),
144
+ secure: url.protocol === 'https:',
145
+ }
146
+ }
147
+
148
+ function createSocksConnector(proxyHost) {
149
+ const proxy = parseProxyHost(proxyHost)
150
+ return (options, callback) => {
151
+ const destinationHost = options.hostname || options.host
152
+ const destinationPort = Number(options.port || (options.protocol === 'https:' ? 443 : 80))
153
+ let settled = false
154
+
155
+ const complete = (error, socket) => {
156
+ if (settled) return
157
+ settled = true
158
+ callback(error, socket)
159
+ }
160
+
161
+ SocksClient.createConnection({
162
+ proxy: {
163
+ host: proxy.host,
164
+ port: proxy.port,
165
+ type: 5,
166
+ },
167
+ command: 'connect',
168
+ destination: {
169
+ host: destinationHost,
170
+ port: destinationPort,
171
+ },
172
+ timeout: Number(options.timeout || 10000),
173
+ }).then(({ socket }) => {
174
+ if (options.protocol !== 'https:') {
175
+ complete(null, socket)
176
+ return
177
+ }
178
+ const secureSocket = tls.connect({
179
+ socket,
180
+ servername: options.servername || destinationHost,
181
+ ALPNProtocols: ['http/1.1'],
182
+ })
183
+ secureSocket.once('secureConnect', () => complete(null, secureSocket))
184
+ secureSocket.once('error', (error) => complete(error))
185
+ }).catch((error) => complete(error))
186
+
187
+ return () => {
188
+ settled = true
189
+ }
190
+ }
191
+ }
192
+
193
+ class RetryHandler extends DecoratorHandler {
194
+ constructor(dispatcher, options, handler, retry) {
195
+ super(handler)
196
+ this.dispatcher = dispatcher
197
+ this.options = options
198
+ this.retry = retry
199
+ this.receivedResponse = false
200
+ }
201
+
202
+ onResponseStart(controller, statusCode, headers, statusMessage) {
203
+ this.receivedResponse = true
204
+ return super.onResponseStart(controller, statusCode, headers, statusMessage)
205
+ }
206
+
207
+ onResponseError(controller, error) {
208
+ if (!this.receivedResponse) {
209
+ void this.retry(error)
210
+ return
211
+ }
212
+ return super.onResponseError(controller, error)
213
+ }
214
+ }
215
+
216
+ class SystemProxyDispatcher extends Dispatcher {
217
+ constructor(resolver) {
218
+ super()
219
+ this.resolver = resolver
220
+ this.directAgent = new Agent()
221
+ this.dispatchers = new Map()
222
+ }
223
+
224
+ dispatcherFor(route) {
225
+ if (!route || route.kind === 'direct') return this.directAgent
226
+ const key = `${route.kind}:${route.host}`
227
+ if (this.dispatchers.has(key)) return this.dispatchers.get(key)
228
+
229
+ let dispatcher
230
+ if (route.kind === 'http') {
231
+ const proxy = parseProxyHost(route.host)
232
+ dispatcher = new ProxyAgent({ uri: `${proxy.secure ? 'https' : 'http'}://${proxy.host}:${proxy.port}` })
233
+ } else if (route.kind === 'socks') {
234
+ dispatcher = new Agent({ connect: createSocksConnector(route.host) })
235
+ } else {
236
+ dispatcher = this.directAgent
237
+ }
238
+ this.dispatchers.set(key, dispatcher)
239
+ return dispatcher
240
+ }
241
+
242
+ dispatch(options, handler) {
243
+ const url = `${options.origin}${options.path}`
244
+ let routes = []
245
+ let index = 0
246
+
247
+ const tryNext = async (lastError) => {
248
+ if (index >= routes.length) {
249
+ handler.onResponseError?.(null, lastError || new Error('No usable system proxy route'))
250
+ return
251
+ }
252
+ const route = routes[index]
253
+ index += 1
254
+ const dispatcher = this.dispatcherFor(route)
255
+ const retryHandler = new RetryHandler(dispatcher, options, handler, async (error) => {
256
+ if (route?.kind !== 'direct') this.resolver.reportProxyFailed(route)
257
+ await tryNext(error)
258
+ })
259
+ try {
260
+ dispatcher.dispatch(options, retryHandler)
261
+ } catch (error) {
262
+ if (route?.kind !== 'direct') this.resolver.reportProxyFailed(route)
263
+ await tryNext(error)
264
+ }
265
+ }
266
+
267
+ void this.resolver.resolve(url).then((resolvedRoutes) => {
268
+ routes = Array.isArray(resolvedRoutes) && resolvedRoutes.length ? resolvedRoutes : [{ kind: 'direct' }]
269
+ return tryNext()
270
+ }).catch((error) => handler.onResponseError?.(null, error))
271
+
272
+ return true
273
+ }
274
+
275
+ async close() {
276
+ const dispatchers = [this.directAgent, ...this.dispatchers.values()]
277
+ await Promise.allSettled(dispatchers.map((dispatcher) => dispatcher.close()))
278
+ this.dispatchers.clear()
279
+ }
280
+
281
+ async destroy(error) {
282
+ const dispatchers = [this.directAgent, ...this.dispatchers.values()]
283
+ await Promise.allSettled(dispatchers.map((dispatcher) => dispatcher.destroy(error)))
284
+ this.dispatchers.clear()
285
+ }
286
+ }
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
+
305
+ async function createNetworkDispatcher(config) {
306
+ if (config.mode === 'manual') return new ProxyAgent(config.proxyUrl)
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')
309
+ return new Agent()
310
+ }
311
+
312
+ async function replaceNetworkDispatcher(config) {
313
+ const previous = networkDispatcher
314
+ networkDispatcher = await createNetworkDispatcher(config)
315
+ if (previous) void previous.close().catch(() => {})
316
+ }
317
+
318
+ function installNetworkFetch() {
319
+ if (globalThis[FETCH_PATCH_MARKER]) return
320
+ globalThis.fetch = async (input, init = {}) => {
321
+ if (isLoopbackUrl(input)) return ORIGINAL_FETCH(input, init)
322
+ if (hostRuntime) {
323
+ if (currentConfig.mode === 'direct') return ORIGINAL_FETCH(input, init)
324
+ return hostRuntime.fetch(input, init)
325
+ }
326
+ if (!networkDispatcher) return ORIGINAL_FETCH(input, init)
327
+ return ORIGINAL_FETCH(input, { ...init, dispatcher: networkDispatcher })
328
+ }
329
+ globalThis[FETCH_PATCH_MARKER] = true
330
+ }
331
+
332
+ export function registerHostNetworkRuntime(runtime) {
333
+ hostRuntime = runtime || null
334
+ }
335
+
336
+ async function persistConfig(config) {
337
+ await atomicUpdate('settings', (settings) => ({
338
+ ...settings,
339
+ [SETTINGS_KEY]: config,
340
+ }))
341
+ }
342
+
343
+ async function applyConfig(config) {
344
+ const normalized = normalizeNetworkProxyConfig(config)
345
+ if (normalized.mode === 'manual') normalized.proxyUrl = validateManualProxyUrl(normalized.proxyUrl)
346
+ if (normalized.mode === 'pac') normalized.proxyUrl = validatePacUrl(normalized.proxyUrl)
347
+
348
+ if (hostRuntime) await hostRuntime.apply(normalized)
349
+ else await replaceNetworkDispatcher(normalized)
350
+
351
+ currentConfig = normalized
352
+ lastAppliedAt = new Date().toISOString()
353
+ return currentConfig
354
+ }
355
+
356
+ async function nativeStatus() {
357
+ try {
358
+ const resolver = await getNativeResolver()
359
+ const config = await resolver.readProxyConfig()
360
+ const source = config.platform?.kind || process.platform
361
+ return {
362
+ supported: true,
363
+ source,
364
+ features: {
365
+ pac: config.configuredPac?.state !== 'unsupported',
366
+ pacUrl: false,
367
+ wpad: config.wpadDns?.state !== 'unsupported' || config.wpadDhcp?.state !== 'unsupported',
368
+ httpProxy: true,
369
+ httpsProxy: true,
370
+ socks: true,
371
+ },
372
+ }
373
+ } catch (error) {
374
+ return {
375
+ supported: false,
376
+ source: 'none',
377
+ features: { pac: false, pacUrl: false, wpad: false, httpProxy: false, httpsProxy: false, socks: false },
378
+ error: error instanceof Error ? error.message : String(error),
379
+ }
380
+ }
381
+ }
382
+
383
+ export async function getNetworkProxyStatus() {
384
+ const runtimeStatus = hostRuntime
385
+ ? await hostRuntime.getStatus()
386
+ : await nativeStatus()
387
+ const configuredMode = currentConfig.mode
388
+ const modeSupported = configuredMode === 'system'
389
+ ? runtimeStatus.supported
390
+ : configuredMode !== 'pac' || runtimeStatus.features?.pacUrl === true
391
+ return {
392
+ configuredMode,
393
+ effectiveMode: modeSupported ? configuredMode : 'unsupported',
394
+ proxyUrl: currentConfig.proxyUrl,
395
+ runtimeKind: hostRuntime ? 'electron-inline' : (process.versions.electron ? 'electron-node' : 'node'),
396
+ lastAppliedAt,
397
+ ...runtimeStatus,
398
+ }
399
+ }
400
+
401
+ export async function initializeNetworkProxy() {
402
+ if (initialized) return { config: currentConfig, status: await getNetworkProxyStatus() }
403
+ installNetworkFetch()
404
+ const settings = await readStore('settings')
405
+ await applyConfig(settings?.[SETTINGS_KEY])
406
+ initialized = true
407
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
408
+ }
409
+
410
+ export async function getNetworkProxyConfig() {
411
+ if (!initialized) await initializeNetworkProxy()
412
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
413
+ }
414
+
415
+ export async function updateNetworkProxyConfig(value) {
416
+ if (!initialized) await initializeNetworkProxy()
417
+ const previous = currentConfig
418
+ const next = normalizeNetworkProxyConfig(value)
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
+ }
429
+
430
+ await applyConfig(next)
431
+ try {
432
+ await persistConfig(currentConfig)
433
+ } catch (error) {
434
+ await applyConfig(previous)
435
+ throw error
436
+ }
437
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
438
+ }
439
+
440
+ export async function refreshSystemProxy() {
441
+ if (!initialized) await initializeNetworkProxy()
442
+ nativeResolver?.close?.()
443
+ nativeResolver = null
444
+ nativeResolverError = null
445
+ if (hostRuntime) await hostRuntime.refresh()
446
+ else await replaceNetworkDispatcher(currentConfig)
447
+ lastAppliedAt = new Date().toISOString()
448
+ return { config: currentConfig, status: await getNetworkProxyStatus() }
449
+ }
@@ -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
 
@@ -140,6 +144,10 @@ async function waitForQuickForge(options = {}) {
140
144
  }
141
145
 
142
146
  export async function startQuickForge(options = {}) {
147
+ if (options.networkRuntime) {
148
+ const { registerHostNetworkRuntime } = await import('./network-proxy.mjs')
149
+ registerHostNetworkRuntime(options.networkRuntime)
150
+ }
143
151
  const existingHealth = options.reuseExisting === false ? null : await checkQuickForgeHealth(options)
144
152
  const url = getQuickForgeUrl(options)
145
153
  const healthUrl = getQuickForgeHealthUrl(options)
@@ -187,7 +195,7 @@ export async function startQuickForge(options = {}) {
187
195
  stdio: options.stdio || 'ignore',
188
196
  windowsHide: true,
189
197
  shell: false,
190
- env: buildEnv(options),
198
+ env: createNodeProcessEnv(buildEnv(options)),
191
199
  })
192
200
 
193
201
  let exitInfo = null
@@ -1,4 +1,5 @@
1
- import { streamSimple } from '@earendil-works/pi-ai/compat'
1
+ import { streamSimpleWithAiHttpLogging } from '../ai-http-logger.mjs'
2
+ import { DEFAULT_AI_MAX_RETRIES } from '../ai-provider-options.mjs'
2
3
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
3
4
  import { readStore } from '../storage.mjs'
4
5
  import { logger } from '../utils/logger.mjs'
@@ -89,7 +90,7 @@ Rules:
89
90
  - If the user requests Chinese, write label, description, and systemPrompt in Chinese; otherwise match the user's language.`
90
91
 
91
92
  try {
92
- const stream = streamSimple(
93
+ const stream = streamSimpleWithAiHttpLogging(
93
94
  model,
94
95
  {
95
96
  systemPrompt,
@@ -101,6 +102,7 @@ Rules:
101
102
  maxTokens: 1600,
102
103
  temperature: 0,
103
104
  reasoning: thinkingLevel === 'off' ? undefined : thinkingLevel,
105
+ maxRetries: DEFAULT_AI_MAX_RETRIES,
104
106
  maxRetryDelayMs: 60000,
105
107
  },
106
108
  )
@@ -1,4 +1,5 @@
1
1
  import { sendJson, readJsonBody, decodeSegment } from '../utils/response.mjs'
2
+ import { logger } from '../utils/logger.mjs'
2
3
  import {
3
4
  createAgent,
4
5
  runPrompt,
@@ -96,6 +97,30 @@ export async function handleAgentApi(req, res, url) {
96
97
  return
97
98
  }
98
99
 
100
+ // POST /api/agents/:sessionId/restore — restore once and return the authoritative state
101
+ if (req.method === 'POST' && subPath === 'restore') {
102
+ const startedAt = performance.now()
103
+ const session = await restoreAgent(sessionId)
104
+ if (!session) {
105
+ const error = new Error('Session not found')
106
+ error.statusCode = 404
107
+ throw error
108
+ }
109
+ const state = getSessionState(sessionId)
110
+ if (!state) {
111
+ const error = new Error('Session not found')
112
+ error.statusCode = 404
113
+ throw error
114
+ }
115
+ logger.debug(`Restored session ${sessionId} for client`, {
116
+ sessionId,
117
+ messageCount: state.messages?.length ?? 0,
118
+ durationMs: Math.round((performance.now() - startedAt) * 10) / 10,
119
+ })
120
+ sendJson(res, 200, state)
121
+ return
122
+ }
123
+
99
124
  // GET /api/agents/:sessionId/state — get session state
100
125
  if (req.method === 'GET' && subPath === 'state') {
101
126
  let state = getSessionState(sessionId)
@@ -141,6 +166,9 @@ export async function handleAgentApi(req, res, url) {
141
166
  status: session.status,
142
167
  scope: session.scope,
143
168
  title: session.title,
169
+ source: session.source || undefined,
170
+ channelId: session.channelId || undefined,
171
+ channelName: session.channelName || undefined,
144
172
  accessMode: session.accessMode,
145
173
  yoloMode: session.yoloMode,
146
174
  })