@zythum02/fello-server 0.1.55 → 0.1.57

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 (396) hide show
  1. package/out/renderer/assets/PopoverPopup-BcHY-oda.js +591 -0
  2. package/out/renderer/assets/PopoverPopup-BcHY-oda.js.br +0 -0
  3. package/out/renderer/assets/PopoverPopup-BcHY-oda.js.gz +0 -0
  4. package/out/renderer/assets/{_baseUniq-BdLqGxOi.js → _baseUniq-KD2XGr9F.js} +1 -1
  5. package/out/renderer/assets/_baseUniq-KD2XGr9F.js.br +0 -0
  6. package/out/renderer/assets/_baseUniq-KD2XGr9F.js.gz +0 -0
  7. package/out/renderer/assets/{arc-UpL8po5R.js → arc-CR7wDdln.js} +1 -1
  8. package/out/renderer/assets/arc-CR7wDdln.js.br +0 -0
  9. package/out/renderer/assets/arc-CR7wDdln.js.gz +0 -0
  10. package/out/renderer/assets/{architectureDiagram-Q4EWVU46-ManEkEbl.js → architectureDiagram-Q4EWVU46-ob7rXg_N.js} +6 -6
  11. package/out/renderer/assets/architectureDiagram-Q4EWVU46-ob7rXg_N.js.br +0 -0
  12. package/out/renderer/assets/architectureDiagram-Q4EWVU46-ob7rXg_N.js.gz +0 -0
  13. package/out/renderer/assets/{arrow-left-BnuWerui.js → arrow-left-Ck_ZVJr5.js} +1 -1
  14. package/out/renderer/assets/{arrow-up-quD46bYg.js → arrow-up-D7cOrlYE.js} +4 -15
  15. package/out/renderer/assets/{automation-cVl19jbl.js → automation-W30eYgU8.js} +36 -13
  16. package/out/renderer/assets/automation-W30eYgU8.js.br +0 -0
  17. package/out/renderer/assets/automation-W30eYgU8.js.gz +0 -0
  18. package/out/renderer/assets/{blockDiagram-DXYQGD6D-B7n8X8F6.js → blockDiagram-DXYQGD6D-DH0dgWHF.js} +7 -7
  19. package/out/renderer/assets/blockDiagram-DXYQGD6D-DH0dgWHF.js.br +0 -0
  20. package/out/renderer/assets/blockDiagram-DXYQGD6D-DH0dgWHF.js.gz +0 -0
  21. package/out/renderer/assets/{c4Diagram-AHTNJAMY-BoP_JNY0.js → c4Diagram-AHTNJAMY--55RlH_-.js} +3 -3
  22. package/out/renderer/assets/c4Diagram-AHTNJAMY--55RlH_-.js.br +0 -0
  23. package/out/renderer/assets/c4Diagram-AHTNJAMY--55RlH_-.js.gz +0 -0
  24. package/out/renderer/assets/{channel-BTAU8ahK.js → channel-Bz679Wzq.js} +1 -1
  25. package/out/renderer/assets/{chunk-4BX2VUAB-BCKz1EgZ.js → chunk-4BX2VUAB-0jn0O1rt.js} +1 -1
  26. package/out/renderer/assets/{chunk-4TB4RGXK-B2HhnMqB.js → chunk-4TB4RGXK-BI1TetiG.js} +5 -5
  27. package/out/renderer/assets/chunk-4TB4RGXK-BI1TetiG.js.br +0 -0
  28. package/out/renderer/assets/chunk-4TB4RGXK-BI1TetiG.js.gz +0 -0
  29. package/out/renderer/assets/{chunk-55IACEB6-Drurz_4k.js → chunk-55IACEB6-9LfmpuHC.js} +1 -1
  30. package/out/renderer/assets/{chunk-EDXVE4YY-Xfghcg7b.js → chunk-EDXVE4YY-CtHVLcvG.js} +1 -1
  31. package/out/renderer/assets/chunk-EDXVE4YY-CtHVLcvG.js.br +0 -0
  32. package/out/renderer/assets/chunk-EDXVE4YY-CtHVLcvG.js.gz +0 -0
  33. package/out/renderer/assets/{chunk-FMBD7UC4-Birgz8kz.js → chunk-FMBD7UC4-eUJaG2CL.js} +1 -1
  34. package/out/renderer/assets/{chunk-OYMX7WX6-Cv4jgShd.js → chunk-OYMX7WX6-D-sPmxYW.js} +3 -3
  35. package/out/renderer/assets/chunk-OYMX7WX6-D-sPmxYW.js.br +0 -0
  36. package/out/renderer/assets/chunk-OYMX7WX6-D-sPmxYW.js.gz +0 -0
  37. package/out/renderer/assets/{chunk-QZHKN3VN-DkqKjktH.js → chunk-QZHKN3VN-Pdv-mSqK.js} +1 -1
  38. package/out/renderer/assets/{chunk-YZCP3GAM-CJK_tBTq.js → chunk-YZCP3GAM-DLpllNY2.js} +1 -1
  39. package/out/renderer/assets/chunk-YZCP3GAM-DLpllNY2.js.br +0 -0
  40. package/out/renderer/assets/chunk-YZCP3GAM-DLpllNY2.js.gz +0 -0
  41. package/out/renderer/assets/{circle-x-C9_Yp0Tg.js → circle-x-Cs_T6g0B.js} +1 -1
  42. package/out/renderer/assets/{classDiagram-v2-HSJHXN6E-Bf4To0VI.js → classDiagram-6PBFFD2Q-BQx6mUfF.js} +7 -7
  43. package/out/renderer/assets/{classDiagram-6PBFFD2Q-Bf4To0VI.js → classDiagram-v2-HSJHXN6E-BQx6mUfF.js} +7 -7
  44. package/out/renderer/assets/{clone-Ci5J50Pn.js → clone-DsYDv_WX.js} +1 -1
  45. package/out/renderer/assets/{code-editor-BwPwET3m.js → code-editor-Ce-8PAj3.js} +2 -2
  46. package/out/renderer/assets/code-editor-Ce-8PAj3.js.br +0 -0
  47. package/out/renderer/assets/code-editor-Ce-8PAj3.js.gz +0 -0
  48. package/out/renderer/assets/{collapsible-CMQ8h8ID.js → collapsible-CKgHeKAs.js} +1 -1
  49. package/out/renderer/assets/collapsible-CKgHeKAs.js.br +0 -0
  50. package/out/renderer/assets/collapsible-CKgHeKAs.js.gz +0 -0
  51. package/out/renderer/assets/{command-nKlwdb6i.js → command-Cgkdasyl.js} +1 -1
  52. package/out/renderer/assets/{cose-bilkent-S5V4N54A-BZpcn2q9.js → cose-bilkent-S5V4N54A-hNCr9Ibx.js} +2 -2
  53. package/out/renderer/assets/cose-bilkent-S5V4N54A-hNCr9Ibx.js.br +0 -0
  54. package/out/renderer/assets/cose-bilkent-S5V4N54A-hNCr9Ibx.js.gz +0 -0
  55. package/out/renderer/assets/{dagre-KV5264BT-onHZodsn.js → dagre-KV5264BT-CGyBht8M.js} +7 -7
  56. package/out/renderer/assets/dagre-KV5264BT-CGyBht8M.js.br +0 -0
  57. package/out/renderer/assets/dagre-KV5264BT-CGyBht8M.js.gz +0 -0
  58. package/out/renderer/assets/{diagram-5BDNPKRD-y3QVYwp7.js → diagram-5BDNPKRD-BiZfiCzA.js} +7 -7
  59. package/out/renderer/assets/diagram-5BDNPKRD-BiZfiCzA.js.br +0 -0
  60. package/out/renderer/assets/diagram-5BDNPKRD-BiZfiCzA.js.gz +0 -0
  61. package/out/renderer/assets/{diagram-G4DWMVQ6-LB3RUjrJ.js → diagram-G4DWMVQ6-CWj4c_xE.js} +7 -7
  62. package/out/renderer/assets/diagram-G4DWMVQ6-CWj4c_xE.js.br +0 -0
  63. package/out/renderer/assets/diagram-G4DWMVQ6-CWj4c_xE.js.gz +0 -0
  64. package/out/renderer/assets/{diagram-MMDJMWI5-7NpY26IL.js → diagram-MMDJMWI5-iBnXAZDJ.js} +6 -6
  65. package/out/renderer/assets/diagram-MMDJMWI5-iBnXAZDJ.js.br +0 -0
  66. package/out/renderer/assets/diagram-MMDJMWI5-iBnXAZDJ.js.gz +0 -0
  67. package/out/renderer/assets/{diagram-TYMM5635-DRM1RRr4.js → diagram-TYMM5635-C7IGtL42.js} +6 -6
  68. package/out/renderer/assets/diagram-TYMM5635-C7IGtL42.js.br +0 -0
  69. package/out/renderer/assets/diagram-TYMM5635-C7IGtL42.js.gz +0 -0
  70. package/out/renderer/assets/{ellipsis-D_eCkauJ.js → ellipsis-DSui0Rae.js} +1 -1
  71. package/out/renderer/assets/{erDiagram-SMLLAGMA-DNIZ639C.js → erDiagram-SMLLAGMA-ChPUl_x9.js} +5 -5
  72. package/out/renderer/assets/erDiagram-SMLLAGMA-ChPUl_x9.js.br +0 -0
  73. package/out/renderer/assets/erDiagram-SMLLAGMA-ChPUl_x9.js.gz +0 -0
  74. package/out/renderer/assets/external-link-CoW4VuI3.js +15 -0
  75. package/out/renderer/assets/{field-YZTi9Jxc.js → field-CXofwfDF.js} +1 -1
  76. package/out/renderer/assets/field-CXofwfDF.js.br +0 -0
  77. package/out/renderer/assets/field-CXofwfDF.js.gz +0 -0
  78. package/out/renderer/assets/{file-text-DDtS1g4Q.js → file-text-CwFSQGtt.js} +1 -1
  79. package/out/renderer/assets/{flowDiagram-DWJPFMVM-C0ghMibN.js → flowDiagram-DWJPFMVM-C5Zt4Sfn.js} +7 -7
  80. package/out/renderer/assets/flowDiagram-DWJPFMVM-C5Zt4Sfn.js.br +0 -0
  81. package/out/renderer/assets/flowDiagram-DWJPFMVM-C5Zt4Sfn.js.gz +0 -0
  82. package/out/renderer/assets/{ganttDiagram-T4ZO3ILL-tqeHqMu4.js → ganttDiagram-T4ZO3ILL-Cp30RG1g.js} +3 -3
  83. package/out/renderer/assets/ganttDiagram-T4ZO3ILL-Cp30RG1g.js.br +0 -0
  84. package/out/renderer/assets/ganttDiagram-T4ZO3ILL-Cp30RG1g.js.gz +0 -0
  85. package/out/renderer/assets/{gitGraphDiagram-UUTBAWPF-D6acFhfE.js → gitGraphDiagram-UUTBAWPF-CkfzOUvM.js} +7 -7
  86. package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-CkfzOUvM.js.br +0 -0
  87. package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-CkfzOUvM.js.gz +0 -0
  88. package/out/renderer/assets/{graph-DqFGMaSz.js → graph-HfEt9-p2.js} +2 -2
  89. package/out/renderer/assets/graph-HfEt9-p2.js.br +0 -0
  90. package/out/renderer/assets/graph-HfEt9-p2.js.gz +0 -0
  91. package/out/renderer/assets/{highlighted-body-KPVGNVTW-D2IZ7Enb.js → highlighted-body-KPVGNVTW-CUokS6r6.js} +2 -2
  92. package/out/renderer/assets/index-Byirxhp5.css +382 -0
  93. package/out/renderer/assets/index-Byirxhp5.css.br +0 -0
  94. package/out/renderer/assets/index-Byirxhp5.css.gz +0 -0
  95. package/out/renderer/assets/{index-BeEZXfJZ.js → index-CKDwIiIb.js} +2277 -923
  96. package/out/renderer/assets/index-CKDwIiIb.js.br +0 -0
  97. package/out/renderer/assets/index-CKDwIiIb.js.gz +0 -0
  98. package/out/renderer/assets/{infoDiagram-42DDH7IO-BJeydI3V.js → infoDiagram-42DDH7IO-DbQvToBf.js} +5 -5
  99. package/out/renderer/assets/infoDiagram-42DDH7IO-DbQvToBf.js.br +0 -0
  100. package/out/renderer/assets/infoDiagram-42DDH7IO-DbQvToBf.js.gz +0 -0
  101. package/out/renderer/assets/{ishikawaDiagram-UXIWVN3A-DPgToD8D.js → ishikawaDiagram-UXIWVN3A-Crmx2UcL.js} +2 -2
  102. package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-Crmx2UcL.js.br +0 -0
  103. package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-Crmx2UcL.js.gz +0 -0
  104. package/out/renderer/assets/{item-SQsw2AyN.js → item-2gdt65LZ.js} +1 -1
  105. package/out/renderer/assets/item-2gdt65LZ.js.br +0 -0
  106. package/out/renderer/assets/item-2gdt65LZ.js.gz +0 -0
  107. package/out/renderer/assets/{journeyDiagram-VCZTEJTY-CAnZXd__.js → journeyDiagram-VCZTEJTY-BD3MzoDm.js} +5 -5
  108. package/out/renderer/assets/journeyDiagram-VCZTEJTY-BD3MzoDm.js.br +0 -0
  109. package/out/renderer/assets/journeyDiagram-VCZTEJTY-BD3MzoDm.js.gz +0 -0
  110. package/out/renderer/assets/{kanban-definition-6JOO6SKY-VCfgwCTi.js → kanban-definition-6JOO6SKY-DlPWdcq8.js} +3 -3
  111. package/out/renderer/assets/kanban-definition-6JOO6SKY-DlPWdcq8.js.br +0 -0
  112. package/out/renderer/assets/kanban-definition-6JOO6SKY-DlPWdcq8.js.gz +0 -0
  113. package/out/renderer/assets/keyboard-BmncEbVT.js +21 -0
  114. package/out/renderer/assets/{layout-lw2KhCMB.js → layout-OHsAY-70.js} +4 -4
  115. package/out/renderer/assets/layout-OHsAY-70.js.br +0 -0
  116. package/out/renderer/assets/layout-OHsAY-70.js.gz +0 -0
  117. package/out/renderer/assets/{linear-DmbMR7a-.js → linear-CnBTAk2r.js} +1 -1
  118. package/out/renderer/assets/linear-CnBTAk2r.js.br +0 -0
  119. package/out/renderer/assets/linear-CnBTAk2r.js.gz +0 -0
  120. package/out/renderer/assets/{link-2-CWpGKoWR.js → link-2-CABYiITN.js} +3 -15
  121. package/out/renderer/assets/{mermaid-HWGCJPDP-BufLuIVq.js → mermaid-HWGCJPDP-OB5tfzeR.js} +58 -54
  122. package/out/renderer/assets/mermaid-HWGCJPDP-OB5tfzeR.js.br +0 -0
  123. package/out/renderer/assets/mermaid-HWGCJPDP-OB5tfzeR.js.gz +0 -0
  124. package/out/renderer/assets/{mic-UZHvCQhw.js → mic-Bu7eaTKt.js} +1 -1
  125. package/out/renderer/assets/{min-BCqStlR1.js → min-lM3sC06g.js} +2 -2
  126. package/out/renderer/assets/min-lM3sC06g.js.br +0 -0
  127. package/out/renderer/assets/min-lM3sC06g.js.gz +0 -0
  128. package/out/renderer/assets/{mindmap-definition-QFDTVHPH-C0dBO3f9.js → mindmap-definition-QFDTVHPH-2KHTVjBe.js} +4 -4
  129. package/out/renderer/assets/mindmap-definition-QFDTVHPH-2KHTVjBe.js.br +0 -0
  130. package/out/renderer/assets/mindmap-definition-QFDTVHPH-2KHTVjBe.js.gz +0 -0
  131. package/out/renderer/assets/{open-guide-CWoE2W97.js → open-guide-CKryRC5n.js} +1 -1
  132. package/out/renderer/assets/{package-search-SZPcR_1L.js → package-search-DPG91yZB.js} +1 -1
  133. package/out/renderer/assets/{pieDiagram-DEJITSTG-Dv_tH6Z_.js → pieDiagram-DEJITSTG-CWRoGvfQ.js} +7 -7
  134. package/out/renderer/assets/pieDiagram-DEJITSTG-CWRoGvfQ.js.br +0 -0
  135. package/out/renderer/assets/pieDiagram-DEJITSTG-CWRoGvfQ.js.gz +0 -0
  136. package/out/renderer/assets/{plus-BZ0wwKkh.js → plus-CFoa8VhI.js} +1 -1
  137. package/out/renderer/assets/{quadrantDiagram-34T5L4WZ-Cvsj5cyu.js → quadrantDiagram-34T5L4WZ-BJVP6FPm.js} +3 -3
  138. package/out/renderer/assets/quadrantDiagram-34T5L4WZ-BJVP6FPm.js.br +0 -0
  139. package/out/renderer/assets/quadrantDiagram-34T5L4WZ-BJVP6FPm.js.gz +0 -0
  140. package/out/renderer/assets/{requirementDiagram-MS252O5E-CxM7H308.js → requirementDiagram-MS252O5E-_MTj_l0V.js} +4 -4
  141. package/out/renderer/assets/requirementDiagram-MS252O5E-_MTj_l0V.js.br +0 -0
  142. package/out/renderer/assets/requirementDiagram-MS252O5E-_MTj_l0V.js.gz +0 -0
  143. package/out/renderer/assets/{resizable-DD8ADhVy.js → resizable-CcCcCDAN.js} +1 -1
  144. package/out/renderer/assets/resizable-CcCcCDAN.js.br +0 -0
  145. package/out/renderer/assets/resizable-CcCcCDAN.js.gz +0 -0
  146. package/out/renderer/assets/{sankeyDiagram-XADWPNL6-BGTOA6ee.js → sankeyDiagram-XADWPNL6-BWkr3adU.js} +2 -2
  147. package/out/renderer/assets/sankeyDiagram-XADWPNL6-BWkr3adU.js.br +0 -0
  148. package/out/renderer/assets/sankeyDiagram-XADWPNL6-BWkr3adU.js.gz +0 -0
  149. package/out/renderer/assets/{schedule-DMyyz9A1.js → schedule-H1BEvD9Q.js} +24 -11
  150. package/out/renderer/assets/schedule-H1BEvD9Q.js.br +0 -0
  151. package/out/renderer/assets/schedule-H1BEvD9Q.js.gz +0 -0
  152. package/out/renderer/assets/{search-XexOgLud.js → search-B3aFhEKs.js} +1 -1
  153. package/out/renderer/assets/{sequenceDiagram-FGHM5R23-DezlXo1X.js → sequenceDiagram-FGHM5R23-CfRs3_Dx.js} +4 -4
  154. package/out/renderer/assets/sequenceDiagram-FGHM5R23-CfRs3_Dx.js.br +0 -0
  155. package/out/renderer/assets/sequenceDiagram-FGHM5R23-CfRs3_Dx.js.gz +0 -0
  156. package/out/renderer/assets/{session-DQEqHTKb.js → session-D6NE3yNE.js} +2327 -2791
  157. package/out/renderer/assets/session-D6NE3yNE.js.br +0 -0
  158. package/out/renderer/assets/session-D6NE3yNE.js.gz +0 -0
  159. package/out/renderer/assets/setting-dialog-CANlzubW.js +5086 -0
  160. package/out/renderer/assets/setting-dialog-CANlzubW.js.br +0 -0
  161. package/out/renderer/assets/setting-dialog-CANlzubW.js.gz +0 -0
  162. package/out/renderer/assets/{settings-agents-f3HkdiTw.js → settings-agents-DxIwcY7M.js} +12 -12
  163. package/out/renderer/assets/settings-agents-DxIwcY7M.js.br +0 -0
  164. package/out/renderer/assets/settings-agents-DxIwcY7M.js.gz +0 -0
  165. package/out/renderer/assets/settings-general-B85uTAij.js +624 -0
  166. package/out/renderer/assets/settings-general-B85uTAij.js.br +0 -0
  167. package/out/renderer/assets/settings-general-B85uTAij.js.gz +0 -0
  168. package/out/renderer/assets/{settings-ilink-Da336Koc.js → settings-ilink-BgeA04Nv.js} +51 -20
  169. package/out/renderer/assets/settings-ilink-BgeA04Nv.js.br +0 -0
  170. package/out/renderer/assets/settings-ilink-BgeA04Nv.js.gz +0 -0
  171. package/out/renderer/assets/{settings-image-generation-RmWs73ZG.js → settings-image-generation-CsWv4wXY.js} +6 -6
  172. package/out/renderer/assets/settings-image-generation-CsWv4wXY.js.br +0 -0
  173. package/out/renderer/assets/settings-image-generation-CsWv4wXY.js.gz +0 -0
  174. package/out/renderer/assets/{settings-layout-DL6ZEzgP.js → settings-layout-DJbT4rIH.js} +32 -7
  175. package/out/renderer/assets/settings-layout-DJbT4rIH.js.br +0 -0
  176. package/out/renderer/assets/settings-layout-DJbT4rIH.js.gz +0 -0
  177. package/out/renderer/assets/{settings-mcp-BKW8JKtO.js → settings-mcp-Cz6GMogp.js} +97 -29
  178. package/out/renderer/assets/settings-mcp-Cz6GMogp.js.br +0 -0
  179. package/out/renderer/assets/settings-mcp-Cz6GMogp.js.gz +0 -0
  180. package/out/renderer/assets/{settings-memory-o4r-_vob.js → settings-memory-Cidr_Ghw.js} +2 -2
  181. package/out/renderer/assets/settings-memory-Cidr_Ghw.js.br +0 -0
  182. package/out/renderer/assets/settings-memory-Cidr_Ghw.js.gz +0 -0
  183. package/out/renderer/assets/settings-peripherals-DilwOpyO.js +269 -0
  184. package/out/renderer/assets/settings-peripherals-DilwOpyO.js.br +0 -0
  185. package/out/renderer/assets/settings-peripherals-DilwOpyO.js.gz +0 -0
  186. package/out/renderer/assets/{settings-shortcuts-BmUGS1q_.js → settings-shortcuts-dWdRCFxB.js} +16 -6
  187. package/out/renderer/assets/settings-shortcuts-dWdRCFxB.js.br +0 -0
  188. package/out/renderer/assets/settings-shortcuts-dWdRCFxB.js.gz +0 -0
  189. package/out/renderer/assets/{settings-snippets-CpAOJmDF.js → settings-snippets-CrtJYwLV.js} +9 -9
  190. package/out/renderer/assets/settings-snippets-CrtJYwLV.js.br +0 -0
  191. package/out/renderer/assets/settings-snippets-CrtJYwLV.js.gz +0 -0
  192. package/out/renderer/assets/settings-speech-CteE8F4J.js +1369 -0
  193. package/out/renderer/assets/settings-speech-CteE8F4J.js.br +0 -0
  194. package/out/renderer/assets/settings-speech-CteE8F4J.js.gz +0 -0
  195. package/out/renderer/assets/{settings-webui-DIEKvTrI.js → settings-webui-DKiOzDSD.js} +3 -3
  196. package/out/renderer/assets/settings-webui-DKiOzDSD.js.br +0 -0
  197. package/out/renderer/assets/settings-webui-DKiOzDSD.js.gz +0 -0
  198. package/out/renderer/assets/{skills-installed-BuWBDk6r.js → skills-installed-Beqe9G7p.js} +4 -4
  199. package/out/renderer/assets/skills-installed-Beqe9G7p.js.br +0 -0
  200. package/out/renderer/assets/skills-installed-Beqe9G7p.js.gz +0 -0
  201. package/out/renderer/assets/{skills-layout-ByPhfHG6.js → skills-layout-BAzfcnkT.js} +2 -2
  202. package/out/renderer/assets/skills-layout-BAzfcnkT.js.br +0 -0
  203. package/out/renderer/assets/skills-layout-BAzfcnkT.js.gz +0 -0
  204. package/out/renderer/assets/{skills-skills-sh-wR7YNmAJ.js → skills-skills-sh-Dzocqfu4.js} +4 -4
  205. package/out/renderer/assets/skills-skills-sh-Dzocqfu4.js.br +0 -0
  206. package/out/renderer/assets/skills-skills-sh-Dzocqfu4.js.gz +0 -0
  207. package/out/renderer/assets/{settings-general-BcCOVmx3.js → slider-DEK94SlL.js} +4 -623
  208. package/out/renderer/assets/slider-DEK94SlL.js.br +0 -0
  209. package/out/renderer/assets/slider-DEK94SlL.js.gz +0 -0
  210. package/out/renderer/assets/{sortable.esm-BWvT9HIK.js → sortable.esm-Cm4rqFU7.js} +1 -1
  211. package/out/renderer/assets/sortable.esm-Cm4rqFU7.js.br +0 -0
  212. package/out/renderer/assets/sortable.esm-Cm4rqFU7.js.gz +0 -0
  213. package/out/renderer/assets/{standard-schema-BkTVIf7N.js → standard-schema-B43ZsT5c.js} +52 -1
  214. package/out/renderer/assets/standard-schema-B43ZsT5c.js.br +0 -0
  215. package/out/renderer/assets/standard-schema-B43ZsT5c.js.gz +0 -0
  216. package/out/renderer/assets/{stateDiagram-FHFEXIEX-xdKKCs-p.js → stateDiagram-FHFEXIEX-CaiRC_NA.js} +9 -9
  217. package/out/renderer/assets/stateDiagram-FHFEXIEX-CaiRC_NA.js.br +0 -0
  218. package/out/renderer/assets/stateDiagram-FHFEXIEX-CaiRC_NA.js.gz +0 -0
  219. package/out/renderer/assets/{stateDiagram-v2-QKLJ7IA2-BaaiQ6Kr.js → stateDiagram-v2-QKLJ7IA2-EA3ViVW-.js} +5 -5
  220. package/out/renderer/assets/{task-PHAOSE-W.js → task-DM-bWOFz.js} +13 -11
  221. package/out/renderer/assets/task-DM-bWOFz.js.br +0 -0
  222. package/out/renderer/assets/task-DM-bWOFz.js.gz +0 -0
  223. package/out/renderer/assets/{textarea-1ZWl3C-x.js → textarea-B0OAhnF7.js} +1 -1
  224. package/out/renderer/assets/{timeline-definition-GMOUNBTQ-DFLeLybV.js → timeline-definition-GMOUNBTQ-D-M9BAZz.js} +3 -3
  225. package/out/renderer/assets/timeline-definition-GMOUNBTQ-D-M9BAZz.js.br +0 -0
  226. package/out/renderer/assets/timeline-definition-GMOUNBTQ-D-M9BAZz.js.gz +0 -0
  227. package/out/renderer/assets/{vennDiagram-DHZGUBPP-PKmZLY7H.js → vennDiagram-DHZGUBPP-B_yjg_0U.js} +2 -2
  228. package/out/renderer/assets/vennDiagram-DHZGUBPP-B_yjg_0U.js.br +0 -0
  229. package/out/renderer/assets/vennDiagram-DHZGUBPP-B_yjg_0U.js.gz +0 -0
  230. package/out/renderer/assets/{wardley-RL74JXVD-BCH2vKnv.js → wardley-RL74JXVD-2Xd7XlwA.js} +4 -4
  231. package/out/renderer/assets/wardley-RL74JXVD-2Xd7XlwA.js.br +0 -0
  232. package/out/renderer/assets/wardley-RL74JXVD-2Xd7XlwA.js.gz +0 -0
  233. package/out/renderer/assets/{wardleyDiagram-NUSXRM2D-Bkd3xMno.js → wardleyDiagram-NUSXRM2D-BA9tzMk-.js} +6 -6
  234. package/out/renderer/assets/wardleyDiagram-NUSXRM2D-BA9tzMk-.js.br +0 -0
  235. package/out/renderer/assets/wardleyDiagram-NUSXRM2D-BA9tzMk-.js.gz +0 -0
  236. package/out/renderer/assets/{wrench-8H0LZ83G.js → wrench-QfEqqx9F.js} +1 -1
  237. package/out/renderer/assets/wrench-QfEqqx9F.js.br +0 -0
  238. package/out/renderer/assets/wrench-QfEqqx9F.js.gz +0 -0
  239. package/out/renderer/assets/{xlsx-view-BYx5Ha2P.js → xlsx-view-Bov2NXsI.js} +141 -79
  240. package/out/renderer/assets/xlsx-view-Bov2NXsI.js.br +0 -0
  241. package/out/renderer/assets/xlsx-view-Bov2NXsI.js.gz +0 -0
  242. package/out/renderer/assets/{xychartDiagram-5P7HB3ND-DCOm4uPr.js → xychartDiagram-5P7HB3ND-svs-11is.js} +3 -3
  243. package/out/renderer/assets/xychartDiagram-5P7HB3ND-svs-11is.js.br +0 -0
  244. package/out/renderer/assets/xychartDiagram-5P7HB3ND-svs-11is.js.gz +0 -0
  245. package/out/renderer/index.html +2 -2
  246. package/out/renderer/index.html.br +0 -0
  247. package/out/renderer/index.html.gz +0 -0
  248. package/out/scripts/mcp-toolbox/server.mjs +80 -1
  249. package/out/server/main.js +1576 -353
  250. package/package.json +3 -2
  251. package/out/renderer/assets/_baseUniq-BdLqGxOi.js.br +0 -0
  252. package/out/renderer/assets/_baseUniq-BdLqGxOi.js.gz +0 -0
  253. package/out/renderer/assets/arc-UpL8po5R.js.br +0 -0
  254. package/out/renderer/assets/arc-UpL8po5R.js.gz +0 -0
  255. package/out/renderer/assets/architectureDiagram-Q4EWVU46-ManEkEbl.js.br +0 -0
  256. package/out/renderer/assets/architectureDiagram-Q4EWVU46-ManEkEbl.js.gz +0 -0
  257. package/out/renderer/assets/automation-cVl19jbl.js.br +0 -0
  258. package/out/renderer/assets/automation-cVl19jbl.js.gz +0 -0
  259. package/out/renderer/assets/blockDiagram-DXYQGD6D-B7n8X8F6.js.br +0 -0
  260. package/out/renderer/assets/blockDiagram-DXYQGD6D-B7n8X8F6.js.gz +0 -0
  261. package/out/renderer/assets/c4Diagram-AHTNJAMY-BoP_JNY0.js.br +0 -0
  262. package/out/renderer/assets/c4Diagram-AHTNJAMY-BoP_JNY0.js.gz +0 -0
  263. package/out/renderer/assets/chunk-4TB4RGXK-B2HhnMqB.js.br +0 -0
  264. package/out/renderer/assets/chunk-4TB4RGXK-B2HhnMqB.js.gz +0 -0
  265. package/out/renderer/assets/chunk-EDXVE4YY-Xfghcg7b.js.br +0 -0
  266. package/out/renderer/assets/chunk-EDXVE4YY-Xfghcg7b.js.gz +0 -0
  267. package/out/renderer/assets/chunk-OYMX7WX6-Cv4jgShd.js.br +0 -0
  268. package/out/renderer/assets/chunk-OYMX7WX6-Cv4jgShd.js.gz +0 -0
  269. package/out/renderer/assets/chunk-YZCP3GAM-CJK_tBTq.js.br +0 -0
  270. package/out/renderer/assets/chunk-YZCP3GAM-CJK_tBTq.js.gz +0 -0
  271. package/out/renderer/assets/code-editor-BwPwET3m.js.br +0 -0
  272. package/out/renderer/assets/code-editor-BwPwET3m.js.gz +0 -0
  273. package/out/renderer/assets/collapsible-CMQ8h8ID.js.br +0 -0
  274. package/out/renderer/assets/collapsible-CMQ8h8ID.js.gz +0 -0
  275. package/out/renderer/assets/cose-bilkent-S5V4N54A-BZpcn2q9.js.br +0 -0
  276. package/out/renderer/assets/cose-bilkent-S5V4N54A-BZpcn2q9.js.gz +0 -0
  277. package/out/renderer/assets/dagre-KV5264BT-onHZodsn.js.br +0 -0
  278. package/out/renderer/assets/dagre-KV5264BT-onHZodsn.js.gz +0 -0
  279. package/out/renderer/assets/diagram-5BDNPKRD-y3QVYwp7.js.br +0 -0
  280. package/out/renderer/assets/diagram-5BDNPKRD-y3QVYwp7.js.gz +0 -0
  281. package/out/renderer/assets/diagram-G4DWMVQ6-LB3RUjrJ.js.br +0 -0
  282. package/out/renderer/assets/diagram-G4DWMVQ6-LB3RUjrJ.js.gz +0 -0
  283. package/out/renderer/assets/diagram-MMDJMWI5-7NpY26IL.js.br +0 -0
  284. package/out/renderer/assets/diagram-MMDJMWI5-7NpY26IL.js.gz +0 -0
  285. package/out/renderer/assets/diagram-TYMM5635-DRM1RRr4.js.br +0 -0
  286. package/out/renderer/assets/diagram-TYMM5635-DRM1RRr4.js.gz +0 -0
  287. package/out/renderer/assets/erDiagram-SMLLAGMA-DNIZ639C.js.br +0 -0
  288. package/out/renderer/assets/erDiagram-SMLLAGMA-DNIZ639C.js.gz +0 -0
  289. package/out/renderer/assets/field-YZTi9Jxc.js.br +0 -0
  290. package/out/renderer/assets/field-YZTi9Jxc.js.gz +0 -0
  291. package/out/renderer/assets/flowDiagram-DWJPFMVM-C0ghMibN.js.br +0 -0
  292. package/out/renderer/assets/flowDiagram-DWJPFMVM-C0ghMibN.js.gz +0 -0
  293. package/out/renderer/assets/ganttDiagram-T4ZO3ILL-tqeHqMu4.js.br +0 -0
  294. package/out/renderer/assets/ganttDiagram-T4ZO3ILL-tqeHqMu4.js.gz +0 -0
  295. package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-D6acFhfE.js.br +0 -0
  296. package/out/renderer/assets/gitGraphDiagram-UUTBAWPF-D6acFhfE.js.gz +0 -0
  297. package/out/renderer/assets/graph-DqFGMaSz.js.br +0 -0
  298. package/out/renderer/assets/graph-DqFGMaSz.js.gz +0 -0
  299. package/out/renderer/assets/index-BZTYKZe8.css +0 -382
  300. package/out/renderer/assets/index-BZTYKZe8.css.br +0 -0
  301. package/out/renderer/assets/index-BZTYKZe8.css.gz +0 -0
  302. package/out/renderer/assets/index-BeEZXfJZ.js.br +0 -0
  303. package/out/renderer/assets/index-BeEZXfJZ.js.gz +0 -0
  304. package/out/renderer/assets/infoDiagram-42DDH7IO-BJeydI3V.js.br +0 -0
  305. package/out/renderer/assets/infoDiagram-42DDH7IO-BJeydI3V.js.gz +0 -0
  306. package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-DPgToD8D.js.br +0 -0
  307. package/out/renderer/assets/ishikawaDiagram-UXIWVN3A-DPgToD8D.js.gz +0 -0
  308. package/out/renderer/assets/item-SQsw2AyN.js.br +0 -0
  309. package/out/renderer/assets/item-SQsw2AyN.js.gz +0 -0
  310. package/out/renderer/assets/journeyDiagram-VCZTEJTY-CAnZXd__.js.br +0 -0
  311. package/out/renderer/assets/journeyDiagram-VCZTEJTY-CAnZXd__.js.gz +0 -0
  312. package/out/renderer/assets/kanban-definition-6JOO6SKY-VCfgwCTi.js.br +0 -0
  313. package/out/renderer/assets/kanban-definition-6JOO6SKY-VCfgwCTi.js.gz +0 -0
  314. package/out/renderer/assets/layout-lw2KhCMB.js.br +0 -0
  315. package/out/renderer/assets/layout-lw2KhCMB.js.gz +0 -0
  316. package/out/renderer/assets/linear-DmbMR7a-.js.br +0 -0
  317. package/out/renderer/assets/linear-DmbMR7a-.js.gz +0 -0
  318. package/out/renderer/assets/link-2-CWpGKoWR.js.br +0 -0
  319. package/out/renderer/assets/link-2-CWpGKoWR.js.gz +0 -0
  320. package/out/renderer/assets/mermaid-HWGCJPDP-BufLuIVq.js.br +0 -0
  321. package/out/renderer/assets/mermaid-HWGCJPDP-BufLuIVq.js.gz +0 -0
  322. package/out/renderer/assets/min-BCqStlR1.js.br +0 -0
  323. package/out/renderer/assets/min-BCqStlR1.js.gz +0 -0
  324. package/out/renderer/assets/mindmap-definition-QFDTVHPH-C0dBO3f9.js.br +0 -0
  325. package/out/renderer/assets/mindmap-definition-QFDTVHPH-C0dBO3f9.js.gz +0 -0
  326. package/out/renderer/assets/pieDiagram-DEJITSTG-Dv_tH6Z_.js.br +0 -0
  327. package/out/renderer/assets/pieDiagram-DEJITSTG-Dv_tH6Z_.js.gz +0 -0
  328. package/out/renderer/assets/quadrantDiagram-34T5L4WZ-Cvsj5cyu.js.br +0 -0
  329. package/out/renderer/assets/quadrantDiagram-34T5L4WZ-Cvsj5cyu.js.gz +0 -0
  330. package/out/renderer/assets/requirementDiagram-MS252O5E-CxM7H308.js.br +0 -0
  331. package/out/renderer/assets/requirementDiagram-MS252O5E-CxM7H308.js.gz +0 -0
  332. package/out/renderer/assets/resizable-DD8ADhVy.js.br +0 -0
  333. package/out/renderer/assets/resizable-DD8ADhVy.js.gz +0 -0
  334. package/out/renderer/assets/sankeyDiagram-XADWPNL6-BGTOA6ee.js.br +0 -0
  335. package/out/renderer/assets/sankeyDiagram-XADWPNL6-BGTOA6ee.js.gz +0 -0
  336. package/out/renderer/assets/schedule-DMyyz9A1.js.br +0 -0
  337. package/out/renderer/assets/schedule-DMyyz9A1.js.gz +0 -0
  338. package/out/renderer/assets/sequenceDiagram-FGHM5R23-DezlXo1X.js.br +0 -0
  339. package/out/renderer/assets/sequenceDiagram-FGHM5R23-DezlXo1X.js.gz +0 -0
  340. package/out/renderer/assets/session-DQEqHTKb.js.br +0 -0
  341. package/out/renderer/assets/session-DQEqHTKb.js.gz +0 -0
  342. package/out/renderer/assets/setting-dialog-BdiZbd8J.js +0 -642
  343. package/out/renderer/assets/setting-dialog-BdiZbd8J.js.br +0 -0
  344. package/out/renderer/assets/setting-dialog-BdiZbd8J.js.gz +0 -0
  345. package/out/renderer/assets/settings-2-q22Cv4G7.js +0 -16
  346. package/out/renderer/assets/settings-agents-f3HkdiTw.js.br +0 -0
  347. package/out/renderer/assets/settings-agents-f3HkdiTw.js.gz +0 -0
  348. package/out/renderer/assets/settings-general-BcCOVmx3.js.br +0 -0
  349. package/out/renderer/assets/settings-general-BcCOVmx3.js.gz +0 -0
  350. package/out/renderer/assets/settings-ilink-Da336Koc.js.br +0 -0
  351. package/out/renderer/assets/settings-ilink-Da336Koc.js.gz +0 -0
  352. package/out/renderer/assets/settings-image-generation-RmWs73ZG.js.br +0 -0
  353. package/out/renderer/assets/settings-image-generation-RmWs73ZG.js.gz +0 -0
  354. package/out/renderer/assets/settings-layout-DL6ZEzgP.js.br +0 -0
  355. package/out/renderer/assets/settings-layout-DL6ZEzgP.js.gz +0 -0
  356. package/out/renderer/assets/settings-mcp-BKW8JKtO.js.br +0 -0
  357. package/out/renderer/assets/settings-mcp-BKW8JKtO.js.gz +0 -0
  358. package/out/renderer/assets/settings-memory-o4r-_vob.js.br +0 -0
  359. package/out/renderer/assets/settings-memory-o4r-_vob.js.gz +0 -0
  360. package/out/renderer/assets/settings-shortcuts-BmUGS1q_.js.br +0 -0
  361. package/out/renderer/assets/settings-shortcuts-BmUGS1q_.js.gz +0 -0
  362. package/out/renderer/assets/settings-snippets-CpAOJmDF.js.br +0 -0
  363. package/out/renderer/assets/settings-snippets-CpAOJmDF.js.gz +0 -0
  364. package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js +0 -1054
  365. package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js.br +0 -0
  366. package/out/renderer/assets/settings-speech-to-text-C8KHNGE4.js.gz +0 -0
  367. package/out/renderer/assets/settings-webui-DIEKvTrI.js.br +0 -0
  368. package/out/renderer/assets/settings-webui-DIEKvTrI.js.gz +0 -0
  369. package/out/renderer/assets/skills-installed-BuWBDk6r.js.br +0 -0
  370. package/out/renderer/assets/skills-installed-BuWBDk6r.js.gz +0 -0
  371. package/out/renderer/assets/skills-layout-ByPhfHG6.js.br +0 -0
  372. package/out/renderer/assets/skills-layout-ByPhfHG6.js.gz +0 -0
  373. package/out/renderer/assets/skills-skills-sh-wR7YNmAJ.js.br +0 -0
  374. package/out/renderer/assets/skills-skills-sh-wR7YNmAJ.js.gz +0 -0
  375. package/out/renderer/assets/sortable.esm-BWvT9HIK.js.br +0 -0
  376. package/out/renderer/assets/sortable.esm-BWvT9HIK.js.gz +0 -0
  377. package/out/renderer/assets/standard-schema-BkTVIf7N.js.br +0 -0
  378. package/out/renderer/assets/standard-schema-BkTVIf7N.js.gz +0 -0
  379. package/out/renderer/assets/stateDiagram-FHFEXIEX-xdKKCs-p.js.br +0 -0
  380. package/out/renderer/assets/stateDiagram-FHFEXIEX-xdKKCs-p.js.gz +0 -0
  381. package/out/renderer/assets/task-PHAOSE-W.js.br +0 -0
  382. package/out/renderer/assets/task-PHAOSE-W.js.gz +0 -0
  383. package/out/renderer/assets/timeline-definition-GMOUNBTQ-DFLeLybV.js.br +0 -0
  384. package/out/renderer/assets/timeline-definition-GMOUNBTQ-DFLeLybV.js.gz +0 -0
  385. package/out/renderer/assets/vennDiagram-DHZGUBPP-PKmZLY7H.js.br +0 -0
  386. package/out/renderer/assets/vennDiagram-DHZGUBPP-PKmZLY7H.js.gz +0 -0
  387. package/out/renderer/assets/wardley-RL74JXVD-BCH2vKnv.js.br +0 -0
  388. package/out/renderer/assets/wardley-RL74JXVD-BCH2vKnv.js.gz +0 -0
  389. package/out/renderer/assets/wardleyDiagram-NUSXRM2D-Bkd3xMno.js.br +0 -0
  390. package/out/renderer/assets/wardleyDiagram-NUSXRM2D-Bkd3xMno.js.gz +0 -0
  391. package/out/renderer/assets/wrench-8H0LZ83G.js.br +0 -0
  392. package/out/renderer/assets/wrench-8H0LZ83G.js.gz +0 -0
  393. package/out/renderer/assets/xlsx-view-BYx5Ha2P.js.br +0 -0
  394. package/out/renderer/assets/xlsx-view-BYx5Ha2P.js.gz +0 -0
  395. package/out/renderer/assets/xychartDiagram-5P7HB3ND-DCOm4uPr.js.br +0 -0
  396. package/out/renderer/assets/xychartDiagram-5P7HB3ND-DCOm4uPr.js.gz +0 -0
@@ -11,7 +11,7 @@ process.treeSitterWasmPath = join(resourcesPath, "tree-sitter-wasm");
11
11
 
12
12
  // src/backend/backend.ts
13
13
  import { readdir as readdir2, rm as rm3, readFile as readFile10 } from "fs/promises";
14
- import { join as join25 } from "path";
14
+ import { join as join26 } from "path";
15
15
 
16
16
  // src/backend/storage/index.ts
17
17
  import { join as join5 } from "path";
@@ -64,7 +64,8 @@ var DEFAULT_SETTINGS = {
64
64
  shortcuts: {},
65
65
  proxy: {
66
66
  mode: "off"
67
- }
67
+ },
68
+ peripherals: []
68
69
  };
69
70
  function settingsPath() {
70
71
  return join3(FELLO_DIR, "settings.json");
@@ -252,24 +253,51 @@ function readSettings() {
252
253
  model: p.model,
253
254
  active: typeof p.active === "boolean" ? p.active : false
254
255
  })) : [];
255
- const speechToText = Array.isArray(rawObj?.speechToText) ? rawObj.speechToText.filter((value) => isObject(value)).filter((value) => {
256
- const provider = value.provider;
257
- return typeof value.id === "string" && typeof value.name === "string" && typeof value.apiKey === "string" && (provider === "volcengine" || provider === "dashscope" || provider === "openai" || provider === "iflytek");
258
- }).map((value) => ({
256
+ const speechProviders = (() => {
257
+ const isSpeechProvider = (value) => {
258
+ const provider = value.provider;
259
+ return typeof value.id === "string" && typeof value.name === "string" && typeof value.apiKey === "string" && (provider === "volcengine" || provider === "dashscope" || provider === "openai" || provider === "iflytek");
260
+ };
261
+ const parseShared = (value) => ({
262
+ id: value.id,
263
+ name: value.name,
264
+ provider: value.provider,
265
+ apiKey: value.apiKey,
266
+ appId: typeof value.appId === "string" ? value.appId : void 0,
267
+ apiSecret: typeof value.apiSecret === "string" ? value.apiSecret : void 0,
268
+ baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : void 0,
269
+ workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : void 0,
270
+ region: value.region === "cn-beijing" || value.region === "ap-southeast-1" ? value.region : void 0,
271
+ workspace: typeof value.workspace === "string" ? value.workspace : void 0,
272
+ language: typeof value.language === "string" ? value.language : void 0
273
+ });
274
+ const rows = (input) => Array.isArray(input) ? input.filter((value) => isObject(value)).filter(isSpeechProvider) : [];
275
+ if (Array.isArray(rawObj?.speechProviders)) {
276
+ return rows(rawObj.speechProviders).map((value) => ({
277
+ ...parseShared(value),
278
+ asrModel: typeof value.asrModel === "string" ? value.asrModel : void 0,
279
+ asrResourceId: typeof value.asrResourceId === "string" ? value.asrResourceId : void 0,
280
+ ttsModel: typeof value.ttsModel === "string" ? value.ttsModel : void 0,
281
+ voice: typeof value.voice === "string" ? value.voice : void 0,
282
+ asrEnabled: typeof value.asrEnabled === "boolean" ? value.asrEnabled : false,
283
+ ttsEnabled: typeof value.ttsEnabled === "boolean" ? value.ttsEnabled : false
284
+ }));
285
+ }
286
+ return rows(rawObj?.speechToText).map((value) => ({
287
+ ...parseShared(value),
288
+ asrModel: typeof value.model === "string" ? value.model : void 0,
289
+ asrResourceId: typeof value.resourceId === "string" ? value.resourceId : void 0,
290
+ ttsModel: void 0,
291
+ voice: void 0,
292
+ asrEnabled: typeof value.active === "boolean" ? value.active : false,
293
+ ttsEnabled: false
294
+ }));
295
+ })();
296
+ const peripherals = Array.isArray(rawObj?.peripherals) ? rawObj.peripherals.filter(
297
+ (value) => typeof value === "object" && value !== null && !Array.isArray(value)
298
+ ).filter((value) => typeof value.id === "string").map((value) => ({
259
299
  id: value.id,
260
- name: value.name,
261
- provider: value.provider,
262
- apiKey: value.apiKey,
263
- appId: typeof value.appId === "string" ? value.appId : void 0,
264
- apiSecret: typeof value.apiSecret === "string" ? value.apiSecret : void 0,
265
- resourceId: typeof value.resourceId === "string" ? value.resourceId : void 0,
266
- model: typeof value.model === "string" ? value.model : void 0,
267
- baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : void 0,
268
- workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : void 0,
269
- region: value.region === "cn-beijing" || value.region === "ap-southeast-1" ? value.region : void 0,
270
- workspace: typeof value.workspace === "string" ? value.workspace : void 0,
271
- language: typeof value.language === "string" ? value.language : void 0,
272
- active: typeof value.active === "boolean" ? value.active : false
300
+ enabled: typeof value.enabled === "boolean" ? value.enabled : false
273
301
  })) : [];
274
302
  const editor = (() => {
275
303
  const raw2 = rawObj && isObject(rawObj.editor) ? rawObj.editor : null;
@@ -292,7 +320,8 @@ function readSettings() {
292
320
  proxy,
293
321
  snippets,
294
322
  imageGeneration,
295
- speechToText
323
+ speechProviders,
324
+ peripherals
296
325
  };
297
326
  } catch {
298
327
  return DEFAULT_SETTINGS;
@@ -404,21 +433,28 @@ function getSettings() {
404
433
  model: p.model,
405
434
  active: p.active
406
435
  })),
407
- speechToText: (meta.speechToText ?? []).map((p) => ({
436
+ speechProviders: (meta.speechProviders ?? []).map((p) => ({
408
437
  id: p.id,
409
438
  name: p.name,
410
439
  provider: p.provider,
411
440
  apiKey: p.apiKey,
412
441
  appId: p.appId,
413
442
  apiSecret: p.apiSecret,
414
- resourceId: p.resourceId,
415
- model: p.model,
416
443
  baseUrl: p.baseUrl,
417
444
  workspaceId: p.workspaceId,
418
445
  region: p.region,
419
446
  workspace: p.workspace,
420
447
  language: p.language,
421
- active: p.active
448
+ asrModel: p.asrModel,
449
+ asrResourceId: p.asrResourceId,
450
+ ttsModel: p.ttsModel,
451
+ voice: p.voice,
452
+ asrEnabled: p.asrEnabled,
453
+ ttsEnabled: p.ttsEnabled
454
+ })),
455
+ peripherals: (meta.peripherals ?? []).map((p) => ({
456
+ id: p.id,
457
+ enabled: p.enabled
422
458
  }))
423
459
  };
424
460
  }
@@ -589,22 +625,29 @@ function updateSettings(settings) {
589
625
  model: p.model,
590
626
  active: p.active
591
627
  })) : prevMeta.imageGeneration,
592
- speechToText: settings.speechToText ? settings.speechToText.map((p) => ({
628
+ speechProviders: settings.speechProviders ? settings.speechProviders.map((p) => ({
593
629
  id: p.id,
594
630
  name: p.name,
595
631
  provider: p.provider,
596
632
  apiKey: p.apiKey,
597
633
  appId: p.appId?.trim() || void 0,
598
634
  apiSecret: p.apiSecret?.trim() || void 0,
599
- resourceId: p.resourceId?.trim() || void 0,
600
- model: p.model?.trim() || void 0,
601
635
  baseUrl: p.baseUrl?.trim() || void 0,
602
636
  workspaceId: p.workspaceId?.trim() || void 0,
603
637
  region: p.region,
604
638
  workspace: p.workspace?.trim() || void 0,
605
639
  language: p.language?.trim() || void 0,
606
- active: p.active
607
- })) : prevMeta.speechToText
640
+ asrModel: p.asrModel?.trim() || void 0,
641
+ asrResourceId: p.asrResourceId?.trim() || void 0,
642
+ ttsModel: p.ttsModel?.trim() || void 0,
643
+ voice: p.voice?.trim() || void 0,
644
+ asrEnabled: p.asrEnabled,
645
+ ttsEnabled: p.ttsEnabled
646
+ })) : prevMeta.speechProviders ?? [],
647
+ peripherals: settings.peripherals ? settings.peripherals.map((p) => ({
648
+ id: p.id,
649
+ enabled: p.enabled
650
+ })) : prevMeta.peripherals ?? []
608
651
  };
609
652
  writeSettings(meta);
610
653
  }
@@ -1086,26 +1129,24 @@ var en_default = {
1086
1129
  promptRefusal: "\u26A0\uFE0F Model refused to respond",
1087
1130
  promptCancelled: "\u26A0\uFE0F Generation was cancelled",
1088
1131
  noActiveSession: "No active session",
1089
- title: "Title",
1090
- cwd: "Working Directory",
1091
1132
  projectDir: "Project Directory",
1092
1133
  agent: "Agent",
1093
1134
  features: "Features",
1094
1135
  mcpServers: "MCP Servers",
1095
1136
  project: "Project",
1096
- sessionInfo: "Session Info",
1097
1137
  sessionList: "Switch Session",
1098
1138
  sessionListDesc: "Please choose from the following sessions:",
1099
1139
  newSessionDesc: "Please choose from the following projects:",
1100
1140
  noSessions: "No sessions available.",
1101
1141
  switchSessionHint: "Reply with a number to switch session",
1102
1142
  switchSessionGuide: "\u{1F4A1} Send !s to switch session",
1103
- invalidSessionNumber: "Invalid number, please enter a number between {{min}}-{{max}}. Send a command to retry.",
1143
+ invalidMenuNumber: "Invalid number, please enter a number between {{min}}-{{max}}.",
1144
+ unknownCommand: "\u2753 Unknown command. Send ! to see the current session status and command list.",
1104
1145
  switchedToSession: "Switched to \u300C{{label}}\u300D",
1105
1146
  newSession: "New Chat",
1106
1147
  newSessionTitle: "New Session",
1107
1148
  noProjects: "No projects available.",
1108
- createSessionHint: "Reply with a number to create a new session (send other content to cancel)",
1149
+ createSessionHint: "Reply with a number to create a new session",
1109
1150
  createSessionGuide: "\u{1F4A1} Send !n to create a new session",
1110
1151
  noAgent: "No available agent. Please configure one in settings.",
1111
1152
  createdAndSwitched: "\u2705 Switched to new session in project\u300C{{project}}\u300D",
@@ -1120,7 +1161,32 @@ var en_default = {
1120
1161
  snippetListDesc: "Please choose a quick phrase:",
1121
1162
  noSnippets: "No quick phrases available. Please add some in settings.",
1122
1163
  selectSnippetHint: "Reply with a number to send the quick phrase",
1123
- snippetSent: "\u2705 Quick phrase\u300C{{title}}\u300Dhas been sent"
1164
+ snippetSent: "\u2705 Quick phrase\u300C{{title}}\u300Dhas been sent",
1165
+ featuresMenu: "Feature Switches",
1166
+ featuresMenuDesc: "Toggle the features below (applied immediately):",
1167
+ mcpMenu: "MCP Server Switches",
1168
+ mcpMenuDesc: "Toggle the MCP servers below (applied immediately):",
1169
+ toggleHint: "Reply with a number to toggle that item; send 0 to exit the menu",
1170
+ featureGuide: "\u{1F4A1} Send !f to toggle features",
1171
+ mcpServerGuide: "\u{1F4A1} Send !c to toggle MCP servers",
1172
+ noToggleItems: "Nothing to toggle here.",
1173
+ toggleCancelled: "Exited, nothing changed.",
1174
+ toggledOn: "\u2705 Enabled",
1175
+ toggledOff: "\u2705 Disabled",
1176
+ toggleSavedRestartFailed: "{{label}}, but restarting the session failed. The change will apply on the next resume.",
1177
+ permissionMenu: "Permission",
1178
+ permissionMenuDesc: "Please choose a permission mode:",
1179
+ permissionAsk: "Ask",
1180
+ permissionAllowAll: "Allow all",
1181
+ switchPermissionHint: "Reply with a number to switch permission mode",
1182
+ permissionGuide: "\u{1F4A1} Send !p to switch permission",
1183
+ permissionSwitched: "\u2705 Switched to {{mode}}",
1184
+ featureSkills: "Skills",
1185
+ featureSearch: "Search",
1186
+ featureImageGeneration: "Image Generation",
1187
+ featureMemory: "Memory",
1188
+ featureAskUser: "Ask Me",
1189
+ featureShareToUser: "Share to User"
1124
1190
  }
1125
1191
  };
1126
1192
 
@@ -1143,26 +1209,24 @@ var zh_CN_default = {
1143
1209
  promptRefusal: "\u26A0\uFE0F \u6A21\u578B\u62D2\u7EDD\u56DE\u7B54",
1144
1210
  promptCancelled: "\u26A0\uFE0F \u751F\u6210\u5DF2\u53D6\u6D88",
1145
1211
  noActiveSession: "\u5F53\u524D\u6CA1\u6709\u6D3B\u8DC3\u7684\u4F1A\u8BDD",
1146
- title: "\u6807\u9898",
1147
- cwd: "\u5DE5\u4F5C\u76EE\u5F55",
1148
1212
  projectDir: "\u9879\u76EE\u76EE\u5F55",
1149
1213
  agent: "Agent",
1150
1214
  features: "\u529F\u80FD",
1151
1215
  mcpServers: "MCP \u670D\u52A1\u5668",
1152
1216
  project: "\u9879\u76EE",
1153
- sessionInfo: "\u5F53\u524D\u4F1A\u8BDD",
1154
1217
  sessionList: "\u5207\u6362\u4F1A\u8BDD",
1155
1218
  sessionListDesc: "\u8BF7\u5728\u4E0B\u5217\u4F1A\u8BDD\u4E2D\u9009\u62E9\uFF1A",
1156
1219
  newSessionDesc: "\u8BF7\u5728\u4E0B\u5217\u9879\u76EE\u4E2D\u9009\u62E9\uFF1A",
1157
1220
  noSessions: "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u4F1A\u8BDD\u3002",
1158
1221
  switchSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u4F1A\u8BDD",
1159
1222
  switchSessionGuide: "\u{1F4A1} \u53D1\u9001 !s \u5207\u6362\u4F1A\u8BDD",
1160
- invalidSessionNumber: "\u65E0\u6548\u5E8F\u53F7\uFF0C\u8BF7\u8F93\u5165 {{min}}-{{max}} \u4E4B\u95F4\u7684\u6570\u5B57\u3002\u8BF7\u53D1\u9001\u6307\u4EE4\u91CD\u65B0\u8BBE\u7F6E\u3002",
1223
+ invalidMenuNumber: "\u65E0\u6548\u5E8F\u53F7\uFF0C\u8BF7\u8F93\u5165 {{min}}-{{max}} \u4E4B\u95F4\u7684\u6570\u5B57\u3002",
1224
+ unknownCommand: "\u2753 \u672A\u77E5\u547D\u4EE4\u3002\u53D1\u9001 ! \u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001\u4E0E\u547D\u4EE4\u5217\u8868\u3002",
1161
1225
  switchedToSession: "\u5DF2\u5207\u6362\u5230\u300C{{label}}\u300D",
1162
1226
  newSession: "\u65B0\u4F1A\u8BDD",
1163
1227
  newSessionTitle: "\u65B0\u5EFA\u4F1A\u8BDD",
1164
1228
  noProjects: "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u9879\u76EE\u3002",
1165
- createSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u521B\u5EFA\u65B0\u4F1A\u8BDD\uFF08\u53D1\u9001\u5176\u4ED6\u5185\u5BB9\u53D6\u6D88\uFF09",
1229
+ createSessionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u521B\u5EFA\u65B0\u4F1A\u8BDD",
1166
1230
  createSessionGuide: "\u{1F4A1} \u53D1\u9001 !n \u65B0\u5EFA\u4F1A\u8BDD",
1167
1231
  noAgent: "\u6CA1\u6709\u53EF\u7528\u7684 Agent\uFF0C\u8BF7\u5148\u5728\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u3002",
1168
1232
  createdAndSwitched: "\u2705 \u5DF2\u5207\u6362\u5230\u9879\u76EE\u300C{{project}}\u300D\u7684\u65B0\u4F1A\u8BDD",
@@ -1177,7 +1241,32 @@ var zh_CN_default = {
1177
1241
  snippetListDesc: "\u8BF7\u5728\u4E0B\u5217\u5FEB\u6377\u77ED\u8BED\u4E2D\u9009\u62E9\uFF1A",
1178
1242
  noSnippets: "\u5F53\u524D\u6CA1\u6709\u5FEB\u6377\u77ED\u8BED\uFF0C\u8BF7\u5148\u5728\u8BBE\u7F6E\u4E2D\u6DFB\u52A0\u3002",
1179
1243
  selectSnippetHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u53D1\u9001\u5FEB\u6377\u77ED\u8BED",
1180
- snippetSent: "\u2705 \u5FEB\u6377\u77ED\u8BED\u300C{{title}}\u300D\u5DF2\u53D1\u9001"
1244
+ snippetSent: "\u2705 \u5FEB\u6377\u77ED\u8BED\u300C{{title}}\u300D\u5DF2\u53D1\u9001",
1245
+ featuresMenu: "\u529F\u80FD\u5F00\u5173",
1246
+ featuresMenuDesc: "\u8BF7\u5728\u4E0B\u5217\u529F\u80FD\u4E2D\u5207\u6362\uFF08\u7ACB\u5373\u751F\u6548\uFF09\uFF1A",
1247
+ mcpMenu: "MCP \u670D\u52A1\u5668\u5F00\u5173",
1248
+ mcpMenuDesc: "\u8BF7\u5728\u4E0B\u5217 MCP \u670D\u52A1\u5668\u4E2D\u5207\u6362\uFF08\u7ACB\u5373\u751F\u6548\uFF09\uFF1A",
1249
+ toggleHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u8BE5\u9879\uFF0C\u53D1\u9001 0 \u9000\u51FA\u83DC\u5355",
1250
+ featureGuide: "\u{1F4A1} \u53D1\u9001 !f \u5207\u6362\u529F\u80FD",
1251
+ mcpServerGuide: "\u{1F4A1} \u53D1\u9001 !c \u5207\u6362 MCP \u670D\u52A1\u5668",
1252
+ noToggleItems: "\u5F53\u524D\u6CA1\u6709\u53EF\u5207\u6362\u7684\u9879\u3002",
1253
+ toggleCancelled: "\u5DF2\u9000\u51FA\uFF0C\u672A\u505A\u4FEE\u6539\u3002",
1254
+ toggledOn: "\u2705 \u5DF2\u5F00\u542F",
1255
+ toggledOff: "\u2705 \u5DF2\u5173\u95ED",
1256
+ toggleSavedRestartFailed: "{{label}}\uFF0C\u4F46\u4F1A\u8BDD\u91CD\u542F\u5931\u8D25\uFF0C\u6539\u52A8\u5C06\u5728\u4E0B\u6B21\u6062\u590D\u4F1A\u8BDD\u65F6\u751F\u6548\u3002",
1257
+ permissionMenu: "\u6743\u9650",
1258
+ permissionMenuDesc: "\u8BF7\u5728\u4E0B\u5217\u6743\u9650\u6A21\u5F0F\u4E2D\u9009\u62E9\uFF1A",
1259
+ permissionAsk: "\u6BCF\u6B21\u8BE2\u95EE",
1260
+ permissionAllowAll: "\u5168\u90E8\u5141\u8BB8",
1261
+ switchPermissionHint: "\u8BF7\u56DE\u590D\u5E8F\u53F7\u5207\u6362\u6743\u9650\u6A21\u5F0F",
1262
+ permissionGuide: "\u{1F4A1} \u53D1\u9001 !p \u5207\u6362\u6743\u9650",
1263
+ permissionSwitched: "\u2705 \u5DF2\u5207\u6362\u4E3A {{mode}}",
1264
+ featureSkills: "\u6280\u80FD",
1265
+ featureSearch: "\u9879\u76EE\u68C0\u7D22",
1266
+ featureImageGeneration: "\u56FE\u7247\u751F\u6210",
1267
+ featureMemory: "\u8BB0\u5FC6",
1268
+ featureAskUser: "\u5411\u6211\u63D0\u95EE",
1269
+ featureShareToUser: "\u5206\u4EAB\u7ED9\u7528\u6237"
1181
1270
  }
1182
1271
  };
1183
1272
 
@@ -1366,10 +1455,35 @@ import { join as join8 } from "path";
1366
1455
  import { readFile as readFile3, stat as stat2 } from "fs/promises";
1367
1456
 
1368
1457
  // src/backend/serve-file.ts
1458
+ import { createReadStream } from "fs";
1369
1459
  import { stat, readFile as readFile2 } from "fs/promises";
1370
1460
  import { resolve, relative, isAbsolute } from "path";
1371
1461
  import * as mimeTypes from "mime-types";
1372
- async function serveFile(filename, cwd) {
1462
+ var RANGE_RE = /^bytes=(\d*)-(\d*)$/;
1463
+ function parseRange(header, size) {
1464
+ const raw = header?.trim();
1465
+ if (!raw) return null;
1466
+ const match = RANGE_RE.exec(raw);
1467
+ if (!match) return null;
1468
+ const [, startRaw = "", endRaw = ""] = match;
1469
+ if (!startRaw && !endRaw) return null;
1470
+ let start;
1471
+ let end;
1472
+ if (!startRaw) {
1473
+ const suffix = Number(endRaw);
1474
+ if (!Number.isFinite(suffix) || suffix <= 0) return null;
1475
+ start = Math.max(0, size - suffix);
1476
+ end = size - 1;
1477
+ } else {
1478
+ start = Number(startRaw);
1479
+ end = endRaw ? Math.min(Number(endRaw), size - 1) : size - 1;
1480
+ }
1481
+ if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
1482
+ if (start >= size) return "unsatisfiable";
1483
+ if (end < start) return null;
1484
+ return { start, end };
1485
+ }
1486
+ async function serveFile(filename, cwd, options = {}) {
1373
1487
  const safeCwd = resolve(cwd);
1374
1488
  const fullPath = resolve(safeCwd, filename || "");
1375
1489
  const rel = relative(safeCwd, fullPath);
@@ -1413,13 +1527,38 @@ async function serveFile(filename, cwd) {
1413
1527
  error: `Not a file: ${filename}`
1414
1528
  };
1415
1529
  }
1530
+ const mimeType = mimeTypes.lookup(fullPath) || "application/octet-stream";
1531
+ const size = fileStat.size;
1532
+ const range = size > 0 ? parseRange(options.range, size) : null;
1533
+ if (range === "unsatisfiable") {
1534
+ return {
1535
+ status: 416,
1536
+ body: "",
1537
+ mimeType,
1538
+ headers: { "Accept-Ranges": "bytes", "Content-Range": `bytes */${size}` },
1539
+ error: `Range not satisfiable: ${options.range} (size ${size})`
1540
+ };
1541
+ }
1542
+ if (range) {
1543
+ return {
1544
+ status: 206,
1545
+ stream: createReadStream(fullPath, { start: range.start, end: range.end }),
1546
+ mimeType,
1547
+ headers: {
1548
+ "Accept-Ranges": "bytes",
1549
+ "Content-Length": String(range.end - range.start + 1),
1550
+ "Content-Range": `bytes ${range.start}-${range.end}/${size}`
1551
+ }
1552
+ };
1553
+ }
1416
1554
  try {
1417
1555
  const content = await readFile2(fullPath);
1418
- const mimeType = mimeTypes.lookup(fullPath) || "application/octet-stream";
1419
1556
  return {
1420
1557
  status: 200,
1421
1558
  body: new Uint8Array(content),
1422
- mimeType
1559
+ mimeType,
1560
+ // 声明支持 Range:媒体元素据此决定能否拖动进度条
1561
+ headers: { "Accept-Ranges": "bytes", "Content-Length": String(size) }
1423
1562
  };
1424
1563
  } catch (err) {
1425
1564
  return {
@@ -1451,8 +1590,23 @@ function normalizeRemainingRuns(value) {
1451
1590
  if (!Number.isFinite(n) || n < 0) return null;
1452
1591
  return n;
1453
1592
  }
1593
+ function normalizeCron(raw) {
1594
+ const type = raw?.type === "once" || raw?.type === "manual" ? raw.type : "cron";
1595
+ return {
1596
+ type,
1597
+ expr: type === "cron" ? raw?.expr ?? "" : void 0,
1598
+ at: type === "once" && typeof raw?.at === "number" ? raw.at : void 0
1599
+ };
1600
+ }
1454
1601
  function normalizeSchedule(raw) {
1455
- return { ...raw, remainingRuns: normalizeRemainingRuns(raw.remainingRuns) };
1602
+ const cron = normalizeCron(raw.cron);
1603
+ const remainingRuns = normalizeRemainingRuns(raw.remainingRuns);
1604
+ return {
1605
+ ...raw,
1606
+ cron,
1607
+ // 单次计划用 remainingRuns 作为「是否已执行」标记;缺失(历史数据)按未执行处理
1608
+ remainingRuns: cron.type === "once" ? remainingRuns ?? 1 : remainingRuns
1609
+ };
1456
1610
  }
1457
1611
  var store = {
1458
1612
  scheduleDir(scheduleId) {
@@ -1506,11 +1660,16 @@ var store = {
1506
1660
  agentId: params.agentId,
1507
1661
  modelId: params.modelId,
1508
1662
  prompt: params.prompt,
1509
- cron: { type: params.cron.type, expr: params.cron.expr ?? "" },
1663
+ cron: {
1664
+ type: params.cron.type,
1665
+ expr: params.cron.type === "cron" ? params.cron.expr ?? "" : void 0,
1666
+ at: params.cron.type === "once" ? params.cron.at : void 0
1667
+ },
1510
1668
  createdAt: now,
1511
1669
  updatedAt: now,
1512
1670
  lastRunAt: null,
1513
- remainingRuns: normalizeRemainingRuns(params.remainingRuns),
1671
+ // 单次计划用 remainingRuns 作为「是否已执行」标记:创建时固定为 1(待执行)
1672
+ remainingRuns: params.cron.type === "once" ? 1 : normalizeRemainingRuns(params.remainingRuns),
1514
1673
  features: (params.features ?? []).filter((f) => f !== "ask_user" && f !== "share_to_user"),
1515
1674
  mcpServers: params.mcpServers ?? []
1516
1675
  };
@@ -1632,7 +1791,7 @@ function parseFileRoute(url) {
1632
1791
  }
1633
1792
  return null;
1634
1793
  }
1635
- async function serveRoute(route) {
1794
+ async function serveRoute(route, options = {}) {
1636
1795
  switch (route.type) {
1637
1796
  case "project": {
1638
1797
  const project = storageOps.getProject(route.projectId);
@@ -1644,7 +1803,7 @@ async function serveRoute(route) {
1644
1803
  error: "Project Not Found"
1645
1804
  };
1646
1805
  }
1647
- return serveFile(route.relativePath, project.cwd);
1806
+ return serveFile(route.relativePath, project.cwd, options);
1648
1807
  }
1649
1808
  case "share": {
1650
1809
  const shareDir = storageOps.sessionShareDir(route.sessionId);
@@ -1656,7 +1815,7 @@ async function serveRoute(route) {
1656
1815
  error: "Session Not Found"
1657
1816
  };
1658
1817
  }
1659
- return serveFile(route.sharePath, shareDir);
1818
+ return serveFile(route.sharePath, shareDir, options);
1660
1819
  }
1661
1820
  case "automation": {
1662
1821
  const task = store.getTask(route.scheduleId, route.taskId);
@@ -1669,7 +1828,7 @@ async function serveRoute(route) {
1669
1828
  };
1670
1829
  }
1671
1830
  const taskDir = store.taskDir(route.scheduleId, route.taskId);
1672
- return serveFile(route.relativePath, taskDir);
1831
+ return serveFile(route.relativePath, taskDir, options);
1673
1832
  }
1674
1833
  }
1675
1834
  }
@@ -1840,9 +1999,17 @@ function createWebUIModule() {
1840
1999
  {
1841
2000
  const route = parseFileRoute(url);
1842
2001
  if (route) {
1843
- const result = await serveRoute(route);
1844
- res.writeHead(result.status, { "Content-Type": result.mimeType });
1845
- res.end(result.body);
2002
+ const result = await serveRoute(route, { range: req.headers.range });
2003
+ res.writeHead(result.status, {
2004
+ "Content-Type": result.mimeType,
2005
+ ...result.headers
2006
+ });
2007
+ if (result.stream) {
2008
+ result.stream.on("error", () => res.destroy());
2009
+ result.stream.pipe(res);
2010
+ } else {
2011
+ res.end(result.body ?? "");
2012
+ }
1846
2013
  return;
1847
2014
  }
1848
2015
  }
@@ -2016,22 +2183,22 @@ function prune() {
2016
2183
  else records.delete(k);
2017
2184
  }
2018
2185
  }
2019
- function recordSelfWrite(projectId, relativePath, stat5) {
2186
+ function recordSelfWrite(projectId, relativePath, stat8) {
2020
2187
  prune();
2021
2188
  const k = keyOf(projectId, relativePath);
2022
2189
  const list = records.get(k) ?? [];
2023
- list.push({ mtimeMs: stat5.mtimeMs, size: stat5.size, at: Date.now() });
2190
+ list.push({ mtimeMs: stat8.mtimeMs, size: stat8.size, at: Date.now() });
2024
2191
  records.set(k, list);
2025
2192
  }
2026
- function isSelfWrite(projectId, relativePath, stat5) {
2193
+ function isSelfWrite(projectId, relativePath, stat8) {
2027
2194
  const k = keyOf(projectId, relativePath);
2028
2195
  const list = records.get(k);
2029
- if (!list || !stat5) return false;
2196
+ if (!list || !stat8) return false;
2030
2197
  const now = Date.now();
2031
2198
  const alive = list.filter((r) => now - r.at <= TTL_MS);
2032
2199
  if (alive.length > 0) records.set(k, alive);
2033
2200
  else records.delete(k);
2034
- return alive.some((r) => r.mtimeMs === stat5.mtimeMs && r.size === stat5.size);
2201
+ return alive.some((r) => r.mtimeMs === stat8.mtimeMs && r.size === stat8.size);
2035
2202
  }
2036
2203
 
2037
2204
  // src/backend/watcher.ts
@@ -3774,6 +3941,29 @@ function getFilenameFromUri(uri) {
3774
3941
  }
3775
3942
 
3776
3943
  // src/agents/acp-client-tools.ts
3944
+ var SHELL_OUTPUT_BYTE_LIMIT = 30 * 1024;
3945
+ var SHELL_OUTPUT_TRUNCATED_NOTICE = "[Shell output: earlier output exceeded the output limit and was dropped, only the tail is shown. Narrow the command (e.g. pipe through head/tail/grep) to see the part you need.]\n";
3946
+ function keepTailWithinByteLimit(text, byteLimit) {
3947
+ const encoded = new TextEncoder().encode(text);
3948
+ if (encoded.length <= byteLimit) return { text, trimmed: false };
3949
+ let start = encoded.length - byteLimit;
3950
+ while (start < encoded.length && (encoded[start] & 192) === 128) start += 1;
3951
+ return {
3952
+ text: new TextDecoder().decode(encoded.subarray(start)),
3953
+ trimmed: true
3954
+ };
3955
+ }
3956
+ function formatShellResultForModel(params) {
3957
+ const notes = [];
3958
+ if (params.timedOut) notes.push("timed out and was killed");
3959
+ const exitCode = params.exitStatus?.exitCode ?? null;
3960
+ if (exitCode != null && exitCode !== 0) notes.push(`exited with code ${exitCode}`);
3961
+ const signal = params.exitStatus?.signal ?? null;
3962
+ if (signal) notes.push(`killed by signal ${signal}`);
3963
+ if (notes.length === 0) return params.output;
3964
+ return `${params.output}
3965
+ [command ${notes.join("; ")}]`;
3966
+ }
3777
3967
  function createACPClientTools(params) {
3778
3968
  const terminals = /* @__PURE__ */ new Map();
3779
3969
  const tools = {
@@ -4100,13 +4290,16 @@ Prefer dedicated tools first:
4100
4290
  - Grep: search content (equivalent: rg "pattern" src or grep -R "pattern" src)
4101
4291
  - Glob/find-style discovery: find files (equivalent: find . -name "*.ts")
4102
4292
  Use Shell as fallback when those tools cannot complete the task.
4103
- Avoid destructive commands and prefer deterministic, non-interactive commands.`,
4293
+ Avoid destructive commands and prefer deterministic, non-interactive commands.
4294
+ Output returned to you is capped at 30720 bytes: beyond that only the tail is kept and a notice is prepended (the terminal buffer uses the same rule). The user still sees the full output in the terminal panel. When output may be large, narrow it in the command itself (pipe through head/tail/grep, or add flags like --no-pager).`,
4104
4295
  inputSchema: z4.object({
4105
4296
  command: z4.string().describe("Executable command."),
4106
4297
  args: z4.array(z4.string()).optional().describe("Command arguments."),
4107
4298
  cwd: z4.string().optional().describe("Absolute working directory."),
4108
4299
  env: z4.record(z4.string(), z4.string()).optional().describe("Environment variables map."),
4109
- outputByteLimit: z4.number().int().positive().optional().describe("Output retention byte limit."),
4300
+ outputByteLimit: z4.number().int().positive().optional().describe(
4301
+ "Terminal buffer retention limit in bytes. Defaults to 30720, which is also the cap on the output returned to you."
4302
+ ),
4110
4303
  timeoutSeconds: z4.number().positive().optional().describe("Timeout before killing command.")
4111
4304
  }),
4112
4305
  execute: async ({ command, args: args2, cwd, env, outputByteLimit, timeoutSeconds }, { toolCallId }) => {
@@ -4147,7 +4340,8 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
4147
4340
  args: args2,
4148
4341
  cwd: cwd ?? null,
4149
4342
  env: toEnvVariables(env),
4150
- outputByteLimit: outputByteLimit ?? null
4343
+ // 未指定时用统一预算,避免 ACP 侧落回 agent-bridge 的 1MB 默认值
4344
+ outputByteLimit: outputByteLimit ?? SHELL_OUTPUT_BYTE_LIMIT
4151
4345
  });
4152
4346
  terminals.set(terminal.id, terminal);
4153
4347
  const toolCallStartUpdate = {
@@ -4184,11 +4378,12 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
4184
4378
  terminals.delete(terminal.id);
4185
4379
  }
4186
4380
  const finalOutput = output ?? { output: "", truncated: false, exitStatus: null };
4381
+ const exitStatus = finalOutput.exitStatus ?? null;
4187
4382
  const sessionOutput = {
4188
4383
  terminalId: terminal.id,
4189
4384
  output: finalOutput.output,
4190
4385
  truncated: finalOutput.truncated,
4191
- exitStatus: finalOutput.exitStatus ?? null,
4386
+ exitStatus,
4192
4387
  timedOut
4193
4388
  };
4194
4389
  await connection.sessionUpdate({
@@ -4201,7 +4396,13 @@ Avoid destructive commands and prefer deterministic, non-interactive commands.`,
4201
4396
  content: toolCallStartUpdate.content
4202
4397
  }
4203
4398
  });
4204
- return sessionOutput;
4399
+ const keptOutput = keepTailWithinByteLimit(finalOutput.output, SHELL_OUTPUT_BYTE_LIMIT);
4400
+ const headDropped = finalOutput.truncated || keptOutput.trimmed;
4401
+ return formatShellResultForModel({
4402
+ output: headDropped ? SHELL_OUTPUT_TRUNCATED_NOTICE + keptOutput.text : keptOutput.text,
4403
+ exitStatus,
4404
+ timedOut
4405
+ });
4205
4406
  } catch (error) {
4206
4407
  const errorText = error instanceof Error ? error.message : String(error);
4207
4408
  await connection.sessionUpdate({
@@ -4821,67 +5022,87 @@ import { z as z5 } from "zod";
4821
5022
  function createImageAnalysisTool(params) {
4822
5023
  return {
4823
5024
  ImageAnalysis: tool2({
4824
- description: `Analyze an image file by reading it and using vision capabilities to understand its content.
5025
+ description: `Analyze one or more image files by reading them and using vision capabilities to understand their content.
4825
5026
 
4826
- Use this when you need to visually understand or analyze image content (e.g., describe what's in the image, extract text from a screenshot, analyze a diagram, identify UI elements, read charts).
5027
+ Use this when you need to visually understand or analyze image content (e.g., describe what's in the image, extract text from a screenshot, analyze a diagram, identify UI elements, read charts). Pass every image that belongs to the same question in a single call (up to 10 per call) so the model can compare them: each image carries a name that labels it in the request, so you can refer to that name in the query and attribute the findings in the answer.
4827
5028
 
4828
5029
  For simple metadata queries (dimensions, format), use image_metadata instead.
4829
5030
 
4830
- Provide a specific query describing what you want to know about the image \u2014 the more specific the query, the better the analysis.`,
5031
+ Provide a specific query describing what you want to know about the images \u2014 the more specific the query, the better the analysis.`,
4831
5032
  inputSchema: z5.object({
4832
- path: z5.string().describe("Absolute or project-relative path to the image file."),
5033
+ images: z5.array(
5034
+ z5.object({
5035
+ name: z5.string().describe(
5036
+ "Short label identifying this image (e.g. 'login page' or 'before'). It is sent to the model as the image's caption and also shown in the UI."
5037
+ ),
5038
+ path: z5.string().describe("Absolute or project-relative path to the image file.")
5039
+ })
5040
+ ).min(1).max(10).describe(
5041
+ "Images to analyze together, in order. Min 1, max 10 \u2014 every image is sent to the model in the same request, so split larger batches into several calls."
5042
+ ),
4833
5043
  query: z5.string().describe(
4834
- "What you want to know about the image. Be specific (e.g., 'Extract all text from this screenshot', 'Describe the UI layout', 'What colors are used in this chart?')."
5044
+ "What you want to know about the images. Be specific (e.g., 'Extract all text from this screenshot', 'Describe the UI layout', 'Which of these charts has the highest value?')."
4835
5045
  )
4836
5046
  }),
4837
- execute: async ({ path: imgPath, query }, { toolCallId }) => {
5047
+ execute: async ({ images, query }, { toolCallId }) => {
4838
5048
  const connection = params.getConnection();
5049
+ const imageLabel = images.map((image) => image.name).join(", ");
4839
5050
  if (connection) {
4840
5051
  await connection.sessionUpdate({
4841
5052
  sessionId: params.sessionId,
4842
5053
  update: {
4843
5054
  sessionUpdate: "tool_call",
4844
5055
  toolCallId,
4845
- title: `image_analysis ${imgPath}`,
5056
+ title: `image_analysis ${imageLabel}`,
4846
5057
  kind: "read",
4847
5058
  status: "in_progress",
4848
- rawInput: { path: imgPath, query }
5059
+ rawInput: { images, query }
4849
5060
  }
4850
5061
  });
4851
5062
  }
4852
5063
  try {
4853
- const absPath = resolve7(params.cwd, imgPath);
4854
- const buffer = await readFile6(absPath);
4855
- const base64 = buffer.toString("base64");
4856
- const mimeType = mime.lookup(absPath) || "image/png";
5064
+ const content = [];
5065
+ const failures = [];
5066
+ for (const image of images) {
5067
+ try {
5068
+ const absPath = resolve7(params.cwd, image.path);
5069
+ const buffer = await readFile6(absPath);
5070
+ const mimeType = mime.lookup(absPath) || "image/png";
5071
+ content.push({ type: "text", text: `Image "${image.name}":` });
5072
+ content.push({
5073
+ type: "file",
5074
+ data: buffer.toString("base64"),
5075
+ mediaType: mimeType
5076
+ });
5077
+ } catch (error) {
5078
+ const reason = error instanceof Error ? error.message : String(error);
5079
+ failures.push(`${image.name} (${image.path}): ${reason}`);
5080
+ }
5081
+ }
5082
+ if (content.length === 0) {
5083
+ throw new Error(`Failed to read any image \u2014 ${failures.join("; ")}`);
5084
+ }
5085
+ content.push({ type: "text", text: query });
4857
5086
  const result = await generateText({
4858
5087
  model: params.getModel(),
4859
5088
  messages: [
4860
5089
  {
4861
5090
  role: "user",
4862
- content: [
4863
- {
4864
- type: "image",
4865
- image: base64,
4866
- mediaType: mimeType
4867
- },
4868
- {
4869
- type: "text",
4870
- text: query
4871
- }
4872
- ]
5091
+ content
4873
5092
  }
4874
5093
  ],
4875
5094
  abortSignal: params.parentSignal,
4876
5095
  providerOptions: {
4877
5096
  openaiCompatible: {
4878
5097
  thinking: { type: "disabled" },
4879
- enable_thinking: false,
4880
- reasoningEffort: "high"
5098
+ enable_thinking: false
4881
5099
  }
4882
5100
  }
4883
5101
  });
4884
5102
  const analysisText = result.text || "(no analysis returned)";
5103
+ const outputText = failures.length > 0 ? `${analysisText}
5104
+
5105
+ [failed to read: ${failures.join("; ")}]` : analysisText;
4885
5106
  if (connection) {
4886
5107
  await connection.sessionUpdate({
4887
5108
  sessionId: params.sessionId,
@@ -4892,13 +5113,13 @@ Provide a specific query describing what you want to know about the image \u2014
4892
5113
  content: [
4893
5114
  {
4894
5115
  type: "content",
4895
- content: { type: "text", text: analysisText }
5116
+ content: { type: "text", text: outputText }
4896
5117
  }
4897
5118
  ]
4898
5119
  }
4899
5120
  });
4900
5121
  }
4901
- return analysisText;
5122
+ return outputText;
4902
5123
  } catch (error) {
4903
5124
  const errorText = error instanceof Error ? error.message : String(error);
4904
5125
  if (connection) {
@@ -4911,13 +5132,16 @@ Provide a specific query describing what you want to know about the image \u2014
4911
5132
  content: [
4912
5133
  {
4913
5134
  type: "content",
4914
- content: { type: "text", text: `Error analyzing image: ${errorText}` }
5135
+ content: {
5136
+ type: "text",
5137
+ text: `Error analyzing images: ${errorText}`
5138
+ }
4915
5139
  }
4916
5140
  ]
4917
5141
  }
4918
5142
  });
4919
5143
  }
4920
- return `Error analyzing image: ${errorText}`;
5144
+ return `Error analyzing images: ${errorText}`;
4921
5145
  }
4922
5146
  }
4923
5147
  })
@@ -5468,6 +5692,17 @@ var COMPACT_PROMPT = `Compress the conversation into a structured markdown summa
5468
5692
  Use headings and bullet points for clarity.
5469
5693
  Detect the user's commonly used language from the conversation history and respond in that language.
5470
5694
  Output only the summary.`;
5695
+ function buildCompactSummaryMessage(summary) {
5696
+ return {
5697
+ role: "user",
5698
+ content: `<previous_conversation_summary>
5699
+ This session continues from a previous conversation that ran out of context.
5700
+ The summary below covers the earlier portion of the conversation.
5701
+
5702
+ ${summary}
5703
+ </previous_conversation_summary>`
5704
+ };
5705
+ }
5471
5706
  var OpenaiCompatibleAgent = class {
5472
5707
  sessions = /* @__PURE__ */ new Map();
5473
5708
  provider;
@@ -5498,6 +5733,9 @@ var OpenaiCompatibleAgent = class {
5498
5733
  name: "openai-compatible",
5499
5734
  baseURL: this.baseUrl,
5500
5735
  apiKey: this.apiKey,
5736
+ // 流式请求需带 stream_options.include_usage,否则 vLLM 等服务端
5737
+ // 不会在流中返回 usage,导致整轮 token 用量为 0。
5738
+ includeUsage: true,
5501
5739
  headers: {
5502
5740
  "User-Agent": AgentDescription,
5503
5741
  ...this.headers
@@ -5863,12 +6101,7 @@ var OpenaiCompatibleAgent = class {
5863
6101
  maxOutputTokens: 5e3
5864
6102
  });
5865
6103
  const summary = summaryResult.text;
5866
- session.history = [
5867
- {
5868
- role: "system",
5869
- content: `Summary of previous conversation: ${summary}`
5870
- }
5871
- ];
6104
+ session.history = [buildCompactSummaryMessage(summary)];
5872
6105
  await savePersistedSessionHistory({
5873
6106
  agentId: this.agentId,
5874
6107
  sessionId: session.id,
@@ -6016,7 +6249,7 @@ var OpenaiCompatibleAgent = class {
6016
6249
  system: systemPrompt,
6017
6250
  messages: [...session.history, userMessage],
6018
6251
  tools: allTools,
6019
- stopWhen: isStepCount2(128),
6252
+ stopWhen: isStepCount2(256),
6020
6253
  abortSignal: abortController.signal,
6021
6254
  providerOptions: {
6022
6255
  openaiCompatible: {
@@ -6246,6 +6479,7 @@ var AgentTerminalManager = class {
6246
6479
  process: proc,
6247
6480
  outputBuffer: Buffer.alloc(0),
6248
6481
  outputByteLimit,
6482
+ truncated: false,
6249
6483
  exitCode: null,
6250
6484
  signal: null,
6251
6485
  isFinished: false,
@@ -6258,7 +6492,12 @@ var AgentTerminalManager = class {
6258
6492
  const handleData = (chunk) => {
6259
6493
  let newBuffer = Buffer.concat([terminal.outputBuffer, chunk]);
6260
6494
  if (newBuffer.length > terminal.outputByteLimit) {
6261
- newBuffer = newBuffer.subarray(newBuffer.length - terminal.outputByteLimit);
6495
+ let start = newBuffer.length - terminal.outputByteLimit;
6496
+ while (start < newBuffer.length && (newBuffer.readUInt8(start) & 192) === 128) {
6497
+ start += 1;
6498
+ }
6499
+ newBuffer = newBuffer.subarray(start);
6500
+ terminal.truncated = true;
6262
6501
  }
6263
6502
  terminal.outputBuffer = newBuffer;
6264
6503
  const text = terminal.outputDecoder.decode(chunk, { stream: true });
@@ -6287,8 +6526,7 @@ var AgentTerminalManager = class {
6287
6526
  }
6288
6527
  return {
6289
6528
  output: terminal.outputDecoder.decode(terminal.outputBuffer),
6290
- truncated: false
6291
- // Simplification
6529
+ truncated: terminal.truncated
6292
6530
  };
6293
6531
  }
6294
6532
  getTerminal(terminalId) {
@@ -7710,6 +7948,9 @@ function createTerminalModule(ctx, deps) {
7710
7948
  };
7711
7949
  }
7712
7950
 
7951
+ // src/backend/project/index.ts
7952
+ import { stat as stat5 } from "fs/promises";
7953
+
7713
7954
  // src/backend/project/filesystem.ts
7714
7955
  import Fuse from "fuse.js";
7715
7956
  import {
@@ -8203,7 +8444,12 @@ function createProjectModule(ctx, deps) {
8203
8444
  return storage.listProjects();
8204
8445
  }
8205
8446
  async function addProject2(cwd) {
8206
- const info = storage.addProject(cwd);
8447
+ const trimmed = cwd.trim();
8448
+ const s = await stat5(trimmed).catch(() => null);
8449
+ if (!s || !s.isDirectory()) {
8450
+ throw new Error(`Not a directory: ${trimmed}`);
8451
+ }
8452
+ const info = storage.addProject(trimmed);
8207
8453
  fs2.initProjectFsVersion(info.id);
8208
8454
  await deps.watcher.syncWatchers();
8209
8455
  sendEvent("projects-changed", void 0);
@@ -10067,6 +10313,32 @@ function createIlinkModule(ctx) {
10067
10313
  if (!handlers) throw new Error("[ilink] handlers not yet initialized");
10068
10314
  return handlers;
10069
10315
  }
10316
+ function parseMenuIndex(input) {
10317
+ const match = input.match(/^\d+/);
10318
+ return match ? parseInt(match[0], 10) : null;
10319
+ }
10320
+ async function replyInvalidIndex(msg, max) {
10321
+ if (!msg.from_user_id) return;
10322
+ await bridge?.sendTextReply(
10323
+ msg.from_user_id,
10324
+ t("ilink.invalidMenuNumber", { min: "1", max: String(max) })
10325
+ );
10326
+ }
10327
+ async function replyUnknownCommand(msg) {
10328
+ if (!msg.from_user_id) return;
10329
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.unknownCommand"));
10330
+ }
10331
+ const FEATURE_LABEL_KEYS = {
10332
+ skills: "ilink.featureSkills",
10333
+ search: "ilink.featureSearch",
10334
+ image_generation: "ilink.featureImageGeneration",
10335
+ memory: "ilink.featureMemory",
10336
+ ask_user: "ilink.featureAskUser",
10337
+ share_to_user: "ilink.featureShareToUser"
10338
+ };
10339
+ function getFeatureLabel(feature) {
10340
+ return t(FEATURE_LABEL_KEYS[feature]);
10341
+ }
10070
10342
  const state = {
10071
10343
  getBridge: () => bridge,
10072
10344
  getActiveSessionId: () => activeSessionId,
@@ -10099,88 +10371,99 @@ function createIlinkModule(ctx) {
10099
10371
  const combinedText = [text, voiceText].filter(Boolean).join("\n");
10100
10372
  if (!combinedText.trim() && !hasImages) return;
10101
10373
  const trimmed = text.trim();
10102
- if (commandPending) {
10103
- commandPending(trimmed);
10104
- commandPending = null;
10105
- return;
10106
- }
10107
10374
  if (trimmed[0] === "!" || trimmed[0] === "\uFF01") {
10375
+ commandPending = null;
10108
10376
  await handleIlinkCommand(trimmed, msg);
10109
10377
  return;
10110
10378
  }
10111
- const sessionId = activeSessionId ?? "";
10112
- if (!sessionId) {
10113
- console.warn("[iLink] No active session, ignoring message");
10114
- if (msg.from_user_id) {
10115
- const lines = [
10116
- `\u{1F4CB} **${t("ilink.noActiveSession")}**`,
10117
- "",
10118
- t("ilink.switchSessionGuide"),
10119
- t("ilink.createSessionGuide")
10120
- ];
10121
- await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10122
- }
10123
- return;
10124
- }
10125
- const contents = [];
10126
- if (combinedText.trim()) contents.push({ type: "text", text: combinedText });
10127
- if (hasImages && bridge) {
10128
- const { useOriginalImage } = storage.getSettings().ilink;
10129
- for (const item of msg.item_list ?? []) {
10130
- if (item.type !== 2 || !item.image_item) continue;
10131
- try {
10132
- const base64 = await bridge.downloadImage(item.image_item, { useOriginalImage });
10133
- if (base64) contents.push({ type: "image", data: base64, mimeType: "image/jpeg" });
10134
- } catch (err) {
10135
- console.error("[iLink] Failed to download image:", err);
10136
- }
10137
- }
10138
- }
10139
- if (contents.length === 0) return;
10140
- const h = getHandlers();
10141
- const pending = await h.getPendingAskUserRequests({ sessionId });
10142
- if (pending.length > 0) {
10143
- const req = pending[0];
10144
- const options = req.options;
10145
- let respondedValue = null;
10146
- if (/^\d+$/.test(trimmed)) {
10147
- const index = parseInt(trimmed, 10) - 1;
10148
- const option = options[index];
10149
- if (option) respondedValue = option.value;
10150
- }
10151
- if (respondedValue !== null) {
10152
- await h.respondAskUser({
10153
- sessionId,
10154
- askUserId: req.askUserId,
10155
- value: respondedValue
10156
- });
10157
- } else {
10158
- await h.respondAskUser({
10159
- sessionId,
10160
- askUserId: req.askUserId,
10161
- value: null,
10162
- reason: trimmed || t("ilink.noInput")
10163
- });
10164
- }
10165
- if (bridge?.isConnected && activeSessionId === sessionId) {
10166
- const userId = bridge.userId;
10167
- if (userId) bridge.sendTyping(userId, true).catch(() => {
10168
- });
10379
+ if (commandPending) {
10380
+ const index = parseMenuIndex(trimmed);
10381
+ if (index !== null) {
10382
+ const pending = commandPending;
10383
+ commandPending = null;
10384
+ await pending(index);
10385
+ return;
10169
10386
  }
10170
- return;
10171
- }
10172
- try {
10173
- await h.sendPrompt({ sessionId, contents });
10174
- } catch (err) {
10175
- console.error("[iLink] Failed to route message to session:", err);
10176
- if (msg.from_user_id)
10177
- await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10387
+ commandPending = null;
10178
10388
  }
10389
+ await handleUserMessage(msg, { trimmed, combinedText, hasImages });
10179
10390
  }
10180
10391
  });
10181
10392
  }
10182
10393
  return bridge;
10183
10394
  }
10395
+ async function handleUserMessage(msg, input) {
10396
+ const { trimmed, combinedText, hasImages } = input;
10397
+ const sessionId = activeSessionId ?? "";
10398
+ if (!sessionId) {
10399
+ console.warn("[iLink] No active session, ignoring message");
10400
+ if (msg.from_user_id) {
10401
+ const lines = [
10402
+ `\u{1F4CB} **${t("ilink.noActiveSession")}**`,
10403
+ "",
10404
+ t("ilink.switchSessionGuide"),
10405
+ "",
10406
+ t("ilink.createSessionGuide")
10407
+ ];
10408
+ await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10409
+ }
10410
+ return;
10411
+ }
10412
+ const contents = [];
10413
+ if (combinedText.trim()) contents.push({ type: "text", text: combinedText });
10414
+ if (hasImages && bridge) {
10415
+ const { useOriginalImage } = storage.getSettings().ilink;
10416
+ for (const item of msg.item_list ?? []) {
10417
+ if (item.type !== 2 || !item.image_item) continue;
10418
+ try {
10419
+ const base64 = await bridge.downloadImage(item.image_item, { useOriginalImage });
10420
+ if (base64) contents.push({ type: "image", data: base64, mimeType: "image/jpeg" });
10421
+ } catch (err) {
10422
+ console.error("[iLink] Failed to download image:", err);
10423
+ }
10424
+ }
10425
+ }
10426
+ if (contents.length === 0) return;
10427
+ const h = getHandlers();
10428
+ const pending = await h.getPendingAskUserRequests({ sessionId });
10429
+ if (pending.length > 0) {
10430
+ const req = pending[0];
10431
+ const options = req.options;
10432
+ let respondedValue = null;
10433
+ if (/^\d+$/.test(trimmed)) {
10434
+ const index = parseInt(trimmed, 10) - 1;
10435
+ const option = options[index];
10436
+ if (option) respondedValue = option.value;
10437
+ }
10438
+ if (respondedValue !== null) {
10439
+ await h.respondAskUser({
10440
+ sessionId,
10441
+ askUserId: req.askUserId,
10442
+ value: respondedValue
10443
+ });
10444
+ } else {
10445
+ await h.respondAskUser({
10446
+ sessionId,
10447
+ askUserId: req.askUserId,
10448
+ value: null,
10449
+ reason: trimmed || t("ilink.noInput")
10450
+ });
10451
+ }
10452
+ if (bridge?.isConnected && activeSessionId === sessionId) {
10453
+ const userId = bridge.userId;
10454
+ if (userId) bridge.sendTyping(userId, true).catch(() => {
10455
+ });
10456
+ }
10457
+ return;
10458
+ }
10459
+ try {
10460
+ await h.sendPrompt({ sessionId, contents });
10461
+ } catch (err) {
10462
+ console.error("[iLink] Failed to route message to session:", err);
10463
+ if (msg.from_user_id)
10464
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10465
+ }
10466
+ }
10184
10467
  async function handleIlinkCommand(trimmed, msg) {
10185
10468
  const session = activeSessionId ? storage.getSession(activeSessionId) : null;
10186
10469
  if (session && session.isStreaming) {
@@ -10189,11 +10472,16 @@ function createIlinkModule(ctx) {
10189
10472
  });
10190
10473
  }
10191
10474
  const [command] = trimmed.slice(1).split(/\s+/);
10192
- if (command.toLowerCase() === "s") await handleCommandSwitchSession(msg);
10193
- else if (command.toLowerCase() === "n") await handleCommandNewSession(msg);
10194
- else if (command.toLowerCase() === "m") await handleCommandSwitchModel(msg);
10195
- else if (command.toLowerCase() === "q") await handleCommandSnippet(msg);
10196
- else await handleCommandInfo(msg);
10475
+ const name = command.toLowerCase();
10476
+ if (name === "") await handleCommandInfo(msg);
10477
+ else if (name === "s") await handleCommandSwitchSession(msg);
10478
+ else if (name === "n") await handleCommandNewSession(msg);
10479
+ else if (name === "m") await handleCommandSwitchModel(msg);
10480
+ else if (name === "p") await handleCommandPermission(msg);
10481
+ else if (name === "f") await handleCommandFeatures(msg);
10482
+ else if (name === "c") await handleCommandMcpServers(msg);
10483
+ else if (name === "q") await handleCommandSnippet(msg);
10484
+ else await replyUnknownCommand(msg);
10197
10485
  }
10198
10486
  async function handleCommandSwitchSession(msg) {
10199
10487
  const allSessions = storage.listSessions();
@@ -10205,7 +10493,7 @@ function createIlinkModule(ctx) {
10205
10493
  const projectMap = new Map(projects.map((p) => [p.id, p]));
10206
10494
  const lines = [];
10207
10495
  lines.push(`\u{1F4CB} **${t("ilink.sessionList")}**`);
10208
- lines.push(t("ilink.sessionListDesc"));
10496
+ lines.push("", t("ilink.sessionListDesc"));
10209
10497
  let index = 1;
10210
10498
  let isFirstGroup = true;
10211
10499
  const sessionEntries = [];
@@ -10222,39 +10510,37 @@ function createIlinkModule(ctx) {
10222
10510
  ---`);
10223
10511
  isFirstGroup = false;
10224
10512
  lines.push(`
10225
- **${projectName}**`);
10513
+ **${projectName}**`, "");
10226
10514
  for (const s of sessions) {
10227
10515
  const marker = s.id === activeSessionId ? " \u{1F448}" : "";
10228
10516
  const label = s.title || t("ilink.newSession");
10229
- lines.push(` ${index}. ${label}${marker}`);
10517
+ const agentId = s.agentId;
10518
+ lines.push(` ${index}. [${agentId}] ${label}${marker}`);
10230
10519
  sessionEntries.push({ sessionId: s.id, label });
10231
10520
  index++;
10232
10521
  }
10233
10522
  }
10234
10523
  lines.push("", "---", t("ilink.switchSessionHint"));
10235
10524
  if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10236
- commandPending = (input) => {
10237
- const num = parseInt(input, 10);
10238
- if (isNaN(num) || num < 1 || num > sessionEntries.length) {
10239
- if (msg.from_user_id)
10240
- bridge?.sendTextReply(
10241
- msg.from_user_id,
10242
- t("ilink.invalidSessionNumber", { min: "1", max: String(sessionEntries.length) })
10243
- );
10525
+ const pending = async (index2) => {
10526
+ const entry = sessionEntries[index2 - 1];
10527
+ if (!entry) {
10528
+ await replyInvalidIndex(msg, sessionEntries.length);
10529
+ commandPending = pending;
10244
10530
  return;
10245
10531
  }
10246
- const entry = sessionEntries[num - 1];
10247
10532
  activeSessionId = entry.sessionId;
10248
10533
  replyBuffer = "";
10249
10534
  writeActiveSessionId(entry.sessionId).catch(() => {
10250
10535
  });
10251
10536
  sendEvent("ilink-active-session-changed", { sessionId: entry.sessionId });
10252
10537
  if (msg.from_user_id)
10253
- bridge?.sendTextReply(
10538
+ await bridge?.sendTextReply(
10254
10539
  msg.from_user_id,
10255
10540
  t("ilink.switchedToSession", { label: entry.label })
10256
10541
  );
10257
10542
  };
10543
+ commandPending = pending;
10258
10544
  }
10259
10545
  async function handleCommandNewSession(msg) {
10260
10546
  const allProjects = storage.listProjects();
@@ -10265,7 +10551,7 @@ function createIlinkModule(ctx) {
10265
10551
  const sortedProjects = [...allProjects].sort((a, b) => a.title.localeCompare(b.title));
10266
10552
  const lines = [];
10267
10553
  lines.push(`\u{1F4CB} **${t("ilink.newSessionTitle")}**`);
10268
- lines.push(t("ilink.newSessionDesc"));
10554
+ lines.push("", t("ilink.newSessionDesc"));
10269
10555
  const projectEntries = [];
10270
10556
  sortedProjects.forEach((p, i) => {
10271
10557
  lines.push(` ${i + 1}. ${p.title}`);
@@ -10273,17 +10559,13 @@ function createIlinkModule(ctx) {
10273
10559
  });
10274
10560
  lines.push("", "---", t("ilink.createSessionHint"));
10275
10561
  if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10276
- commandPending = (input) => {
10277
- const num = parseInt(input, 10);
10278
- if (isNaN(num) || num < 1 || num > projectEntries.length) {
10279
- if (msg.from_user_id)
10280
- bridge?.sendTextReply(
10281
- msg.from_user_id,
10282
- t("ilink.invalidSessionNumber", { min: "1", max: String(projectEntries.length) })
10283
- );
10562
+ const pending = (index) => {
10563
+ const entry = projectEntries[index - 1];
10564
+ if (!entry) {
10565
+ void replyInvalidIndex(msg, projectEntries.length);
10566
+ commandPending = pending;
10284
10567
  return;
10285
10568
  }
10286
- const entry = projectEntries[num - 1];
10287
10569
  const settings = storage.getSettings();
10288
10570
  const agent2 = settings.agents.find((a) => !a.disabled);
10289
10571
  if (!agent2) {
@@ -10313,31 +10595,34 @@ function createIlinkModule(ctx) {
10313
10595
  if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10314
10596
  });
10315
10597
  };
10598
+ commandPending = pending;
10316
10599
  }
10317
10600
  async function handleCommandSwitchModel(msg) {
10318
10601
  const sessionId = activeSessionId ?? "";
10319
10602
  if (!sessionId) {
10320
- if (msg.from_user_id) {
10321
- await bridge?.sendTextReply(
10322
- msg.from_user_id,
10323
- [
10324
- `\u{1F4CB} **${t("ilink.noActiveSession")}**`,
10325
- "",
10326
- t("ilink.switchSessionGuide"),
10327
- t("ilink.createSessionGuide")
10328
- ].join("\n")
10329
- );
10603
+ await replyNoActiveSession(msg);
10604
+ return;
10605
+ }
10606
+ let modelState;
10607
+ try {
10608
+ modelState = await getHandlers().getModels({ sessionId });
10609
+ if (!modelState) {
10610
+ await getHandlers().loadSession({ sessionId });
10611
+ modelState = await getHandlers().getModels({ sessionId });
10330
10612
  }
10613
+ } catch (err) {
10614
+ console.error("[iLink] Failed to prepare model list:", err);
10615
+ if (msg.from_user_id)
10616
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10331
10617
  return;
10332
10618
  }
10333
- const modelState = await getHandlers().getModels({ sessionId });
10334
10619
  if (!modelState || !modelState.availableModels || modelState.availableModels.length === 0) {
10335
10620
  if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noModels"));
10336
10621
  return;
10337
10622
  }
10338
10623
  const lines = [];
10339
10624
  lines.push(`\u{1F9E0} **${t("ilink.modelList")}**`);
10340
- lines.push(t("ilink.modelListDesc"));
10625
+ lines.push("", t("ilink.modelListDesc"));
10341
10626
  const modelEntries = [];
10342
10627
  modelState.availableModels.forEach((m, i) => {
10343
10628
  const marker = m.modelId === modelState.currentModelId ? " \u{1F448}" : "";
@@ -10347,28 +10632,225 @@ function createIlinkModule(ctx) {
10347
10632
  });
10348
10633
  lines.push("", "---", t("ilink.switchModelHint"));
10349
10634
  if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10350
- commandPending = (input) => {
10351
- const num = parseInt(input, 10);
10352
- if (isNaN(num) || num < 1 || num > modelEntries.length) {
10635
+ const pending = (index) => {
10636
+ const entry = modelEntries[index - 1];
10637
+ if (!entry) {
10638
+ void replyInvalidIndex(msg, modelEntries.length);
10639
+ commandPending = pending;
10640
+ return;
10641
+ }
10642
+ getHandlers().setModel({ sessionId, modelId: entry.modelId }).then(() => {
10353
10643
  if (msg.from_user_id)
10354
10644
  bridge?.sendTextReply(
10355
10645
  msg.from_user_id,
10356
- t("ilink.invalidSessionNumber", { min: "1", max: String(modelEntries.length) })
10646
+ t("ilink.switchedToModel", { model: entry.label })
10357
10647
  );
10648
+ }).catch((err) => {
10649
+ console.error("[iLink] Failed to set model:", err);
10650
+ if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10651
+ });
10652
+ };
10653
+ commandPending = pending;
10654
+ }
10655
+ async function replyNoActiveSession(msg) {
10656
+ if (!msg.from_user_id) return;
10657
+ await bridge?.sendTextReply(
10658
+ msg.from_user_id,
10659
+ [
10660
+ `\u{1F4CB} **${t("ilink.noActiveSession")}**`,
10661
+ "",
10662
+ t("ilink.switchSessionGuide"),
10663
+ "",
10664
+ t("ilink.createSessionGuide")
10665
+ ].join("\n")
10666
+ );
10667
+ }
10668
+ const PERMISSION_MODES = ["ask", "allow-all"];
10669
+ function getPermissionModeLabel(mode) {
10670
+ return mode === "allow-all" ? t("ilink.permissionAllowAll") : t("ilink.permissionAsk");
10671
+ }
10672
+ async function handleCommandPermission(msg) {
10673
+ const sessionId = activeSessionId ?? "";
10674
+ const session = sessionId ? storage.getSession(sessionId) : null;
10675
+ if (!session) {
10676
+ await replyNoActiveSession(msg);
10677
+ return;
10678
+ }
10679
+ const lines = [];
10680
+ lines.push(`\u{1F4CB} **${t("ilink.permissionMenu")}**`);
10681
+ lines.push("", t("ilink.permissionMenuDesc"));
10682
+ const modeEntries = [];
10683
+ PERMISSION_MODES.forEach((mode, index) => {
10684
+ const marker = mode === session.permissionMode ? " \u{1F448}" : "";
10685
+ const label = getPermissionModeLabel(mode);
10686
+ lines.push(` ${index + 1}. ${label}${marker}`);
10687
+ modeEntries.push({ mode, label });
10688
+ });
10689
+ lines.push("", "---", t("ilink.switchPermissionHint"));
10690
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10691
+ const pending = (index) => {
10692
+ const entry = modeEntries[index - 1];
10693
+ if (!entry) {
10694
+ void replyInvalidIndex(msg, modeEntries.length);
10695
+ commandPending = pending;
10358
10696
  return;
10359
10697
  }
10360
- const entry = modelEntries[num - 1];
10361
- getHandlers().setModel({ sessionId, modelId: entry.modelId }).then(() => {
10698
+ getHandlers().updateSession({ sessionId, permissionMode: entry.mode }).then(() => {
10362
10699
  if (msg.from_user_id)
10363
10700
  bridge?.sendTextReply(
10364
10701
  msg.from_user_id,
10365
- t("ilink.switchedToModel", { model: entry.label })
10702
+ t("ilink.permissionSwitched", { mode: entry.label })
10366
10703
  );
10367
10704
  }).catch((err) => {
10368
- console.error("[iLink] Failed to set model:", err);
10705
+ console.error("[iLink] Failed to update permission mode:", err);
10369
10706
  if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10370
10707
  });
10371
10708
  };
10709
+ commandPending = pending;
10710
+ }
10711
+ async function handleCommandFeatures(msg) {
10712
+ const sessionId = activeSessionId ?? "";
10713
+ if (!sessionId || !storage.getSession(sessionId)) {
10714
+ await replyNoActiveSession(msg);
10715
+ return;
10716
+ }
10717
+ const getEntries = () => {
10718
+ const enabled = new Set(storage.getSession(sessionId)?.features ?? []);
10719
+ return ALL_FEATURES.map((feature) => ({
10720
+ id: feature,
10721
+ label: getFeatureLabel(feature),
10722
+ enabled: enabled.has(feature)
10723
+ }));
10724
+ };
10725
+ const buildMenuText = (entries2) => {
10726
+ const lines = [];
10727
+ lines.push(`\u{1F4CB} **${t("ilink.featuresMenu")}**`);
10728
+ lines.push("", t("ilink.featuresMenuDesc"));
10729
+ entries2.forEach((entry, index) => {
10730
+ lines.push(` ${index + 1}. ${entry.enabled ? "\u2713" : "\u2717"} ${entry.label}`);
10731
+ });
10732
+ lines.push("", "---", t("ilink.toggleHint"));
10733
+ return lines.join("\n");
10734
+ };
10735
+ const entries = getEntries();
10736
+ if (entries.length === 0) {
10737
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noToggleItems"));
10738
+ return;
10739
+ }
10740
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, buildMenuText(entries));
10741
+ const pending = async (index) => {
10742
+ if (index === 0) {
10743
+ if (msg.from_user_id)
10744
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.toggleCancelled"));
10745
+ return;
10746
+ }
10747
+ const current = getEntries();
10748
+ const target = current[index - 1];
10749
+ if (!target) {
10750
+ await replyInvalidIndex(msg, current.length);
10751
+ commandPending = pending;
10752
+ return;
10753
+ }
10754
+ const label = `${t(target.enabled ? "ilink.toggledOff" : "ilink.toggledOn")} ${target.label}`;
10755
+ const nextIds = current.filter((entry) => entry.enabled && entry.id !== target.id).map((entry) => entry.id);
10756
+ if (!target.enabled) nextIds.push(target.id);
10757
+ try {
10758
+ await getHandlers().updateSession({ sessionId, features: nextIds });
10759
+ } catch (err) {
10760
+ console.error("[iLink] Failed to update session features:", err);
10761
+ if (msg.from_user_id)
10762
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10763
+ return;
10764
+ }
10765
+ try {
10766
+ await getHandlers().loadSession({ sessionId, force: true });
10767
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, label);
10768
+ } catch (err) {
10769
+ console.error("[iLink] Failed to restart session after feature toggle:", err);
10770
+ if (msg.from_user_id)
10771
+ await bridge?.sendTextReply(
10772
+ msg.from_user_id,
10773
+ t("ilink.toggleSavedRestartFailed", { label })
10774
+ );
10775
+ }
10776
+ const fresh = getEntries();
10777
+ if (msg.from_user_id && fresh.length > 0)
10778
+ await bridge?.sendTextReply(msg.from_user_id, buildMenuText(fresh));
10779
+ commandPending = pending;
10780
+ };
10781
+ commandPending = pending;
10782
+ }
10783
+ async function handleCommandMcpServers(msg) {
10784
+ const sessionId = activeSessionId ?? "";
10785
+ if (!sessionId || !storage.getSession(sessionId)) {
10786
+ await replyNoActiveSession(msg);
10787
+ return;
10788
+ }
10789
+ const getEntries = () => {
10790
+ const enabled = new Set(storage.getSession(sessionId)?.mcpServers ?? []);
10791
+ return (storage.getSettings().mcpServers ?? []).map((server) => ({
10792
+ id: server.id,
10793
+ label: server.id,
10794
+ enabled: enabled.has(server.id)
10795
+ }));
10796
+ };
10797
+ const buildMenuText = (entries2) => {
10798
+ const lines = [];
10799
+ lines.push(`\u{1F4CB} **${t("ilink.mcpMenu")}**`);
10800
+ lines.push("", t("ilink.mcpMenuDesc"));
10801
+ entries2.forEach((entry, index) => {
10802
+ lines.push(` ${index + 1}. ${entry.enabled ? "\u2713" : "\u2717"} ${entry.label}`);
10803
+ });
10804
+ lines.push("", "---", t("ilink.toggleHint"));
10805
+ return lines.join("\n");
10806
+ };
10807
+ const entries = getEntries();
10808
+ if (entries.length === 0) {
10809
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, t("ilink.noToggleItems"));
10810
+ return;
10811
+ }
10812
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, buildMenuText(entries));
10813
+ const pending = async (index) => {
10814
+ if (index === 0) {
10815
+ if (msg.from_user_id)
10816
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.toggleCancelled"));
10817
+ return;
10818
+ }
10819
+ const current = getEntries();
10820
+ const target = current[index - 1];
10821
+ if (!target) {
10822
+ await replyInvalidIndex(msg, current.length);
10823
+ commandPending = pending;
10824
+ return;
10825
+ }
10826
+ const label = `${t(target.enabled ? "ilink.toggledOff" : "ilink.toggledOn")} ${target.label}`;
10827
+ const nextIds = current.filter((entry) => entry.enabled && entry.id !== target.id).map((entry) => entry.id);
10828
+ if (!target.enabled) nextIds.push(target.id);
10829
+ try {
10830
+ await getHandlers().updateSession({ sessionId, mcpServers: nextIds });
10831
+ } catch (err) {
10832
+ console.error("[iLink] Failed to update session MCP servers:", err);
10833
+ if (msg.from_user_id)
10834
+ await bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10835
+ return;
10836
+ }
10837
+ try {
10838
+ await getHandlers().loadSession({ sessionId, force: true });
10839
+ if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, label);
10840
+ } catch (err) {
10841
+ console.error("[iLink] Failed to restart session after MCP toggle:", err);
10842
+ if (msg.from_user_id)
10843
+ await bridge?.sendTextReply(
10844
+ msg.from_user_id,
10845
+ t("ilink.toggleSavedRestartFailed", { label })
10846
+ );
10847
+ }
10848
+ const fresh = getEntries();
10849
+ if (msg.from_user_id && fresh.length > 0)
10850
+ await bridge?.sendTextReply(msg.from_user_id, buildMenuText(fresh));
10851
+ commandPending = pending;
10852
+ };
10853
+ commandPending = pending;
10372
10854
  }
10373
10855
  async function handleCommandSnippet(msg) {
10374
10856
  const settings = storage.getSettings();
@@ -10379,7 +10861,7 @@ function createIlinkModule(ctx) {
10379
10861
  }
10380
10862
  const lines = [];
10381
10863
  lines.push(`\u{1F4DD} **${t("ilink.snippetList")}**`);
10382
- lines.push(t("ilink.snippetListDesc"));
10864
+ lines.push("", t("ilink.snippetListDesc"));
10383
10865
  const snippetEntries = [];
10384
10866
  snippets.forEach((s, i) => {
10385
10867
  const preview = s.content.length > 50 ? s.content.substring(0, 50) + "..." : s.content;
@@ -10388,17 +10870,13 @@ function createIlinkModule(ctx) {
10388
10870
  });
10389
10871
  lines.push("", "---", t("ilink.selectSnippetHint"));
10390
10872
  if (msg.from_user_id) await bridge?.sendTextReply(msg.from_user_id, lines.join("\n"));
10391
- commandPending = (input) => {
10392
- const num = parseInt(input, 10);
10393
- if (isNaN(num) || num < 1 || num > snippetEntries.length) {
10394
- if (msg.from_user_id)
10395
- bridge?.sendTextReply(
10396
- msg.from_user_id,
10397
- t("ilink.invalidSessionNumber", { min: "1", max: String(snippetEntries.length) })
10398
- );
10873
+ const pending = (index) => {
10874
+ const entry = snippetEntries[index - 1];
10875
+ if (!entry) {
10876
+ void replyInvalidIndex(msg, snippetEntries.length);
10877
+ commandPending = pending;
10399
10878
  return;
10400
10879
  }
10401
- const entry = snippetEntries[num - 1];
10402
10880
  const sessionId = activeSessionId ?? "";
10403
10881
  if (!sessionId) {
10404
10882
  if (msg.from_user_id)
@@ -10408,6 +10886,7 @@ function createIlinkModule(ctx) {
10408
10886
  `\u{1F4CB} **${t("ilink.noActiveSession")}**`,
10409
10887
  "",
10410
10888
  t("ilink.switchSessionGuide"),
10889
+ "",
10411
10890
  t("ilink.createSessionGuide")
10412
10891
  ].join("\n")
10413
10892
  );
@@ -10421,43 +10900,55 @@ function createIlinkModule(ctx) {
10421
10900
  if (msg.from_user_id) bridge?.sendTextReply(msg.from_user_id, t("ilink.errorProcessing"));
10422
10901
  });
10423
10902
  };
10903
+ commandPending = pending;
10424
10904
  }
10425
10905
  async function handleCommandInfo(msg) {
10426
10906
  const currentSession = activeSessionId ? storage.getSession(activeSessionId) : null;
10427
10907
  const message = (() => {
10428
10908
  const lines = [];
10429
- lines.push(`\u{1F4CB} **${t("ilink.sessionInfo")}**`);
10430
10909
  if (!currentSession) {
10431
10910
  lines.push(t("ilink.noActiveSession"));
10432
- lines.push("", "---", t("ilink.switchSessionGuide"), t("ilink.createSessionGuide"));
10911
+ lines.push("", "---", t("ilink.switchSessionGuide"), "", t("ilink.createSessionGuide"));
10433
10912
  return lines.join("\n");
10434
10913
  }
10435
10914
  const projects = storage.listProjects();
10436
10915
  const project = projects.find((p) => p.id === currentSession.projectId);
10437
- lines.push(`**${t("ilink.title")}**: ${currentSession.title || t("ilink.newSession")}`);
10438
- if (project) lines.push(`**${t("ilink.project")}**: ${project.title}`);
10439
- lines.push(`**${t("ilink.projectDir")}**: \`${currentSession.cwd}\``);
10440
- lines.push(`**${t("ilink.agent")}**: \`${currentSession.agentId}\``);
10916
+ lines.push(`\u{1F4CB} **${currentSession.title || t("ilink.newSession")}**
10917
+ `);
10918
+ if (project) lines.push(`**${t("ilink.project")}**: ${project.title}
10919
+ `);
10920
+ lines.push(`**${t("ilink.projectDir")}**: \`${currentSession.cwd}\`
10921
+ `);
10922
+ lines.push(`**${t("ilink.agent")}**: \`${currentSession.agentId}\`
10923
+ `);
10924
+ lines.push(
10925
+ `**${t("ilink.permissionMenu")}**: ${getPermissionModeLabel(currentSession.permissionMode)}
10926
+ `
10927
+ );
10441
10928
  const enabledFeatures = new Set(currentSession.features ?? []);
10442
10929
  lines.push(`**${t("ilink.features")}**:`);
10443
- for (const f of ALL_FEATURES) lines.push(` - ${enabledFeatures.has(f) ? "\u2713" : "\u2717"} ${f}`);
10444
- const globalSettings = storage.getSettings();
10930
+ for (const f of ALL_FEATURES)
10931
+ lines.push(` - ${enabledFeatures.has(f) ? "\u2713" : "\u2717"} ${getFeatureLabel(f)}`);
10445
10932
  const sessionMcpIds = new Set(currentSession.mcpServers ?? []);
10446
- const allMcpServers = globalSettings.mcpServers ?? [];
10933
+ const allMcpServers = storage.getSettings().mcpServers ?? [];
10934
+ lines.push("");
10447
10935
  if (allMcpServers.length > 0) {
10448
10936
  lines.push(`**${t("ilink.mcpServers")}**:`);
10449
10937
  for (const srv of allMcpServers)
10450
- lines.push(` - ${sessionMcpIds.has(srv.id) && !srv.disabled ? "\u2713" : "\u2717"} \`${srv.id}\``);
10938
+ lines.push(` - ${sessionMcpIds.has(srv.id) ? "\u2713" : "\u2717"} ${srv.id}`);
10451
10939
  } else {
10452
10940
  lines.push(`**${t("ilink.mcpServers")}**: \u2014`);
10453
10941
  }
10454
10942
  lines.push(
10455
10943
  "",
10456
10944
  "---",
10457
- t("ilink.switchSessionGuide"),
10458
- t("ilink.createSessionGuide"),
10459
- t("ilink.modelGuide"),
10460
- t("ilink.snippetGuide")
10945
+ "- " + t("ilink.switchSessionGuide"),
10946
+ "- " + t("ilink.createSessionGuide"),
10947
+ "- " + t("ilink.modelGuide"),
10948
+ "- " + t("ilink.permissionGuide"),
10949
+ "- " + t("ilink.featureGuide"),
10950
+ "- " + t("ilink.mcpServerGuide"),
10951
+ "- " + t("ilink.snippetGuide")
10461
10952
  );
10462
10953
  return lines.join("\n");
10463
10954
  })();
@@ -10678,6 +11169,11 @@ function createAutomationModule(ctx, deps) {
10678
11169
  const scheduledCrons = /* @__PURE__ */ new Map();
10679
11170
  const runningTasks = /* @__PURE__ */ new Set();
10680
11171
  function getNextRun(schedule) {
11172
+ if (schedule.cron.type === "once") {
11173
+ if (isRunLimitReached(schedule)) return null;
11174
+ const at = schedule.cron.at;
11175
+ return at && at > Date.now() ? at : null;
11176
+ }
10681
11177
  const job = scheduledCrons.get(schedule.id);
10682
11178
  if (!job) return null;
10683
11179
  try {
@@ -10703,6 +11199,34 @@ function createAutomationModule(ctx, deps) {
10703
11199
  function scheduleCron(schedule) {
10704
11200
  unscheduleCron(schedule.id);
10705
11201
  if (isRunLimitReached(schedule)) return;
11202
+ if (schedule.cron.type === "once") {
11203
+ const at = schedule.cron.at;
11204
+ if (!at || at <= Date.now()) return;
11205
+ try {
11206
+ const onceJob = new CronJob(
11207
+ new Date(at),
11208
+ async () => {
11209
+ try {
11210
+ await executeTask(schedule.id, "cron");
11211
+ } catch (err) {
11212
+ console.error(`[Automation] Failed to run once task for "${schedule.name}":`, err);
11213
+ } finally {
11214
+ unscheduleCron(schedule.id);
11215
+ }
11216
+ },
11217
+ null,
11218
+ false
11219
+ );
11220
+ scheduledCrons.set(schedule.id, onceJob);
11221
+ onceJob.start();
11222
+ console.log(
11223
+ `[Automation] Scheduled once "${schedule.name}" at ${new Date(at).toLocaleString(storage.getSettings().i18n?.language)}`
11224
+ );
11225
+ } catch (err) {
11226
+ console.error(`[Automation] Failed to schedule once "${schedule.name}":`, err);
11227
+ }
11228
+ return;
11229
+ }
10706
11230
  if (schedule.cron.type !== "cron" || !schedule.cron.expr) return;
10707
11231
  try {
10708
11232
  const cronJob = new CronJob(
@@ -10742,9 +11266,21 @@ function createAutomationModule(ctx, deps) {
10742
11266
  function restoreActiveSchedules() {
10743
11267
  const schedules = store.listSchedules();
10744
11268
  for (const s of schedules) {
10745
- if (s.cron.type === "cron" && s.cron.expr) scheduleCron(s);
10746
- }
10747
- console.log(`[Automation] Restored ${scheduledCrons.size} active schedule(s)`);
11269
+ if (s.cron.type === "cron" && s.cron.expr) {
11270
+ scheduleCron(s);
11271
+ } else if (s.cron.type === "once") {
11272
+ if (isRunLimitReached(s)) continue;
11273
+ const at = s.cron.at;
11274
+ if (at && at > Date.now()) {
11275
+ scheduleCron(s);
11276
+ } else {
11277
+ console.log(
11278
+ `[Automation] "${s.name}" once trigger missed (scheduled at ${at ? new Date(at).toLocaleString(storage.getSettings().i18n?.language) : "?"})`
11279
+ );
11280
+ }
11281
+ }
11282
+ }
11283
+ console.log(`[Automation] Restored ${scheduledCrons.size} active schedule(s)`);
10748
11284
  }
10749
11285
  function buildAutomationMcpServers(mcpIds) {
10750
11286
  const settings = storage.getSettings();
@@ -10789,6 +11325,10 @@ function createAutomationModule(ctx, deps) {
10789
11325
  const task = { id: taskId, scheduleId, startedAt, completedAt: null, status: "running" };
10790
11326
  store.saveTask(scheduleId, task);
10791
11327
  sendEvent("task-update", { scheduleId, task });
11328
+ schedule.lastRunAt = Date.now();
11329
+ schedule.updatedAt = Date.now();
11330
+ store.saveSchedule(schedule);
11331
+ sendEvent("schedules-changed", void 0);
10792
11332
  const taskDir = store.taskDir(scheduleId, taskId);
10793
11333
  try {
10794
11334
  if (source === "cron") consumeRun(schedule);
@@ -10837,7 +11377,6 @@ function createAutomationModule(ctx, deps) {
10837
11377
  2
10838
11378
  )
10839
11379
  );
10840
- schedule.lastRunAt = Date.now();
10841
11380
  schedule.updatedAt = Date.now();
10842
11381
  store.saveSchedule(schedule);
10843
11382
  sendEvent("schedules-changed", void 0);
@@ -10864,19 +11403,21 @@ function createAutomationModule(ctx, deps) {
10864
11403
  }
10865
11404
  function createSchedule(params) {
10866
11405
  const schedule = store.createSchedule(params);
10867
- if (schedule.cron.type === "cron" && schedule.cron.expr) scheduleCron(schedule);
11406
+ if (schedule.cron.type !== "manual") scheduleCron(schedule);
10868
11407
  return schedule;
10869
11408
  }
10870
11409
  function updateSchedule(scheduleId, updates) {
10871
11410
  const schedule = store.getSchedule(scheduleId);
10872
11411
  if (!schedule) throw new Error("Schedule not found");
10873
11412
  Object.assign(schedule, updates);
10874
- if ("remainingRuns" in updates) {
11413
+ if (schedule.cron.type === "once") {
11414
+ schedule.remainingRuns = 1;
11415
+ } else if ("remainingRuns" in updates) {
10875
11416
  schedule.remainingRuns = normalizeRemainingRuns(updates.remainingRuns);
10876
11417
  }
10877
11418
  schedule.updatedAt = Date.now();
10878
11419
  store.saveSchedule(schedule);
10879
- if (schedule.cron.type === "cron" && schedule.cron.expr) scheduleCron(schedule);
11420
+ if (schedule.cron.type !== "manual") scheduleCron(schedule);
10880
11421
  else unscheduleCron(scheduleId);
10881
11422
  return schedule;
10882
11423
  }
@@ -11503,7 +12044,7 @@ async function callOpenAICompatibleApi(provider, request) {
11503
12044
  }
11504
12045
  function createImageGenerationModule(ctx, deps) {
11505
12046
  const { storage } = ctx;
11506
- function getActiveProvider2() {
12047
+ function getActiveProvider() {
11507
12048
  const settings = storage.getSettings();
11508
12049
  const provider = settings.imageGeneration?.find((p) => p.active);
11509
12050
  if (!provider) {
@@ -11514,7 +12055,7 @@ function createImageGenerationModule(ctx, deps) {
11514
12055
  return provider;
11515
12056
  }
11516
12057
  async function generateImage(sessionId, request) {
11517
- const provider = getActiveProvider2();
12058
+ const provider = getActiveProvider();
11518
12059
  const apiResult = await callOpenAICompatibleApi(provider, request);
11519
12060
  const shareDir = storage.sessionShareDir(sessionId);
11520
12061
  if (!shareDir) throw new Error(`Session not found: ${sessionId}`);
@@ -11587,8 +12128,8 @@ function createImageGenerationModule(ctx, deps) {
11587
12128
  }
11588
12129
 
11589
12130
  // src/backend/toolbox.ts
11590
- import { join as join24, resolve as resolve8, dirname as dirname9, basename as basename4, extname as extname3 } from "path";
11591
- import { writeFile as writeFile6 } from "fs/promises";
12131
+ import { join as join25, resolve as resolve8, dirname as dirname11, basename as basename4, extname as extname3 } from "path";
12132
+ import { writeFile as writeFile7 } from "fs/promises";
11592
12133
  import { createHash as createHash6, randomUUID as randomUUID13, randomInt } from "crypto";
11593
12134
  import * as qr from "qr-image";
11594
12135
 
@@ -11733,6 +12274,606 @@ var qrCodeRespondSchema = z10.object({
11733
12274
  output: z10.string()
11734
12275
  })
11735
12276
  });
12277
+ var audioTranscribeRequestSchema = z10.object({
12278
+ path: z10.string().describe("Absolute or project-relative path to the audio file to transcribe."),
12279
+ language: z10.string().optional().describe(
12280
+ "BCP-47 language hint, e.g. 'zh-CN' or 'en-US'. If omitted, the language configured for the active speech-to-text provider is used."
12281
+ ),
12282
+ ffmpegPath: z10.string().optional().describe(
12283
+ "Absolute path to the ffmpeg executable, for when it is not on PATH (e.g. a custom install location). When provided it is used as-is: an unusable path is reported as an error instead of falling back to auto-detection. Omit it to auto-detect (FFMPEG_PATH env var, then PATH, then common install directories)."
12284
+ ),
12285
+ timeoutSeconds: z10.number().int().min(10).max(3600).default(600).describe("Maximum time to wait for the transcription, in seconds (10-3600). Default: 600.")
12286
+ });
12287
+ var audioTranscribeRespondSchema = z10.object({
12288
+ result: z10.object({
12289
+ text: z10.string(),
12290
+ durationSeconds: z10.number(),
12291
+ segments: z10.number()
12292
+ })
12293
+ });
12294
+ var textToSpeechRequestSchema = z10.object({
12295
+ text: z10.string().min(1).describe("The text to synthesize into speech."),
12296
+ output: z10.string().optional().describe(
12297
+ "Output audio path; a relative path resolves against the project root. If omitted, saves as 'speech.wav' (or 'speech.mp3' when format is 'mp3') in the project root."
12298
+ ),
12299
+ format: z10.enum(["wav", "mp3"]).default("wav").describe(
12300
+ "Output audio format. 'wav' needs no extra tools; 'mp3' additionally requires the system ffmpeg. Default: wav."
12301
+ )
12302
+ });
12303
+ var textToSpeechRespondSchema = z10.object({
12304
+ result: z10.object({
12305
+ output: z10.string(),
12306
+ format: z10.enum(["wav", "mp3"]),
12307
+ sampleRate: z10.number(),
12308
+ channels: z10.number(),
12309
+ durationSeconds: z10.number(),
12310
+ bytes: z10.number()
12311
+ })
12312
+ });
12313
+
12314
+ // src/backend/speech/transcribe.ts
12315
+ import { stat as stat6 } from "node:fs/promises";
12316
+ import { createASRClient } from "unified-realtime-asr";
12317
+
12318
+ // src/shared/speech.ts
12319
+ var DASHSCOPE_ASR_MODEL = "fun-asr-flash-8k-realtime";
12320
+ var OPENAI_ASR_MODEL = "gpt-4o-transcribe";
12321
+ var DEFAULT_ASR_MODELS = {
12322
+ dashscope: DASHSCOPE_ASR_MODEL,
12323
+ openai: OPENAI_ASR_MODEL
12324
+ };
12325
+ function effectiveAsrModel(provider) {
12326
+ return provider.asrModel?.trim() || DEFAULT_ASR_MODELS[provider.provider];
12327
+ }
12328
+ var DASHSCOPE_TTS_VOICE = "longanhuan_v3.6";
12329
+ var OPENAI_TTS_VOICE = "coral";
12330
+ var IFLYTEK_TTS_VCN = "x5_lingxiaoxuan_flow";
12331
+ var VOLC_TTS_VOICE = "zh_female_vv_uranus_bigtts";
12332
+ var DEFAULT_TTS_VOICES = {
12333
+ dashscope: DASHSCOPE_TTS_VOICE,
12334
+ openai: OPENAI_TTS_VOICE,
12335
+ iflytek: IFLYTEK_TTS_VCN,
12336
+ volcengine: VOLC_TTS_VOICE
12337
+ };
12338
+ function effectiveTtsVoice(provider) {
12339
+ return provider.voice?.trim() || DEFAULT_TTS_VOICES[provider.provider];
12340
+ }
12341
+
12342
+ // src/backend/speech/util.ts
12343
+ function optionalString(value) {
12344
+ const trimmed = value?.trim();
12345
+ return trimmed || void 0;
12346
+ }
12347
+ function requireField(value, name, direction) {
12348
+ const result = optionalString(value);
12349
+ if (!result) throw new Error(`${direction}\u914D\u7F6E\u7F3A\u5C11 ${name}\u3002`);
12350
+ return result;
12351
+ }
12352
+ function errorMessage(error) {
12353
+ return error instanceof Error ? error.message : String(error);
12354
+ }
12355
+
12356
+ // src/backend/speech/asr-config.ts
12357
+ function required(value, name) {
12358
+ return requireField(value, name, "\u5B9E\u65F6\u8BED\u97F3\u8BC6\u522B");
12359
+ }
12360
+ function buildOptions(provider, overrides = {}) {
12361
+ return {
12362
+ language: optionalString(overrides.language) ?? optionalString(provider.language) ?? "zh-CN",
12363
+ sampleRate: 16e3,
12364
+ channels: 1,
12365
+ format: "pcm",
12366
+ interimResults: true,
12367
+ punctuation: true,
12368
+ autoReconnect: false,
12369
+ transcriptionModel: provider.provider === "openai" ? effectiveAsrModel(provider) : void 0
12370
+ };
12371
+ }
12372
+ function buildAsrConfig(provider, overrides = {}) {
12373
+ const options = buildOptions(provider, overrides);
12374
+ const url = optionalString(provider.baseUrl);
12375
+ switch (provider.provider) {
12376
+ case "volcengine":
12377
+ return {
12378
+ provider: "volcengine",
12379
+ apiKey: required(provider.apiKey, "API Key"),
12380
+ resourceId: optionalString(provider.asrResourceId),
12381
+ appId: optionalString(provider.appId),
12382
+ url,
12383
+ options
12384
+ };
12385
+ case "dashscope":
12386
+ return {
12387
+ provider: "dashscope",
12388
+ apiKey: required(provider.apiKey, "API Key"),
12389
+ model: effectiveAsrModel(provider),
12390
+ workspaceId: optionalString(provider.workspaceId),
12391
+ region: provider.region,
12392
+ workspace: optionalString(provider.workspace),
12393
+ url,
12394
+ options
12395
+ };
12396
+ case "openai":
12397
+ return {
12398
+ provider: "openai",
12399
+ apiKey: required(provider.apiKey, "API Key"),
12400
+ url,
12401
+ options
12402
+ };
12403
+ case "iflytek":
12404
+ return {
12405
+ provider: "iflytek",
12406
+ appId: required(provider.appId, "App ID"),
12407
+ apiKey: required(provider.apiKey, "API Key"),
12408
+ apiSecret: required(provider.apiSecret, "API Secret"),
12409
+ url,
12410
+ options
12411
+ };
12412
+ }
12413
+ }
12414
+ function getActiveAsrProvider(ctx) {
12415
+ const provider = ctx.storage.getSettings().speechProviders.find((item) => item.asrEnabled);
12416
+ if (!provider) {
12417
+ throw new Error(
12418
+ "\u8BBE\u7F6E\u4E2D\u6CA1\u6709\u627E\u5230\u8BED\u97F3\u8BC6\u522B\uFF08ASR\uFF09\u914D\u7F6E\uFF1A\u5F53\u524D\u6CA1\u6709\u542F\u7528\u4E2D\u7684 Provider\u3002\u8BF7\u5148\u5728\u300C\u8BBE\u7F6E \u2192 \u8BED\u97F3 \u2192 \u8BC6\u522B\u300D\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\uFF0C\u7136\u540E\u91CD\u65B0\u8C03\u7528\u672C\u5DE5\u5177\u3002"
12419
+ );
12420
+ }
12421
+ return provider;
12422
+ }
12423
+
12424
+ // src/backend/speech/ffmpeg.ts
12425
+ import { spawn as spawn4 } from "node:child_process";
12426
+ import { access, constants } from "node:fs/promises";
12427
+ import { delimiter, dirname as dirname9, join as join24 } from "node:path";
12428
+ var FFMPEG_BIN = process.platform === "win32" ? "ffmpeg.exe" : "ffmpeg";
12429
+ var MAX_STDERR_BYTES = 8192;
12430
+ function fallbackDirs() {
12431
+ switch (process.platform) {
12432
+ case "darwin":
12433
+ return ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin", "/usr/bin"];
12434
+ case "win32": {
12435
+ const dirs = ["C:\\ffmpeg\\bin"];
12436
+ if (process.env.ProgramFiles) dirs.unshift(join24(process.env.ProgramFiles, "ffmpeg", "bin"));
12437
+ if (process.env.LOCALAPPDATA) {
12438
+ dirs.push(join24(process.env.LOCALAPPDATA, "Microsoft", "WinGet", "Links"));
12439
+ }
12440
+ return dirs;
12441
+ }
12442
+ default:
12443
+ return ["/usr/bin", "/usr/local/bin", "/snap/bin", "/var/lib/flatpak/exports/bin"];
12444
+ }
12445
+ }
12446
+ async function isExecutable(filePath) {
12447
+ try {
12448
+ await access(filePath, process.platform === "win32" ? constants.F_OK : constants.X_OK);
12449
+ return true;
12450
+ } catch {
12451
+ return false;
12452
+ }
12453
+ }
12454
+ function ffmpegInstallHint() {
12455
+ const howTo = process.platform === "darwin" ? " macOS: brew install ffmpeg" : process.platform === "win32" ? " Windows: winget install --id Gyan.FFmpeg -e (\u6216 choco install ffmpeg)" : " Linux: sudo apt install ffmpeg (\u6216 sudo dnf install ffmpeg)";
12456
+ return [
12457
+ "\u672A\u627E\u5230 ffmpeg\uFF0C\u65E0\u6CD5\u89E3\u7801\u97F3\u9891\u6587\u4EF6\u3002",
12458
+ "\u8BF7\u5148\u5B89\u88C5 ffmpeg\uFF0C\u7136\u540E\u91CD\u65B0\u8C03\u7528\u672C\u5DE5\u5177\uFF1A",
12459
+ howTo,
12460
+ "\u4E5F\u53EF\u4EE5\u7528 FFMPEG_PATH \u73AF\u5883\u53D8\u91CF\u6307\u5B9A ffmpeg \u53EF\u6267\u884C\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002"
12461
+ ].join("\n");
12462
+ }
12463
+ function ffmpegPathUnusableHint(explicitPath) {
12464
+ return [
12465
+ `\u6307\u5B9A\u7684 ffmpeg \u8DEF\u5F84\u4E0D\u53EF\u7528\uFF1A${explicitPath}`,
12466
+ "\u8BE5\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u6CA1\u6709\u6267\u884C\u6743\u9650\u3002",
12467
+ "\u8BF7\u4F20\u5165\u6B63\u786E\u7684 ffmpegPath\uFF0C\u6216\u8005\u4E0D\u4F20\u8BE5\u53C2\u6570\u3001\u7531\u5DE5\u5177\u81EA\u52A8\u63A2\u6D4B\uFF08FFMPEG_PATH \u73AF\u5883\u53D8\u91CF \u2192 \u7CFB\u7EDF PATH \u2192 \u5E38\u89C1\u5B89\u88C5\u76EE\u5F55\uFF09\u3002"
12468
+ ].join("\n");
12469
+ }
12470
+ async function resolveFfmpegPath(explicitPath) {
12471
+ const explicit = explicitPath?.trim();
12472
+ if (explicit) {
12473
+ return await isExecutable(explicit) ? explicit : null;
12474
+ }
12475
+ const candidates = [];
12476
+ const envPath = process.env.FFMPEG_PATH?.trim();
12477
+ if (envPath) candidates.push(envPath);
12478
+ const dirs = [
12479
+ ...envPath ? [dirname9(envPath)] : [],
12480
+ ...(process.env.PATH ?? "").split(delimiter),
12481
+ ...fallbackDirs()
12482
+ ];
12483
+ for (const dir of dirs) {
12484
+ if (dir) candidates.push(join24(dir, FFMPEG_BIN));
12485
+ }
12486
+ for (const candidate of candidates) {
12487
+ if (await isExecutable(candidate)) return candidate;
12488
+ }
12489
+ return null;
12490
+ }
12491
+ function decodeArgs(inputPath) {
12492
+ return [
12493
+ "-nostdin",
12494
+ "-hide_banner",
12495
+ "-loglevel",
12496
+ "error",
12497
+ "-i",
12498
+ inputPath,
12499
+ "-vn",
12500
+ "-f",
12501
+ "s16le",
12502
+ "-acodec",
12503
+ "pcm_s16le",
12504
+ "-ac",
12505
+ "1",
12506
+ "-ar",
12507
+ "16000",
12508
+ "-"
12509
+ ];
12510
+ }
12511
+ async function decodeAudioToPcm16k(options) {
12512
+ const { ffmpegPath, inputPath, onChunk, signal } = options;
12513
+ const child = spawn4(ffmpegPath, decodeArgs(inputPath), {
12514
+ stdio: ["ignore", "pipe", "pipe"],
12515
+ windowsHide: true
12516
+ });
12517
+ let stderr = "";
12518
+ child.stderr.setEncoding("utf8");
12519
+ child.stderr.on("data", (chunk) => {
12520
+ if (stderr.length < MAX_STDERR_BYTES) stderr += chunk;
12521
+ });
12522
+ const abort = () => child.kill("SIGKILL");
12523
+ if (signal?.aborted) abort();
12524
+ else signal?.addEventListener("abort", abort, { once: true });
12525
+ const exited = new Promise(
12526
+ (resolve9, reject) => {
12527
+ child.once("error", reject);
12528
+ child.once("close", (code2, closeSignal2) => resolve9({ code: code2, signal: closeSignal2 }));
12529
+ }
12530
+ );
12531
+ try {
12532
+ for await (const chunk of child.stdout) {
12533
+ await onChunk(chunk);
12534
+ }
12535
+ } catch (error) {
12536
+ child.kill("SIGKILL");
12537
+ await exited.catch(() => {
12538
+ });
12539
+ throw error;
12540
+ } finally {
12541
+ signal?.removeEventListener("abort", abort);
12542
+ }
12543
+ const { code, signal: closeSignal } = await exited;
12544
+ if (code !== 0) {
12545
+ const detail = stderr.trim() || `\u8FDB\u7A0B\u88AB\u7EC8\u6B62\uFF08${closeSignal ?? "unknown"}\uFF09`;
12546
+ throw new Error(`ffmpeg \u89E3\u7801\u97F3\u9891\u5931\u8D25\uFF1A${detail}`);
12547
+ }
12548
+ }
12549
+ var MP3_ARGS = ["-codec:a", "libmp3lame", "-b:a", "128k", "-f", "mp3"];
12550
+ async function encodePcmToMp3(options) {
12551
+ const { ffmpegPath, pcm, sampleRate, channels, outputPath } = options;
12552
+ const child = spawn4(
12553
+ ffmpegPath,
12554
+ [
12555
+ "-y",
12556
+ "-nostdin",
12557
+ "-hide_banner",
12558
+ "-loglevel",
12559
+ "error",
12560
+ "-f",
12561
+ "s16le",
12562
+ "-ar",
12563
+ String(sampleRate),
12564
+ "-ac",
12565
+ String(channels),
12566
+ "-i",
12567
+ "-",
12568
+ ...MP3_ARGS,
12569
+ outputPath
12570
+ ],
12571
+ { stdio: ["pipe", "ignore", "pipe"], windowsHide: true }
12572
+ );
12573
+ let stderr = "";
12574
+ child.stderr.setEncoding("utf8");
12575
+ child.stderr.on("data", (chunk) => {
12576
+ if (stderr.length < MAX_STDERR_BYTES) stderr += chunk;
12577
+ });
12578
+ const exited = new Promise((resolve9, reject) => {
12579
+ child.once("error", reject);
12580
+ child.once("close", (code2) => resolve9(code2));
12581
+ });
12582
+ try {
12583
+ await new Promise((resolve9, reject) => {
12584
+ child.stdin.once("error", reject);
12585
+ child.stdin.end(pcm, () => resolve9());
12586
+ });
12587
+ } catch (error) {
12588
+ child.kill("SIGKILL");
12589
+ await exited.catch(() => {
12590
+ });
12591
+ throw new Error(
12592
+ `ffmpeg \u7F16\u7801\u97F3\u9891\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`
12593
+ );
12594
+ }
12595
+ const code = await exited;
12596
+ if (code !== 0) {
12597
+ const detail = stderr.trim() || `\u9000\u51FA\u7801 ${code}`;
12598
+ throw new Error(`ffmpeg \u7F16\u7801 mp3 \u5931\u8D25\uFF1A${detail}`);
12599
+ }
12600
+ }
12601
+
12602
+ // src/backend/speech/transcribe.ts
12603
+ var FEED_FRAME_BYTES = 320 * 2;
12604
+ var FEED_FRAME_MS = 20;
12605
+ var FEED_SPEED = 20;
12606
+ var FLUSH_WAIT_MS = 1500;
12607
+ var DEFAULT_TIMEOUT_SECONDS = 600;
12608
+ function sleep(ms) {
12609
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
12610
+ }
12611
+ var LOCAL_INDEX_BASE = 1e6;
12612
+ function joinFinals(finals) {
12613
+ return [...finals.entries()].sort((a, b) => a[0] - b[0]).map(([, text]) => text).join("");
12614
+ }
12615
+ async function transcribeAudioFile(ctx, options) {
12616
+ const provider = getActiveAsrProvider(ctx);
12617
+ await stat6(options.path).catch(() => {
12618
+ throw new Error(`\u97F3\u9891\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${options.path}`);
12619
+ });
12620
+ const explicitFfmpegPath = options.ffmpegPath?.trim();
12621
+ const ffmpegPath = await resolveFfmpegPath(explicitFfmpegPath);
12622
+ if (!ffmpegPath) {
12623
+ throw new Error(
12624
+ explicitFfmpegPath ? ffmpegPathUnusableHint(explicitFfmpegPath) : ffmpegInstallHint()
12625
+ );
12626
+ }
12627
+ const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
12628
+ const timeoutError = () => new Error(`\u97F3\u9891\u6587\u4EF6\u8F6C\u5199\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeoutSeconds} \u79D2\uFF09\u3002`);
12629
+ const client2 = createASRClient(buildAsrConfig(provider, { language: options.language }));
12630
+ const finals = /* @__PURE__ */ new Map();
12631
+ let seq = 0;
12632
+ let lastError = null;
12633
+ client2.on("transcript", (transcript) => {
12634
+ if (!transcript.isFinal) return;
12635
+ finals.set(transcript.index ?? LOCAL_INDEX_BASE + ++seq, transcript.text);
12636
+ });
12637
+ client2.on("error", (error) => {
12638
+ lastError = errorMessage(error);
12639
+ });
12640
+ const controller = new AbortController();
12641
+ const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
12642
+ let fedBytes = 0;
12643
+ let pending = Buffer.alloc(0);
12644
+ let closed = false;
12645
+ let startedAt = 0;
12646
+ let fedMs = 0;
12647
+ const sendFrame = (frame) => {
12648
+ if (controller.signal.aborted) return;
12649
+ try {
12650
+ client2.sendAudio(frame);
12651
+ } catch (error) {
12652
+ throw new Error(`\u8BED\u97F3\u8BC6\u522B\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF1A${errorMessage(error)}`);
12653
+ }
12654
+ fedBytes += frame.length;
12655
+ };
12656
+ const feed = async (chunk) => {
12657
+ pending = pending.length === 0 ? chunk : Buffer.concat([pending, chunk]);
12658
+ while (pending.length >= FEED_FRAME_BYTES) {
12659
+ if (controller.signal.aborted) return;
12660
+ const frame = pending.subarray(0, FEED_FRAME_BYTES);
12661
+ pending = pending.subarray(FEED_FRAME_BYTES);
12662
+ sendFrame(frame);
12663
+ fedMs += FEED_FRAME_MS;
12664
+ const wait = startedAt + fedMs / FEED_SPEED - Date.now();
12665
+ if (wait > 1) await sleep(wait);
12666
+ }
12667
+ };
12668
+ try {
12669
+ await client2.connect();
12670
+ startedAt = Date.now();
12671
+ try {
12672
+ await decodeAudioToPcm16k({
12673
+ ffmpegPath,
12674
+ inputPath: options.path,
12675
+ signal: controller.signal,
12676
+ onChunk: feed
12677
+ });
12678
+ } catch (error) {
12679
+ if (controller.signal.aborted) throw timeoutError();
12680
+ throw error;
12681
+ }
12682
+ if (controller.signal.aborted) throw timeoutError();
12683
+ if (pending.length > 0) sendFrame(pending);
12684
+ await sleep(FLUSH_WAIT_MS);
12685
+ await client2.close();
12686
+ closed = true;
12687
+ if (finals.size === 0 && lastError) throw new Error(lastError);
12688
+ return {
12689
+ text: joinFinals(finals),
12690
+ durationSeconds: Math.round(fedBytes / 2 / 16e3 * 10) / 10,
12691
+ segments: finals.size
12692
+ };
12693
+ } finally {
12694
+ clearTimeout(timer);
12695
+ client2.removeAllListeners();
12696
+ if (!closed) await client2.close().catch(() => {
12697
+ });
12698
+ }
12699
+ }
12700
+
12701
+ // src/backend/speech/synthesize.ts
12702
+ import { Buffer as Buffer2 } from "node:buffer";
12703
+ import { mkdir as mkdir3, stat as stat7, writeFile as writeFile6 } from "node:fs/promises";
12704
+ import { dirname as dirname10 } from "node:path";
12705
+ import { createTTSClient } from "unified-realtime-asr";
12706
+
12707
+ // src/backend/speech/tts-config.ts
12708
+ function required2(value, name) {
12709
+ return requireField(value, name, "\u8BED\u97F3\u5408\u6210");
12710
+ }
12711
+ function buildTtsConfig(provider) {
12712
+ const options = {
12713
+ voice: effectiveTtsVoice(provider),
12714
+ format: "pcm",
12715
+ language: optionalString(provider.language),
12716
+ autoReconnect: false
12717
+ };
12718
+ const baseUrl = optionalString(provider.baseUrl);
12719
+ switch (provider.provider) {
12720
+ case "volcengine":
12721
+ return {
12722
+ provider: "volcengine",
12723
+ apiKey: required2(provider.apiKey, "API Key"),
12724
+ appId: optionalString(provider.appId),
12725
+ // 双向流式:文本可增量输入,配合逐句 flush 延迟最低
12726
+ mode: "duplex",
12727
+ url: baseUrl,
12728
+ options
12729
+ };
12730
+ case "dashscope":
12731
+ return {
12732
+ provider: "dashscope",
12733
+ apiKey: required2(provider.apiKey, "API Key"),
12734
+ model: optionalString(provider.ttsModel),
12735
+ workspaceId: optionalString(provider.workspaceId),
12736
+ region: provider.region,
12737
+ workspace: optionalString(provider.workspace),
12738
+ url: baseUrl,
12739
+ options
12740
+ };
12741
+ case "openai":
12742
+ return {
12743
+ provider: "openai",
12744
+ apiKey: required2(provider.apiKey, "API Key"),
12745
+ model: optionalString(provider.ttsModel),
12746
+ baseUrl,
12747
+ options
12748
+ };
12749
+ case "iflytek":
12750
+ return {
12751
+ provider: "iflytek",
12752
+ appId: required2(provider.appId, "App ID"),
12753
+ apiKey: required2(provider.apiKey, "API Key"),
12754
+ apiSecret: required2(provider.apiSecret, "API Secret"),
12755
+ url: baseUrl,
12756
+ options
12757
+ };
12758
+ }
12759
+ }
12760
+ function getActiveTtsProvider(ctx) {
12761
+ const provider = ctx.storage.getSettings().speechProviders.find((item) => item.ttsEnabled);
12762
+ if (!provider) {
12763
+ throw new Error(
12764
+ "\u8BBE\u7F6E\u4E2D\u6CA1\u6709\u627E\u5230\u8BED\u97F3\u5408\u6210\uFF08TTS\uFF09\u914D\u7F6E\uFF1A\u5F53\u524D\u6CA1\u6709\u542F\u7528\u4E2D\u7684 Provider\u3002\u8BF7\u5148\u5728\u300C\u8BBE\u7F6E \u2192 \u8BED\u97F3 \u2192 \u5408\u6210\u300D\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\uFF0C\u7136\u540E\u91CD\u8BD5\u3002"
12765
+ );
12766
+ }
12767
+ return provider;
12768
+ }
12769
+
12770
+ // src/backend/speech/synthesize.ts
12771
+ var MAX_TEXT_LENGTH = 1e4;
12772
+ var FALLBACK_SAMPLE_RATE = 24e3;
12773
+ var FALLBACK_CHANNELS = 1;
12774
+ var DEFAULT_TIMEOUT_SECONDS2 = 600;
12775
+ function encodeWav(pcm, sampleRate, channels) {
12776
+ const bitsPerSample = 16;
12777
+ const blockAlign = channels * (bitsPerSample / 8);
12778
+ const header = Buffer2.alloc(44);
12779
+ header.write("RIFF", 0, "ascii");
12780
+ header.writeUInt32LE(36 + pcm.length, 4);
12781
+ header.write("WAVE", 8, "ascii");
12782
+ header.write("fmt ", 12, "ascii");
12783
+ header.writeUInt32LE(16, 16);
12784
+ header.writeUInt16LE(1, 20);
12785
+ header.writeUInt16LE(channels, 22);
12786
+ header.writeUInt32LE(sampleRate, 24);
12787
+ header.writeUInt32LE(sampleRate * blockAlign, 28);
12788
+ header.writeUInt16LE(blockAlign, 32);
12789
+ header.writeUInt16LE(bitsPerSample, 34);
12790
+ header.write("data", 36, "ascii");
12791
+ header.writeUInt32LE(pcm.length, 40);
12792
+ return Buffer2.concat([header, pcm]);
12793
+ }
12794
+ async function requireFfmpegPath() {
12795
+ const ffmpegPath = await resolveFfmpegPath();
12796
+ if (!ffmpegPath) throw new Error(ffmpegInstallHint());
12797
+ return ffmpegPath;
12798
+ }
12799
+ async function synthesizeSpeechToFile(ctx, options) {
12800
+ const text = options.text.trim();
12801
+ if (!text) throw new Error("\u5F85\u5408\u6210\u7684\u6587\u672C\u4E3A\u7A7A\u3002");
12802
+ if (text.length > MAX_TEXT_LENGTH) {
12803
+ throw new Error(
12804
+ `\u5F85\u5408\u6210\u6587\u672C\u8FC7\u957F\uFF08${text.length} \u5B57\uFF0C\u4E0A\u9650 ${MAX_TEXT_LENGTH} \u5B57\uFF09\uFF1A\u8BF7\u62C6\u6210\u51E0\u6BB5\u5206\u522B\u5408\u6210\u3002`
12805
+ );
12806
+ }
12807
+ if (!/[\p{L}\p{N}]/u.test(text)) {
12808
+ throw new Error("\u5F85\u5408\u6210\u7684\u6587\u672C\u6CA1\u6709\u53EF\u6717\u8BFB\u5185\u5BB9\uFF08\u5168\u662F\u7B26\u53F7 / \u7A7A\u767D / emoji\uFF09\u3002");
12809
+ }
12810
+ const ffmpegPath = options.format === "mp3" ? await requireFfmpegPath() : null;
12811
+ await mkdir3(dirname10(options.output), { recursive: true });
12812
+ const client2 = createTTSClient(buildTtsConfig(getActiveTtsProvider(ctx)));
12813
+ const chunks = [];
12814
+ let sampleRate;
12815
+ let channels;
12816
+ let lastError = null;
12817
+ client2.on("audio", (chunk) => {
12818
+ chunks.push(Buffer2.from(chunk.audio));
12819
+ sampleRate ??= chunk.sampleRate;
12820
+ channels ??= chunk.channels;
12821
+ });
12822
+ client2.on("error", (error) => {
12823
+ lastError = errorMessage(error);
12824
+ });
12825
+ const timeoutSeconds = options.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
12826
+ let timer;
12827
+ try {
12828
+ await client2.connect();
12829
+ await Promise.race([
12830
+ (async () => {
12831
+ client2.sendText(text);
12832
+ await client2.flush();
12833
+ })(),
12834
+ new Promise((_, reject) => {
12835
+ timer = setTimeout(
12836
+ () => reject(new Error(`\u8BED\u97F3\u5408\u6210\u8D85\u65F6\uFF08\u8D85\u8FC7 ${timeoutSeconds} \u79D2\uFF09\u3002`)),
12837
+ timeoutSeconds * 1e3
12838
+ );
12839
+ })
12840
+ ]);
12841
+ } catch (error) {
12842
+ if (lastError) throw new Error(lastError);
12843
+ throw error;
12844
+ } finally {
12845
+ clearTimeout(timer);
12846
+ client2.removeAllListeners();
12847
+ await client2.close().catch(() => {
12848
+ });
12849
+ }
12850
+ const pcm = Buffer2.concat(chunks);
12851
+ if (pcm.length === 0) {
12852
+ throw new Error(lastError ?? "\u8BED\u97F3\u5408\u6210\u6CA1\u6709\u8FD4\u56DE\u4EFB\u4F55\u97F3\u9891\u6570\u636E\u3002");
12853
+ }
12854
+ const rate = sampleRate || FALLBACK_SAMPLE_RATE;
12855
+ const channelCount = channels || FALLBACK_CHANNELS;
12856
+ if (ffmpegPath) {
12857
+ await encodePcmToMp3({
12858
+ ffmpegPath,
12859
+ pcm,
12860
+ sampleRate: rate,
12861
+ channels: channelCount,
12862
+ outputPath: options.output
12863
+ });
12864
+ } else {
12865
+ await writeFile6(options.output, encodeWav(pcm, rate, channelCount));
12866
+ }
12867
+ const { size } = await stat7(options.output);
12868
+ return {
12869
+ output: options.output,
12870
+ format: options.format,
12871
+ sampleRate: rate,
12872
+ channels: channelCount,
12873
+ durationSeconds: Math.round(pcm.length / (rate * channelCount * 2) * 10) / 10,
12874
+ bytes: size
12875
+ };
12876
+ }
11736
12877
 
11737
12878
  // src/backend/toolbox.ts
11738
12879
  var CHARSETS = {
@@ -11751,7 +12892,7 @@ function getSharp() {
11751
12892
  }
11752
12893
  return sharpPromise;
11753
12894
  }
11754
- function createToolboxModule(_ctx) {
12895
+ function createToolboxModule(ctx) {
11755
12896
  function extractMetadata(metadata) {
11756
12897
  return {
11757
12898
  width: metadata.width,
@@ -11842,8 +12983,8 @@ function createToolboxModule(_ctx) {
11842
12983
  server.registry("toolbox/image-thumbnail", async (payload) => {
11843
12984
  const { path: imgPath, width, output } = imageThumbnailRequestSchema.parse(payload);
11844
12985
  const absPath = resolve8(projectDir, imgPath);
11845
- const outputPath = output ? resolve8(projectDir, output) : join24(
11846
- dirname9(absPath),
12986
+ const outputPath = output ? resolve8(projectDir, output) : join25(
12987
+ dirname11(absPath),
11847
12988
  `${basename4(absPath, extname3(absPath))}.thumb${width}${extname3(absPath)}`
11848
12989
  );
11849
12990
  const sharp = await getSharp();
@@ -11861,15 +13002,15 @@ function createToolboxModule(_ctx) {
11861
13002
  const actualHeight = resizedMeta.height ?? height ?? 0;
11862
13003
  const ext = extname3(absPath);
11863
13004
  const base = basename4(absPath, ext);
11864
- const outputPath = output ? resolve8(projectDir, output) : join24(dirname9(absPath), `${base}.${actualWidth}x${actualHeight}${ext}`);
11865
- await writeFile6(outputPath, resized);
13005
+ const outputPath = output ? resolve8(projectDir, output) : join25(dirname11(absPath), `${base}.${actualWidth}x${actualHeight}${ext}`);
13006
+ await writeFile7(outputPath, resized);
11866
13007
  const metadata = await sharp(outputPath).metadata();
11867
13008
  return { result: { output: outputPath, metadata: extractMetadata(metadata) } };
11868
13009
  });
11869
13010
  server.registry("toolbox/image-convert", async (payload) => {
11870
13011
  const { path: imgPath, format, quality, output } = imageConvertRequestSchema.parse(payload);
11871
13012
  const absPath = resolve8(projectDir, imgPath);
11872
- const outputPath = output ? resolve8(projectDir, output) : join24(dirname9(absPath), `${basename4(absPath, extname3(absPath))}.${format}`);
13013
+ const outputPath = output ? resolve8(projectDir, output) : join25(dirname11(absPath), `${basename4(absPath, extname3(absPath))}.${format}`);
11873
13014
  const sharp = await getSharp();
11874
13015
  let pipeline = sharp(absPath);
11875
13016
  const opts = {};
@@ -11888,16 +13029,41 @@ function createToolboxModule(_ctx) {
11888
13029
  margin,
11889
13030
  ec_level: errorCorrection
11890
13031
  });
11891
- await writeFile6(outputPath, png);
13032
+ await writeFile7(outputPath, png);
11892
13033
  return { result: { output: outputPath } };
11893
13034
  });
13035
+ server.registry(
13036
+ "toolbox/audio-transcribe",
13037
+ async (payload) => {
13038
+ const {
13039
+ path: audioPath,
13040
+ language,
13041
+ ffmpegPath,
13042
+ timeoutSeconds
13043
+ } = audioTranscribeRequestSchema.parse(payload);
13044
+ const absPath = resolve8(projectDir, audioPath);
13045
+ const outcome = await transcribeAudioFile(ctx, {
13046
+ path: absPath,
13047
+ language,
13048
+ ffmpegPath,
13049
+ timeoutSeconds
13050
+ });
13051
+ return { result: outcome };
13052
+ }
13053
+ );
13054
+ server.registry("toolbox/text-to-speech", async (payload) => {
13055
+ const { text, output, format } = textToSpeechRequestSchema.parse(payload);
13056
+ const outputPath = resolve8(projectDir, output ?? `speech.${format}`);
13057
+ const outcome = await synthesizeSpeechToFile(ctx, { text, output: outputPath, format });
13058
+ return { result: outcome };
13059
+ });
11894
13060
  }
11895
13061
  function buildToolboxMcpServer(options) {
11896
13062
  return {
11897
13063
  name: "toolbox",
11898
13064
  command: process.execPath,
11899
13065
  args: [
11900
- join24(process.scriptsPath, "mcp-toolbox/server.mjs"),
13066
+ join25(process.scriptsPath, "mcp-toolbox/server.mjs"),
11901
13067
  "--project-dir",
11902
13068
  options.projectDir,
11903
13069
  "--socket-path",
@@ -11912,85 +13078,11 @@ function createToolboxModule(_ctx) {
11912
13078
  };
11913
13079
  }
11914
13080
 
11915
- // src/backend/speech/manager.ts
11916
- import { Buffer as Buffer2 } from "node:buffer";
11917
- import {
11918
- createASRClient
11919
- } from "unified-realtime-asr";
11920
- var DEFAULT_DASHSCOPE_MODEL = "fun-asr-flash-8k-realtime";
11921
- var DEFAULT_OPENAI_MODEL = "gpt-4o-transcribe";
11922
- function optionalString(value) {
11923
- const trimmed = value?.trim();
11924
- return trimmed || void 0;
11925
- }
11926
- function required(value, name) {
11927
- const result = optionalString(value);
11928
- if (!result) throw new Error(`\u5B9E\u65F6\u8BED\u97F3\u8BC6\u522B\u914D\u7F6E\u7F3A\u5C11 ${name}\u3002`);
11929
- return result;
11930
- }
11931
- function buildOptions(provider) {
11932
- return {
11933
- language: optionalString(provider.language) ?? "zh-CN",
11934
- sampleRate: 16e3,
11935
- channels: 1,
11936
- format: "pcm",
11937
- interimResults: true,
11938
- punctuation: true,
11939
- autoReconnect: false,
11940
- transcriptionModel: provider.provider === "openai" ? optionalString(provider.model) ?? DEFAULT_OPENAI_MODEL : void 0
11941
- };
11942
- }
11943
- function buildConfig(provider) {
11944
- const options = buildOptions(provider);
11945
- const url = optionalString(provider.baseUrl);
11946
- switch (provider.provider) {
11947
- case "volcengine":
11948
- return {
11949
- provider: "volcengine",
11950
- apiKey: required(provider.apiKey, "API Key"),
11951
- resourceId: optionalString(provider.resourceId),
11952
- appId: optionalString(provider.appId),
11953
- url,
11954
- options
11955
- };
11956
- case "dashscope":
11957
- return {
11958
- provider: "dashscope",
11959
- apiKey: required(provider.apiKey, "API Key"),
11960
- model: optionalString(provider.model) ?? DEFAULT_DASHSCOPE_MODEL,
11961
- workspaceId: optionalString(provider.workspaceId),
11962
- region: provider.region,
11963
- workspace: optionalString(provider.workspace),
11964
- url,
11965
- options
11966
- };
11967
- case "openai":
11968
- return {
11969
- provider: "openai",
11970
- apiKey: required(provider.apiKey, "API Key"),
11971
- url,
11972
- options
11973
- };
11974
- case "iflytek":
11975
- return {
11976
- provider: "iflytek",
11977
- appId: required(provider.appId, "App ID"),
11978
- apiKey: required(provider.apiKey, "API Key"),
11979
- apiSecret: required(provider.apiSecret, "API Secret"),
11980
- url,
11981
- options
11982
- };
11983
- }
11984
- }
11985
- function getActiveProvider(ctx) {
11986
- const provider = ctx.storage.getSettings().speechToText.find((item) => item.active);
11987
- if (!provider) {
11988
- throw new Error("\u8BF7\u5148\u5728\u8BBE\u7F6E \u2192 \u8BED\u97F3\u8BC6\u522B\u4E2D\u914D\u7F6E\u5E76\u542F\u7528\u4E00\u4E2A Provider\u3002");
11989
- }
11990
- return provider;
11991
- }
11992
- function errorMessage(error) {
11993
- return error instanceof Error ? error.message : String(error);
13081
+ // src/backend/speech/asr-manager.ts
13082
+ import { Buffer as Buffer3 } from "node:buffer";
13083
+ import { createASRClient as createASRClient2 } from "unified-realtime-asr";
13084
+ function isNotConnectedError(error) {
13085
+ return typeof error === "object" && error !== null && "code" in error && error.code === "not-connected";
11994
13086
  }
11995
13087
  function createAsrManager(ctx) {
11996
13088
  const sessions = /* @__PURE__ */ new Map();
@@ -12017,9 +13109,9 @@ function createAsrManager(ctx) {
12017
13109
  async start(clientId, asrSessionId) {
12018
13110
  const key = keyOf2(clientId, asrSessionId);
12019
13111
  if (sessions.has(key)) return { ok: true };
12020
- const provider = getActiveProvider(ctx);
12021
- const client2 = createASRClient(buildConfig(provider));
12022
- const active = { clientId, asrSessionId, client: client2 };
13112
+ const provider = getActiveAsrProvider(ctx);
13113
+ const client2 = createASRClient2(buildAsrConfig(provider));
13114
+ const active = { clientId, asrSessionId, client: client2, ready: false };
12023
13115
  sessions.set(key, active);
12024
13116
  client2.on("transcript", (transcript) => {
12025
13117
  if (sessions.get(key) === active) emitTranscript(active, transcript);
@@ -12029,6 +13121,7 @@ function createAsrManager(ctx) {
12029
13121
  });
12030
13122
  client2.on("close", (info) => {
12031
13123
  if (sessions.get(key) !== active) return;
13124
+ active.ready = false;
12032
13125
  sessions.delete(key);
12033
13126
  ctx.sendEvent("asr-closed", {
12034
13127
  clientId,
@@ -12039,6 +13132,7 @@ function createAsrManager(ctx) {
12039
13132
  });
12040
13133
  try {
12041
13134
  await client2.connect();
13135
+ active.ready = true;
12042
13136
  return { ok: true };
12043
13137
  } catch (error) {
12044
13138
  sessions.delete(key);
@@ -12049,9 +13143,14 @@ function createAsrManager(ctx) {
12049
13143
  frame(clientId, asrSessionId, audioB64) {
12050
13144
  const active = sessions.get(keyOf2(clientId, asrSessionId));
12051
13145
  if (!active) return;
13146
+ if (!active.ready) return;
12052
13147
  try {
12053
- active.client.sendAudio(Buffer2.from(audioB64, "base64"));
13148
+ active.client.sendAudio(Buffer3.from(audioB64, "base64"));
12054
13149
  } catch (error) {
13150
+ if (isNotConnectedError(error)) {
13151
+ active.ready = false;
13152
+ return;
13153
+ }
12055
13154
  emitError(active, error);
12056
13155
  }
12057
13156
  },
@@ -12084,6 +13183,116 @@ function createAsrManager(ctx) {
12084
13183
  };
12085
13184
  }
12086
13185
 
13186
+ // src/backend/speech/tts-manager.ts
13187
+ import { Buffer as Buffer4 } from "node:buffer";
13188
+ import { createTTSClient as createTTSClient2 } from "unified-realtime-asr";
13189
+ function createTtsManager(ctx) {
13190
+ const connections = /* @__PURE__ */ new Map();
13191
+ const ensureConnected = async (connection) => {
13192
+ if (connection.connected) return;
13193
+ if (connection.connectFailed) throw new Error("\u8BED\u97F3\u5408\u6210\u8FDE\u63A5\u4E0D\u53EF\u7528\u3002");
13194
+ if (!connection.connecting) {
13195
+ connection.connecting = connection.client.connect().then(() => {
13196
+ connection.connected = true;
13197
+ }).catch((error) => {
13198
+ connection.connectFailed = true;
13199
+ if (connections.get(connection.ttsSessionId) === connection) {
13200
+ ctx.sendEvent("tts-error", {
13201
+ ttsSessionId: connection.ttsSessionId,
13202
+ message: errorMessage(error)
13203
+ });
13204
+ }
13205
+ throw error;
13206
+ }).finally(() => {
13207
+ connection.connecting = null;
13208
+ });
13209
+ }
13210
+ await connection.connecting;
13211
+ };
13212
+ const teardown = (connection) => {
13213
+ if (connections.get(connection.ttsSessionId) === connection) {
13214
+ connections.delete(connection.ttsSessionId);
13215
+ }
13216
+ connection.client.removeAllListeners();
13217
+ };
13218
+ return {
13219
+ async start(ttsSessionId) {
13220
+ if (connections.has(ttsSessionId)) return { ok: true };
13221
+ const client2 = createTTSClient2(buildTtsConfig(getActiveTtsProvider(ctx)));
13222
+ const connection = {
13223
+ ttsSessionId,
13224
+ client: client2,
13225
+ connected: false,
13226
+ connecting: null,
13227
+ connectFailed: false
13228
+ };
13229
+ connections.set(ttsSessionId, connection);
13230
+ client2.on("audio", (chunk) => {
13231
+ if (connections.get(ttsSessionId) !== connection) return;
13232
+ ctx.sendEvent("tts-audio", {
13233
+ ttsSessionId,
13234
+ audioB64: Buffer4.from(chunk.audio).toString("base64"),
13235
+ format: chunk.format,
13236
+ sampleRate: chunk.sampleRate,
13237
+ channels: chunk.channels,
13238
+ isFinal: chunk.isFinal,
13239
+ index: chunk.index
13240
+ });
13241
+ });
13242
+ client2.on("error", (error) => {
13243
+ if (connections.get(ttsSessionId) !== connection) return;
13244
+ ctx.sendEvent("tts-error", { ttsSessionId, message: errorMessage(error) });
13245
+ });
13246
+ client2.on("close", (info) => {
13247
+ if (connections.get(ttsSessionId) !== connection) return;
13248
+ connections.delete(ttsSessionId);
13249
+ ctx.sendEvent("tts-closed", {
13250
+ ttsSessionId,
13251
+ code: info?.code,
13252
+ reason: info?.reason
13253
+ });
13254
+ });
13255
+ return { ok: true };
13256
+ },
13257
+ async speak(ttsSessionId, text) {
13258
+ const connection = connections.get(ttsSessionId);
13259
+ if (!connection) return;
13260
+ await ensureConnected(connection);
13261
+ connection.client.sendText(text);
13262
+ await connection.client.flush();
13263
+ },
13264
+ async end(ttsSessionId) {
13265
+ const connection = connections.get(ttsSessionId);
13266
+ if (!connection) return;
13267
+ if (connection.connected || connection.connecting) {
13268
+ try {
13269
+ await connection.connecting;
13270
+ } catch {
13271
+ }
13272
+ try {
13273
+ await connection.client.close();
13274
+ } catch (error) {
13275
+ ctx.sendEvent("tts-error", { ttsSessionId, message: errorMessage(error) });
13276
+ }
13277
+ }
13278
+ teardown(connection);
13279
+ },
13280
+ async closeAll() {
13281
+ await Promise.all(
13282
+ Array.from(connections.values(), async (connection) => {
13283
+ try {
13284
+ if (connection.connected || connection.connecting) await connection.client.close();
13285
+ } catch {
13286
+ } finally {
13287
+ teardown(connection);
13288
+ }
13289
+ })
13290
+ );
13291
+ connections.clear();
13292
+ }
13293
+ };
13294
+ }
13295
+
12087
13296
  // src/backend/backend.ts
12088
13297
  function initBackend(emitter) {
12089
13298
  const listeners = [];
@@ -12110,6 +13319,7 @@ function initBackend(emitter) {
12110
13319
  const memory = createMemoryModule(ctx, { inference: _inference });
12111
13320
  const imageGeneration = createImageGenerationModule(ctx, { ilink: ilink.state });
12112
13321
  const asr = createAsrManager(ctx);
13322
+ const tts = createTtsManager(ctx);
12113
13323
  const session = createSessionModule(ctx, {
12114
13324
  bridgeConnect,
12115
13325
  askUser,
@@ -12129,6 +13339,8 @@ function initBackend(emitter) {
12129
13339
  newSession: session.newSession,
12130
13340
  getModels: session.getModels,
12131
13341
  setModel: session.setModel,
13342
+ updateSession: session.updateSession,
13343
+ loadSession: session.loadSession,
12132
13344
  respondAskUser: askUser.respondAskUser,
12133
13345
  getPendingAskUserRequests: askUser.getPendingAskUserRequests
12134
13346
  });
@@ -12228,7 +13440,7 @@ function initBackend(emitter) {
12228
13440
  const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
12229
13441
  const skillDir = skills.getSkillSystemPathFromId(skillId, { projectRoot });
12230
13442
  if (!skillDir) throw new Error(`Failed to read skill: ${skillId}`);
12231
- return join25(skillDir, SKILL_FILENAME);
13443
+ return join26(skillDir, SKILL_FILENAME);
12232
13444
  },
12233
13445
  async uninstallSkill({ skillId, projectId }) {
12234
13446
  const projectRoot = projectId ? storageOps.getProject(projectId)?.cwd : void 0;
@@ -12480,7 +13692,7 @@ function initBackend(emitter) {
12480
13692
  }) {
12481
13693
  const shareDir = storageOps.sessionShareDir(sessionId);
12482
13694
  if (!shareDir) throw new Error("Session not found");
12483
- return join25(shareDir, sharePath);
13695
+ return join26(shareDir, sharePath);
12484
13696
  },
12485
13697
  async deleteTask({ scheduleId, taskId }) {
12486
13698
  automation.deleteTask(scheduleId, taskId);
@@ -12507,12 +13719,23 @@ function initBackend(emitter) {
12507
13719
  },
12508
13720
  async stopRealtimeAsr({ clientId, asrSessionId }) {
12509
13721
  await asr.stop(clientId, asrSessionId);
13722
+ },
13723
+ // Speech Synthesis (TTS):无状态转发,文本整理在渲染层
13724
+ async startTts({ ttsSessionId }) {
13725
+ return tts.start(ttsSessionId);
13726
+ },
13727
+ async speakTts({ ttsSessionId, text }) {
13728
+ await tts.speak(ttsSessionId, text);
13729
+ },
13730
+ async endTts({ ttsSessionId }) {
13731
+ await tts.end(ttsSessionId);
12510
13732
  }
12511
13733
  };
12512
13734
  webui.setHandlers(backendHandlers2);
12513
13735
  async function closeBackend2() {
12514
13736
  automation.stopAllCrons();
12515
13737
  await asr.closeAll();
13738
+ await tts.closeAll();
12516
13739
  webui.stop();
12517
13740
  await ilink.stopIlink({ logout: false });
12518
13741
  session.clearSession();
@@ -12521,14 +13744,14 @@ function initBackend(emitter) {
12521
13744
  terminal.killAllTerminals();
12522
13745
  await watcher2.stopAll();
12523
13746
  for (const file of await readdir2(TEMP_DIR)) {
12524
- await rm3(join25(TEMP_DIR, file), { recursive: true, force: true });
13747
+ await rm3(join26(TEMP_DIR, file), { recursive: true, force: true });
12525
13748
  }
12526
13749
  }
12527
13750
  return { backendHandlers: backendHandlers2, closeBackend: closeBackend2 };
12528
13751
  }
12529
13752
 
12530
13753
  // package.json
12531
- var version = "0.1.55";
13754
+ var version = "0.1.57";
12532
13755
 
12533
13756
  // src/server/main.ts
12534
13757
  var port;