@seawork/server 1.0.22 → 2.0.2-rc.6

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 (349) hide show
  1. package/dist/scripts/supervisor-entrypoint.js +48 -8
  2. package/dist/scripts/supervisor-entrypoint.js.map +1 -1
  3. package/dist/scripts/supervisor-native-classifier.js +77 -5
  4. package/dist/scripts/supervisor-native-classifier.js.map +1 -1
  5. package/dist/scripts/supervisor-stdio-tail.js +27 -0
  6. package/dist/scripts/supervisor-stdio-tail.js.map +1 -0
  7. package/dist/scripts/supervisor.js +12 -0
  8. package/dist/scripts/supervisor.js.map +1 -1
  9. package/dist/server/client/daemon-client.d.ts +142 -2
  10. package/dist/server/client/daemon-client.d.ts.map +1 -1
  11. package/dist/server/client/daemon-client.js +384 -3
  12. package/dist/server/client/daemon-client.js.map +1 -1
  13. package/dist/server/server/agent/agent-manager.d.ts +55 -3
  14. package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
  15. package/dist/server/server/agent/agent-manager.js +324 -45
  16. package/dist/server/server/agent/agent-manager.js.map +1 -1
  17. package/dist/server/server/agent/agent-metadata-generator.d.ts +1 -0
  18. package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
  19. package/dist/server/server/agent/agent-metadata-generator.js +8 -0
  20. package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
  21. package/dist/server/server/agent/agent-projections.js +7 -2
  22. package/dist/server/server/agent/agent-projections.js.map +1 -1
  23. package/dist/server/server/agent/agent-response-loop.d.ts +3 -1
  24. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  25. package/dist/server/server/agent/agent-response-loop.js +33 -6
  26. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  27. package/dist/server/server/agent/agent-sdk-types.d.ts +43 -1
  28. package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
  29. package/dist/server/server/agent/claude-memory.d.ts +4 -0
  30. package/dist/server/server/agent/claude-memory.d.ts.map +1 -0
  31. package/dist/server/server/agent/claude-memory.js +97 -0
  32. package/dist/server/server/agent/claude-memory.js.map +1 -0
  33. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  34. package/dist/server/server/agent/mcp-server.js +247 -0
  35. package/dist/server/server/agent/mcp-server.js.map +1 -1
  36. package/dist/server/server/agent/mcp-shared.d.ts +2 -0
  37. package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
  38. package/dist/server/server/agent/provider-launch-config.d.ts +6 -139
  39. package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
  40. package/dist/server/server/agent/provider-launch-config.js +65 -33
  41. package/dist/server/server/agent/provider-launch-config.js.map +1 -1
  42. package/dist/server/server/agent/provider-manifest.d.ts +1 -0
  43. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  44. package/dist/server/server/agent/provider-manifest.js +36 -0
  45. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  46. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  47. package/dist/server/server/agent/provider-registry.js +4 -0
  48. package/dist/server/server/agent/provider-registry.js.map +1 -1
  49. package/dist/server/server/agent/provider-snapshot-manager.d.ts +3 -1
  50. package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
  51. package/dist/server/server/agent/provider-snapshot-manager.js +13 -0
  52. package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
  53. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  54. package/dist/server/server/agent/providers/claude-agent.js +141 -27
  55. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  56. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts.map +1 -1
  57. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +14 -1
  58. package/dist/server/server/agent/providers/codex/tool-call-mapper.js.map +1 -1
  59. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +132 -4
  60. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  61. package/dist/server/server/agent/providers/codex-app-server-agent.js +2233 -163
  62. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  63. package/dist/server/server/agent/providers/codex-binary-resolver.d.ts +9 -0
  64. package/dist/server/server/agent/providers/codex-binary-resolver.d.ts.map +1 -1
  65. package/dist/server/server/agent/providers/codex-binary-resolver.js +35 -14
  66. package/dist/server/server/agent/providers/codex-binary-resolver.js.map +1 -1
  67. package/dist/server/server/agent/providers/codex-health-probe.js +1 -1
  68. package/dist/server/server/agent/providers/codex-health-probe.js.map +1 -1
  69. package/dist/server/server/agent/providers/deepseek/constants.d.ts +4 -0
  70. package/dist/server/server/agent/providers/deepseek/constants.d.ts.map +1 -0
  71. package/dist/server/server/agent/providers/deepseek/constants.js +11 -0
  72. package/dist/server/server/agent/providers/deepseek/constants.js.map +1 -0
  73. package/dist/server/server/agent/providers/deepseek/event-mapper.d.ts +21 -0
  74. package/dist/server/server/agent/providers/deepseek/event-mapper.d.ts.map +1 -0
  75. package/dist/server/server/agent/providers/deepseek/event-mapper.js +286 -0
  76. package/dist/server/server/agent/providers/deepseek/event-mapper.js.map +1 -0
  77. package/dist/server/server/agent/providers/deepseek/serve-client.d.ts +94 -0
  78. package/dist/server/server/agent/providers/deepseek/serve-client.d.ts.map +1 -0
  79. package/dist/server/server/agent/providers/deepseek/serve-client.js +142 -0
  80. package/dist/server/server/agent/providers/deepseek/serve-client.js.map +1 -0
  81. package/dist/server/server/agent/providers/deepseek/serve-process.d.ts +18 -0
  82. package/dist/server/server/agent/providers/deepseek/serve-process.d.ts.map +1 -0
  83. package/dist/server/server/agent/providers/deepseek/serve-process.js +93 -0
  84. package/dist/server/server/agent/providers/deepseek/serve-process.js.map +1 -0
  85. package/dist/server/server/agent/providers/deepseek-agent.d.ts +94 -0
  86. package/dist/server/server/agent/providers/deepseek-agent.d.ts.map +1 -0
  87. package/dist/server/server/agent/providers/deepseek-agent.js +811 -0
  88. package/dist/server/server/agent/providers/deepseek-agent.js.map +1 -0
  89. package/dist/server/server/agent/providers/gateway-telemetry.d.ts +9 -0
  90. package/dist/server/server/agent/providers/gateway-telemetry.d.ts.map +1 -0
  91. package/dist/server/server/agent/providers/gateway-telemetry.js +36 -0
  92. package/dist/server/server/agent/providers/gateway-telemetry.js.map +1 -0
  93. package/dist/server/server/agent/providers/seaagent/constants.d.ts +3 -0
  94. package/dist/server/server/agent/providers/seaagent/constants.d.ts.map +1 -0
  95. package/dist/server/server/agent/providers/seaagent/constants.js +3 -0
  96. package/dist/server/server/agent/providers/seaagent/constants.js.map +1 -0
  97. package/dist/server/server/agent/providers/seaagent/event-mapper.d.ts +3 -0
  98. package/dist/server/server/agent/providers/seaagent/event-mapper.d.ts.map +1 -0
  99. package/dist/server/server/agent/providers/seaagent/event-mapper.js +69 -0
  100. package/dist/server/server/agent/providers/seaagent/event-mapper.js.map +1 -0
  101. package/dist/server/server/agent/providers/seaagent/rpc-client.d.ts +23 -0
  102. package/dist/server/server/agent/providers/seaagent/rpc-client.d.ts.map +1 -0
  103. package/dist/server/server/agent/providers/seaagent/rpc-client.js +139 -0
  104. package/dist/server/server/agent/providers/seaagent/rpc-client.js.map +1 -0
  105. package/dist/server/server/agent/providers/seaagent/tool-call-mapper.d.ts +3 -0
  106. package/dist/server/server/agent/providers/seaagent/tool-call-mapper.d.ts.map +1 -0
  107. package/dist/server/server/agent/providers/seaagent/tool-call-mapper.js +38 -0
  108. package/dist/server/server/agent/providers/seaagent/tool-call-mapper.js.map +1 -0
  109. package/dist/server/server/agent/providers/seaagent-agent.d.ts +81 -0
  110. package/dist/server/server/agent/providers/seaagent-agent.d.ts.map +1 -0
  111. package/dist/server/server/agent/providers/seaagent-agent.js +502 -0
  112. package/dist/server/server/agent/providers/seaagent-agent.js.map +1 -0
  113. package/dist/server/server/agent/providers/seaagent-binary-resolver.d.ts +18 -0
  114. package/dist/server/server/agent/providers/seaagent-binary-resolver.d.ts.map +1 -0
  115. package/dist/server/server/agent/providers/seaagent-binary-resolver.js +46 -0
  116. package/dist/server/server/agent/providers/seaagent-binary-resolver.js.map +1 -0
  117. package/dist/server/server/agent/providers/seaagent-health-probe.d.ts +11 -0
  118. package/dist/server/server/agent/providers/seaagent-health-probe.d.ts.map +1 -0
  119. package/dist/server/server/agent/providers/seaagent-health-probe.js +49 -0
  120. package/dist/server/server/agent/providers/seaagent-health-probe.js.map +1 -0
  121. package/dist/server/server/agent/providers/seawork-models.d.ts +8 -0
  122. package/dist/server/server/agent/providers/seawork-models.d.ts.map +1 -1
  123. package/dist/server/server/agent/providers/seawork-models.js +118 -74
  124. package/dist/server/server/agent/providers/seawork-models.js.map +1 -1
  125. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +2 -2
  126. package/dist/server/server/agent/timeline-projection.d.ts +5 -1
  127. package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
  128. package/dist/server/server/agent/timeline-projection.js +20 -4
  129. package/dist/server/server/agent/timeline-projection.js.map +1 -1
  130. package/dist/server/server/agent-attention-policy.d.ts +1 -0
  131. package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
  132. package/dist/server/server/agent-attention-policy.js +6 -0
  133. package/dist/server/server/agent-attention-policy.js.map +1 -1
  134. package/dist/server/server/allowed-hosts.d.ts +13 -0
  135. package/dist/server/server/allowed-hosts.d.ts.map +1 -1
  136. package/dist/server/server/allowed-hosts.js +33 -0
  137. package/dist/server/server/allowed-hosts.js.map +1 -1
  138. package/dist/server/server/bootstrap.d.ts +2 -0
  139. package/dist/server/server/bootstrap.d.ts.map +1 -1
  140. package/dist/server/server/bootstrap.js +200 -14
  141. package/dist/server/server/bootstrap.js.map +1 -1
  142. package/dist/server/server/browser-extension-token.d.ts +23 -0
  143. package/dist/server/server/browser-extension-token.d.ts.map +1 -0
  144. package/dist/server/server/browser-extension-token.js +114 -0
  145. package/dist/server/server/browser-extension-token.js.map +1 -0
  146. package/dist/server/server/bug-report-handler.d.ts +7 -1
  147. package/dist/server/server/bug-report-handler.d.ts.map +1 -1
  148. package/dist/server/server/bug-report-handler.js +73 -5
  149. package/dist/server/server/bug-report-handler.js.map +1 -1
  150. package/dist/server/server/bug-report-redact.d.ts +25 -1
  151. package/dist/server/server/bug-report-redact.d.ts.map +1 -1
  152. package/dist/server/server/bug-report-redact.js +42 -5
  153. package/dist/server/server/bug-report-redact.js.map +1 -1
  154. package/dist/server/server/config.d.ts +1 -0
  155. package/dist/server/server/config.d.ts.map +1 -1
  156. package/dist/server/server/config.js +51 -1
  157. package/dist/server/server/config.js.map +1 -1
  158. package/dist/server/server/crash-report.d.ts.map +1 -1
  159. package/dist/server/server/crash-report.js +18 -0
  160. package/dist/server/server/crash-report.js.map +1 -1
  161. package/dist/server/server/daemon-config-store.d.ts.map +1 -1
  162. package/dist/server/server/daemon-config-store.js +94 -3
  163. package/dist/server/server/daemon-config-store.js.map +1 -1
  164. package/dist/server/server/disk-full.d.ts +4 -0
  165. package/dist/server/server/disk-full.d.ts.map +1 -0
  166. package/dist/server/server/disk-full.js +46 -0
  167. package/dist/server/server/disk-full.js.map +1 -0
  168. package/dist/server/server/exports.d.ts +3 -2
  169. package/dist/server/server/exports.d.ts.map +1 -1
  170. package/dist/server/server/exports.js +2 -1
  171. package/dist/server/server/exports.js.map +1 -1
  172. package/dist/server/server/git-forge/github-client.d.ts +18 -0
  173. package/dist/server/server/git-forge/github-client.d.ts.map +1 -1
  174. package/dist/server/server/git-forge/github-client.js +88 -0
  175. package/dist/server/server/git-forge/github-client.js.map +1 -1
  176. package/dist/server/server/git-forge/parse-remote.d.ts +2 -0
  177. package/dist/server/server/git-forge/parse-remote.d.ts.map +1 -1
  178. package/dist/server/server/git-forge/parse-remote.js +71 -6
  179. package/dist/server/server/git-forge/parse-remote.js.map +1 -1
  180. package/dist/server/server/git-forge/service.d.ts +87 -0
  181. package/dist/server/server/git-forge/service.d.ts.map +1 -1
  182. package/dist/server/server/git-forge/service.js +198 -4
  183. package/dist/server/server/git-forge/service.js.map +1 -1
  184. package/dist/server/server/index.js +72 -0
  185. package/dist/server/server/index.js.map +1 -1
  186. package/dist/server/server/integrations/wecom-openclaw/bridge.d.ts +88 -0
  187. package/dist/server/server/integrations/wecom-openclaw/bridge.d.ts.map +1 -0
  188. package/dist/server/server/integrations/wecom-openclaw/bridge.js +1229 -0
  189. package/dist/server/server/integrations/wecom-openclaw/bridge.js.map +1 -0
  190. package/dist/server/server/integrations/wecom-openclaw/qr.d.ts +38 -0
  191. package/dist/server/server/integrations/wecom-openclaw/qr.d.ts.map +1 -0
  192. package/dist/server/server/integrations/wecom-openclaw/qr.js +101 -0
  193. package/dist/server/server/integrations/wecom-openclaw/qr.js.map +1 -0
  194. package/dist/server/server/integrations/wecom-openclaw/workspace.d.ts +5 -0
  195. package/dist/server/server/integrations/wecom-openclaw/workspace.d.ts.map +1 -0
  196. package/dist/server/server/integrations/wecom-openclaw/workspace.js +40 -0
  197. package/dist/server/server/integrations/wecom-openclaw/workspace.js.map +1 -0
  198. package/dist/server/server/latency-proxy.d.ts.map +1 -1
  199. package/dist/server/server/latency-proxy.js +45 -5
  200. package/dist/server/server/latency-proxy.js.map +1 -1
  201. package/dist/server/server/library/codex-skill-discovery.d.ts +9 -0
  202. package/dist/server/server/library/codex-skill-discovery.d.ts.map +1 -0
  203. package/dist/server/server/library/codex-skill-discovery.js +49 -0
  204. package/dist/server/server/library/codex-skill-discovery.js.map +1 -0
  205. package/dist/server/server/library/hub-install.d.ts +79 -0
  206. package/dist/server/server/library/hub-install.d.ts.map +1 -0
  207. package/dist/server/server/library/hub-install.js +263 -0
  208. package/dist/server/server/library/hub-install.js.map +1 -0
  209. package/dist/server/server/library/hub-test-run.d.ts +81 -0
  210. package/dist/server/server/library/hub-test-run.d.ts.map +1 -0
  211. package/dist/server/server/library/hub-test-run.js +237 -0
  212. package/dist/server/server/library/hub-test-run.js.map +1 -0
  213. package/dist/server/server/library/library-import.d.ts +27 -0
  214. package/dist/server/server/library/library-import.d.ts.map +1 -0
  215. package/dist/server/server/library/library-import.js +227 -0
  216. package/dist/server/server/library/library-import.js.map +1 -0
  217. package/dist/server/server/library/library-injection.d.ts +16 -0
  218. package/dist/server/server/library/library-injection.d.ts.map +1 -0
  219. package/dist/server/server/library/library-injection.js +49 -0
  220. package/dist/server/server/library/library-injection.js.map +1 -0
  221. package/dist/server/server/library/library-rpc.d.ts +73 -0
  222. package/dist/server/server/library/library-rpc.d.ts.map +1 -0
  223. package/dist/server/server/library/library-rpc.js +239 -0
  224. package/dist/server/server/library/library-rpc.js.map +1 -0
  225. package/dist/server/server/library/library-store.d.ts +35 -0
  226. package/dist/server/server/library/library-store.d.ts.map +1 -0
  227. package/dist/server/server/library/library-store.js +169 -0
  228. package/dist/server/server/library/library-store.js.map +1 -0
  229. package/dist/server/server/library/library-sync.d.ts +46 -0
  230. package/dist/server/server/library/library-sync.d.ts.map +1 -0
  231. package/dist/server/server/library/library-sync.js +235 -0
  232. package/dist/server/server/library/library-sync.js.map +1 -0
  233. package/dist/server/server/library/library-types.d.ts +756 -0
  234. package/dist/server/server/library/library-types.d.ts.map +1 -0
  235. package/dist/server/server/library/library-types.js +99 -0
  236. package/dist/server/server/library/library-types.js.map +1 -0
  237. package/dist/server/server/library/worktree-dev.d.ts +14 -0
  238. package/dist/server/server/library/worktree-dev.d.ts.map +1 -0
  239. package/dist/server/server/library/worktree-dev.js +24 -0
  240. package/dist/server/server/library/worktree-dev.js.map +1 -0
  241. package/dist/server/server/log-stream-error.d.ts +2 -0
  242. package/dist/server/server/log-stream-error.d.ts.map +1 -0
  243. package/dist/server/server/log-stream-error.js +33 -0
  244. package/dist/server/server/log-stream-error.js.map +1 -0
  245. package/dist/server/server/logger.d.ts +1 -0
  246. package/dist/server/server/logger.d.ts.map +1 -1
  247. package/dist/server/server/logger.js +32 -0
  248. package/dist/server/server/logger.js.map +1 -1
  249. package/dist/server/server/loop/rpc-schemas.d.ts +96 -96
  250. package/dist/server/server/loop-service.d.ts +18 -18
  251. package/dist/server/server/messages.d.ts +4 -1
  252. package/dist/server/server/messages.d.ts.map +1 -1
  253. package/dist/server/server/messages.js +40 -2
  254. package/dist/server/server/messages.js.map +1 -1
  255. package/dist/server/server/node-pty-error.d.ts +2 -0
  256. package/dist/server/server/node-pty-error.d.ts.map +1 -0
  257. package/dist/server/server/node-pty-error.js +19 -0
  258. package/dist/server/server/node-pty-error.js.map +1 -0
  259. package/dist/server/server/persisted-config.d.ts +219 -135
  260. package/dist/server/server/persisted-config.d.ts.map +1 -1
  261. package/dist/server/server/persisted-config.js +35 -1
  262. package/dist/server/server/persisted-config.js.map +1 -1
  263. package/dist/server/server/port-in-use.d.ts +4 -0
  264. package/dist/server/server/port-in-use.d.ts.map +1 -0
  265. package/dist/server/server/port-in-use.js +35 -0
  266. package/dist/server/server/port-in-use.js.map +1 -0
  267. package/dist/server/server/provider-runtime-settings-mask.d.ts +7 -0
  268. package/dist/server/server/provider-runtime-settings-mask.d.ts.map +1 -0
  269. package/dist/server/server/provider-runtime-settings-mask.js +65 -0
  270. package/dist/server/server/provider-runtime-settings-mask.js.map +1 -0
  271. package/dist/server/server/sac/auth.d.ts +12 -0
  272. package/dist/server/server/sac/auth.d.ts.map +1 -1
  273. package/dist/server/server/sac/auth.js +19 -1
  274. package/dist/server/server/sac/auth.js.map +1 -1
  275. package/dist/server/server/sac/index.d.ts +2 -2
  276. package/dist/server/server/sac/index.d.ts.map +1 -1
  277. package/dist/server/server/sac/index.js +2 -2
  278. package/dist/server/server/sac/index.js.map +1 -1
  279. package/dist/server/server/sac/poll.d.ts +2 -0
  280. package/dist/server/server/sac/poll.d.ts.map +1 -1
  281. package/dist/server/server/sac/poll.js +7 -2
  282. package/dist/server/server/sac/poll.js.map +1 -1
  283. package/dist/server/server/schedule/cron.d.ts.map +1 -1
  284. package/dist/server/server/schedule/cron.js +6 -6
  285. package/dist/server/server/schedule/cron.js.map +1 -1
  286. package/dist/server/server/schedule/rpc-schemas.d.ts +895 -0
  287. package/dist/server/server/schedule/rpc-schemas.d.ts.map +1 -1
  288. package/dist/server/server/schedule/rpc-schemas.js +34 -0
  289. package/dist/server/server/schedule/rpc-schemas.js.map +1 -1
  290. package/dist/server/server/schedule/service.d.ts +5 -1
  291. package/dist/server/server/schedule/service.d.ts.map +1 -1
  292. package/dist/server/server/schedule/service.js +97 -14
  293. package/dist/server/server/schedule/service.js.map +1 -1
  294. package/dist/server/server/schedule/types.d.ts +19 -0
  295. package/dist/server/server/schedule/types.d.ts.map +1 -1
  296. package/dist/server/server/schedule/types.js +1 -0
  297. package/dist/server/server/schedule/types.js.map +1 -1
  298. package/dist/server/server/session.d.ts +83 -2
  299. package/dist/server/server/session.d.ts.map +1 -1
  300. package/dist/server/server/session.js +895 -82
  301. package/dist/server/server/session.js.map +1 -1
  302. package/dist/server/server/speech/native-runtime-guard.d.ts +1 -0
  303. package/dist/server/server/speech/native-runtime-guard.d.ts.map +1 -1
  304. package/dist/server/server/speech/native-runtime-guard.js +10 -4
  305. package/dist/server/server/speech/native-runtime-guard.js.map +1 -1
  306. package/dist/server/server/websocket-server.d.ts +6 -1
  307. package/dist/server/server/websocket-server.d.ts.map +1 -1
  308. package/dist/server/server/websocket-server.js +79 -7
  309. package/dist/server/server/websocket-server.js.map +1 -1
  310. package/dist/server/server/workspace-git-service.d.ts +2 -1
  311. package/dist/server/server/workspace-git-service.d.ts.map +1 -1
  312. package/dist/server/server/workspace-git-service.js +7 -3
  313. package/dist/server/server/workspace-git-service.js.map +1 -1
  314. package/dist/server/server/workspace-registry-model.d.ts +1 -0
  315. package/dist/server/server/workspace-registry-model.d.ts.map +1 -1
  316. package/dist/server/server/workspace-registry-model.js +18 -0
  317. package/dist/server/server/workspace-registry-model.js.map +1 -1
  318. package/dist/server/server/worktree-session.d.ts +3 -3
  319. package/dist/server/server/worktree-session.d.ts.map +1 -1
  320. package/dist/server/server/worktree-session.js +1 -3
  321. package/dist/server/server/worktree-session.js.map +1 -1
  322. package/dist/server/shared/messages.d.ts +59658 -21927
  323. package/dist/server/shared/messages.d.ts.map +1 -1
  324. package/dist/server/shared/messages.js +531 -3
  325. package/dist/server/shared/messages.js.map +1 -1
  326. package/dist/server/shared/provider-runtime-settings.d.ts +87 -0
  327. package/dist/server/shared/provider-runtime-settings.d.ts.map +1 -0
  328. package/dist/server/shared/provider-runtime-settings.js +33 -0
  329. package/dist/server/shared/provider-runtime-settings.js.map +1 -0
  330. package/dist/server/terminal/terminal.d.ts +9 -0
  331. package/dist/server/terminal/terminal.d.ts.map +1 -1
  332. package/dist/server/terminal/terminal.js +100 -3
  333. package/dist/server/terminal/terminal.js.map +1 -1
  334. package/dist/server/utils/checkout-git.d.ts +23 -1
  335. package/dist/server/utils/checkout-git.d.ts.map +1 -1
  336. package/dist/server/utils/checkout-git.js +182 -21
  337. package/dist/server/utils/checkout-git.js.map +1 -1
  338. package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
  339. package/dist/server/utils/directory-suggestions.js +57 -9
  340. package/dist/server/utils/directory-suggestions.js.map +1 -1
  341. package/dist/src/server/bug-report-redact.js +42 -5
  342. package/dist/src/server/bug-report-redact.js.map +1 -1
  343. package/dist/src/server/crash-report.js +18 -0
  344. package/dist/src/server/crash-report.js.map +1 -1
  345. package/dist/src/server/speech/native-runtime-guard.js +177 -0
  346. package/dist/src/server/speech/native-runtime-guard.js.map +1 -0
  347. package/dist/src/server/speech/speech-types.js +8 -0
  348. package/dist/src/server/speech/speech-types.js.map +1 -0
  349. package/package.json +16 -4
@@ -0,0 +1,1229 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { generateReqId, MessageType, WSAuthFailureError, WSClient, WSReconnectExhaustedError, } from "@wecom/aibot-node-sdk";
6
+ import { DEFAULT_AGENT_PROVIDER, getAgentProviderDefinition, isValidAgentProvider, } from "../../agent/provider-manifest.js";
7
+ import { defaultWeComOpenClawWorkspace, resolveWeComOpenClawAgentCwd } from "./workspace.js";
8
+ const DEFAULT_WEBSOCKET_URL = "wss://openws.work.weixin.qq.com";
9
+ const OPENCLAW_SCENE = 1;
10
+ const OPENCLAW_PLUGIN_VERSION = "2026.5.25";
11
+ const MAX_MARKDOWN_BYTES = 18000;
12
+ const MAX_STREAM_BYTES = MAX_MARKDOWN_BYTES;
13
+ const DEFAULT_STREAM_UPDATE_INTERVAL_MS = 1000;
14
+ const DEFAULT_LOADING_TICK_INTERVAL_MS = 3000;
15
+ const STREAM_TRUNCATION_SUFFIX = "\n\n...(内容过长,已截断)";
16
+ const DEFAULT_CONVERSATION_IDLE_TTL_MS = 30 * 60 * 1000;
17
+ const DEFAULT_MAX_CONVERSATIONS = 200;
18
+ const LOADING_ELLIPSIS_FRAMES = ["…", "……", "………"];
19
+ const DEFAULT_MAX_INBOUND_MEDIA_BYTES = 10 * 1024 * 1024;
20
+ const DEFAULT_MEDIA_DOWNLOAD_TIMEOUT_MS = 20000;
21
+ const DEFAULT_MEDIA_AGGREGATE_WINDOW_MS = 1500;
22
+ // Providers whose AgentPromptInput accepts image content blocks. seaagent and
23
+ // deepseek throw on image blocks, so the bridge must downgrade to text for them.
24
+ const IMAGE_CAPABLE_PROVIDERS = new Set(["codex", "claude"]);
25
+ const MEDIA_DIRECTIVE_PATTERN = /^(MEDIA|FILE):(.+)$/;
26
+ export class WeComOpenClawBridge {
27
+ constructor(options) {
28
+ this.conversations = new Map();
29
+ this.wsClient = null;
30
+ this.stopped = false;
31
+ this.agentManager = options.agentManager;
32
+ this.config = options.config;
33
+ this.loadAgent = options.loadAgent ?? null;
34
+ this.allowFirstUser = options.allowFirstUser ?? null;
35
+ this.now = options.now ?? Date.now;
36
+ this.conversationIdleTtlMs = options.conversationIdleTtlMs ?? DEFAULT_CONVERSATION_IDLE_TTL_MS;
37
+ this.maxConversations = Math.max(1, options.maxConversations ?? DEFAULT_MAX_CONVERSATIONS);
38
+ this.streamUpdateIntervalMs = Math.max(0, options.streamUpdateIntervalMs ?? DEFAULT_STREAM_UPDATE_INTERVAL_MS);
39
+ this.loadingTickIntervalMs = Math.max(1, options.loadingTickIntervalMs ?? DEFAULT_LOADING_TICK_INTERVAL_MS);
40
+ this.logger = options.logger.child({
41
+ module: "integrations",
42
+ integration: "wecom-openclaw",
43
+ });
44
+ }
45
+ updateConfig(patch) {
46
+ const previousConnectionKey = buildConnectionKey(this.config);
47
+ this.config = {
48
+ ...this.config,
49
+ ...patch,
50
+ };
51
+ const nextConnectionKey = buildConnectionKey(this.config);
52
+ if (this.stopped) {
53
+ return;
54
+ }
55
+ if (!this.config.enabled) {
56
+ this.wsClient?.disconnect();
57
+ this.wsClient = null;
58
+ return;
59
+ }
60
+ if (this.wsClient && previousConnectionKey === nextConnectionKey) {
61
+ return;
62
+ }
63
+ this.wsClient?.disconnect();
64
+ this.wsClient = null;
65
+ this.start();
66
+ }
67
+ start() {
68
+ if (!this.config.enabled) {
69
+ return;
70
+ }
71
+ if (!this.config.botId || !this.config.secret) {
72
+ this.logger.warn("OpenClaw WeCom bridge enabled without botId/secret; skipping startup");
73
+ return;
74
+ }
75
+ if (!hasExplicitAllowlist(this.config) && !this.config.pendingAllowFirstUser) {
76
+ this.logger.warn("OpenClaw WeCom bridge enabled without allowUsers/allowChats; skipping startup");
77
+ return;
78
+ }
79
+ if (this.wsClient) {
80
+ return;
81
+ }
82
+ const wsClient = new WSClient({
83
+ botId: this.config.botId,
84
+ secret: this.config.secret,
85
+ wsUrl: this.config.websocketUrl ?? DEFAULT_WEBSOCKET_URL,
86
+ heartbeatInterval: 30000,
87
+ maxReconnectAttempts: 10,
88
+ maxAuthFailureAttempts: 5,
89
+ scene: OPENCLAW_SCENE,
90
+ plug_version: this.config.pluginVersion ?? OPENCLAW_PLUGIN_VERSION,
91
+ logger: {
92
+ debug: (message, ...args) => this.logger.debug({ args }, message),
93
+ info: (message, ...args) => this.logger.info({ args }, message),
94
+ warn: (message, ...args) => this.logger.warn({ args }, message),
95
+ error: (message, ...args) => this.logger.error({ args }, message),
96
+ },
97
+ });
98
+ wsClient.on("connected", () => {
99
+ this.logger.info("OpenClaw WeCom WebSocket connected");
100
+ });
101
+ wsClient.on("authenticated", () => {
102
+ this.logger.info("OpenClaw WeCom WebSocket authenticated");
103
+ });
104
+ wsClient.on("disconnected", (reason) => {
105
+ this.logger.warn({ reason }, "OpenClaw WeCom WebSocket disconnected");
106
+ });
107
+ wsClient.on("reconnecting", (attempt) => {
108
+ this.logger.warn({ attempt }, "OpenClaw WeCom WebSocket reconnecting");
109
+ });
110
+ wsClient.on("event.disconnected_event", (frame) => {
111
+ this.logger.error({ reqId: frame.headers.req_id }, "OpenClaw WeCom WebSocket was kicked by another connection");
112
+ wsClient.disconnect();
113
+ });
114
+ wsClient.on("event.enter_chat", (frame) => {
115
+ void this.replyWelcome(frame);
116
+ });
117
+ wsClient.on("error", (error) => {
118
+ this.logger.error({ err: error }, "OpenClaw WeCom WebSocket error");
119
+ if (error instanceof WSAuthFailureError || error instanceof WSReconnectExhaustedError) {
120
+ wsClient.disconnect();
121
+ }
122
+ });
123
+ wsClient.on("message", (frame) => {
124
+ void this.enqueueMessage(frame);
125
+ });
126
+ this.wsClient = wsClient;
127
+ wsClient.connect();
128
+ }
129
+ stop() {
130
+ this.stopped = true;
131
+ this.wsClient?.disconnect();
132
+ this.wsClient = null;
133
+ }
134
+ async replyWelcome(frame) {
135
+ if (!this.wsClient || this.stopped) {
136
+ return;
137
+ }
138
+ try {
139
+ await this.wsClient.replyWelcome(frame, {
140
+ msgtype: "text",
141
+ text: {
142
+ content: "已连接 Seawork。直接发送任务即可。",
143
+ },
144
+ });
145
+ }
146
+ catch (error) {
147
+ this.logger.warn({ err: error }, "Failed to send OpenClaw WeCom welcome reply");
148
+ }
149
+ }
150
+ async enqueueMessage(frame) {
151
+ if (this.stopped) {
152
+ return;
153
+ }
154
+ const inbound = this.parseInboundMessage(frame);
155
+ if (!inbound) {
156
+ return;
157
+ }
158
+ if (!this.isAllowed(inbound) && !(await this.allowPendingFirstUser(inbound))) {
159
+ this.logger.warn({
160
+ chatId: inbound.chatId,
161
+ chatType: inbound.chatType,
162
+ senderId: inbound.senderId,
163
+ }, "Rejected OpenClaw WeCom message by allowlist");
164
+ return;
165
+ }
166
+ const key = this.conversationKey(inbound);
167
+ if (this.bufferAttachmentOnly(key, inbound)) {
168
+ return;
169
+ }
170
+ this.mergeBufferedAttachments(key, inbound);
171
+ const now = this.now();
172
+ this.pruneConversations(now);
173
+ let existing = this.conversations.get(key);
174
+ if (!existing && this.conversations.size >= this.maxConversations) {
175
+ this.evictOldestIdleConversation();
176
+ existing = this.conversations.get(key);
177
+ if (!existing && this.conversations.size >= this.maxConversations) {
178
+ this.logger.warn({ maxConversations: this.maxConversations }, "Rejected OpenClaw WeCom message because conversation cache is full");
179
+ return;
180
+ }
181
+ }
182
+ const pending = (existing?.pending ?? Promise.resolve())
183
+ .catch(() => undefined)
184
+ .then(() => this.handleMessage(key, inbound))
185
+ .finally(() => this.markConversationIdle(key, pending));
186
+ this.conversations.set(key, {
187
+ agentId: existing?.agentId ?? "",
188
+ pending,
189
+ lastActivityAt: now,
190
+ active: true,
191
+ });
192
+ }
193
+ parseInboundMessage(frame) {
194
+ const body = frame.body;
195
+ if (!body?.from?.userid || !body.msgid) {
196
+ return null;
197
+ }
198
+ const chatId = body.chatid || body.from.userid;
199
+ const chatType = body.chattype === "group" ? "group" : "single";
200
+ const text = extractText(body).trim();
201
+ const attachments = extractAttachments(body);
202
+ if (!text && attachments.length === 0) {
203
+ return null;
204
+ }
205
+ return {
206
+ frame,
207
+ chatId,
208
+ chatType,
209
+ senderId: body.from.userid,
210
+ text,
211
+ attachments,
212
+ receivedAt: this.now(),
213
+ };
214
+ }
215
+ isAllowed(message) {
216
+ const allowUsers = normalizeAllowlist(this.config.allowUsers);
217
+ const allowChats = normalizeAllowlist(this.config.allowChats);
218
+ if (allowUsers.size === 0 && allowChats.size === 0) {
219
+ return false;
220
+ }
221
+ if (allowUsers.has("*") || allowUsers.has(message.senderId)) {
222
+ return true;
223
+ }
224
+ if (allowChats.has("*") || allowChats.has(message.chatId)) {
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ async allowPendingFirstUser(message) {
230
+ if (!this.config.pendingAllowFirstUser || !this.allowFirstUser) {
231
+ return false;
232
+ }
233
+ if (message.chatType !== "single" || message.chatId !== message.senderId) {
234
+ return false;
235
+ }
236
+ const consumed = await this.allowFirstUser(message.senderId);
237
+ if (!consumed) {
238
+ this.config.pendingAllowFirstUser = false;
239
+ this.logger.warn({ senderId: message.senderId }, "Rejected OpenClaw WeCom first-user binding because it was already consumed");
240
+ return false;
241
+ }
242
+ this.config.allowUsers = mergeAllowlist(this.config.allowUsers, [message.senderId]);
243
+ this.config.pendingAllowFirstUser = false;
244
+ this.logger.info({ senderId: message.senderId }, "Allowed first OpenClaw WeCom user");
245
+ return true;
246
+ }
247
+ conversationKey(message) {
248
+ return `${message.chatType}:${message.chatId}`;
249
+ }
250
+ // Hold an attachment-only single-chat message briefly so a follow-up text can
251
+ // be merged ("screenshot + text" split into two WeCom messages). Returns true
252
+ // when the message was buffered (caller must stop processing it now).
253
+ bufferAttachmentOnly(key, message) {
254
+ const windowMs = this.mediaAggregateWindowMs();
255
+ if (windowMs <= 0 || message.chatType !== "single") {
256
+ return false;
257
+ }
258
+ if (message.text || message.attachments.length === 0) {
259
+ return false;
260
+ }
261
+ const state = this.ensureConversation(key);
262
+ const existing = state.pendingAttachments;
263
+ // Only coalesce buffers from the same sender; never let a different user's
264
+ // attachments bleed into this turn.
265
+ const prior = existing && existing.senderId === message.senderId ? existing.attachments : [];
266
+ if (existing?.timer) {
267
+ clearTimeout(existing.timer);
268
+ }
269
+ const attachments = [...prior, ...message.attachments];
270
+ const flushMessage = { ...message, attachments };
271
+ const timer = setTimeout(() => {
272
+ this.flushPendingAttachments(key, flushMessage);
273
+ }, windowMs);
274
+ timer.unref?.();
275
+ state.pendingAttachments = {
276
+ senderId: message.senderId,
277
+ attachments,
278
+ timer,
279
+ };
280
+ state.lastActivityAt = this.now();
281
+ return true;
282
+ }
283
+ mergeBufferedAttachments(key, message) {
284
+ const state = this.conversations.get(key);
285
+ const buffer = state?.pendingAttachments;
286
+ if (!state || !buffer) {
287
+ return;
288
+ }
289
+ // Same-sender guard: only a follow-up from the same user absorbs the
290
+ // buffered attachments. A different sender leaves the buffer (and its timer)
291
+ // untouched so it still flushes on its own turn.
292
+ if (buffer.senderId !== message.senderId) {
293
+ return;
294
+ }
295
+ if (buffer.timer) {
296
+ clearTimeout(buffer.timer);
297
+ }
298
+ state.pendingAttachments = undefined;
299
+ // Always merge once we own the buffer — even if the window just lapsed
300
+ // before the flush timer fired. Dropping here would silently swallow the
301
+ // user's earlier attachment (it would be neither sent on its own nor merged).
302
+ message.attachments = [...buffer.attachments, ...message.attachments];
303
+ }
304
+ // Timer callback: the buffered attachment-only message timed out without a
305
+ // follow-up text, so process it on its own.
306
+ flushPendingAttachments(key, message) {
307
+ if (this.stopped) {
308
+ return;
309
+ }
310
+ const state = this.conversations.get(key);
311
+ if (!state?.pendingAttachments) {
312
+ return;
313
+ }
314
+ state.pendingAttachments = undefined;
315
+ const pending = state.pending
316
+ .catch(() => undefined)
317
+ .then(() => this.handleMessage(key, message))
318
+ .finally(() => this.markConversationIdle(key, pending));
319
+ state.pending = pending;
320
+ state.active = true;
321
+ state.lastActivityAt = this.now();
322
+ }
323
+ ensureConversation(key) {
324
+ let state = this.conversations.get(key);
325
+ if (!state) {
326
+ state = {
327
+ agentId: "",
328
+ pending: Promise.resolve(),
329
+ lastActivityAt: this.now(),
330
+ active: false,
331
+ };
332
+ this.conversations.set(key, state);
333
+ }
334
+ return state;
335
+ }
336
+ async handleMessage(key, message) {
337
+ const wsClient = this.wsClient;
338
+ if (!wsClient || this.stopped) {
339
+ return;
340
+ }
341
+ const streamId = generateReqId("stream");
342
+ let agentId = this.resolveConfiguredAgentId();
343
+ try {
344
+ await this.replyStream({
345
+ message,
346
+ streamId,
347
+ agentId,
348
+ content: formatProcessingReply(agentId),
349
+ finish: false,
350
+ });
351
+ if (!agentId) {
352
+ agentId = this.conversations.get(key)?.agentId ?? null;
353
+ }
354
+ if (!agentId || !this.agentManager.getAgent(agentId)) {
355
+ const agent = agentId
356
+ ? await this.loadConversationAgent(agentId)
357
+ : await this.createConversationAgent(message);
358
+ agentId = agent.id;
359
+ this.updateConversationAgent(key, agentId);
360
+ }
361
+ const prompt = await this.buildPromptInput(message);
362
+ this.agentManager.recordUserMessage(agentId, prompt.recordText, {
363
+ messageId: message.frame.body?.msgid,
364
+ emitState: false,
365
+ ...(prompt.images.length > 0 ? { images: prompt.images } : {}),
366
+ });
367
+ await this.streamAgentReply(agentId, message, streamId, prompt.input);
368
+ }
369
+ catch (error) {
370
+ this.logger.error({ err: error, agentId }, "Failed to process OpenClaw WeCom message");
371
+ await this.replyStream({
372
+ message,
373
+ streamId,
374
+ agentId,
375
+ content: `Seawork 处理失败:${formatError(error)}`,
376
+ finish: true,
377
+ fallbackToMarkdown: true,
378
+ });
379
+ }
380
+ }
381
+ async streamAgentReply(agentId, message, streamId, prompt) {
382
+ let assistantText = "";
383
+ let statusText = null;
384
+ let loadingTick = 0;
385
+ let lastSentContent = formatProcessingReply(agentId);
386
+ let lastSentAt = this.now();
387
+ const sendIntermediate = async (force = false) => {
388
+ const content = formatStreamingReply(agentId, assistantText, statusText, loadingTick);
389
+ if (!content || content === lastSentContent) {
390
+ return;
391
+ }
392
+ const now = this.now();
393
+ if (!force && now - lastSentAt < this.streamUpdateIntervalMs) {
394
+ return;
395
+ }
396
+ const sent = await this.replyStream({
397
+ message,
398
+ streamId,
399
+ agentId,
400
+ content,
401
+ finish: false,
402
+ });
403
+ if (sent) {
404
+ lastSentContent = content;
405
+ lastSentAt = now;
406
+ }
407
+ };
408
+ const sendStatus = async (nextStatus) => {
409
+ if (statusText === nextStatus) {
410
+ return;
411
+ }
412
+ statusText = nextStatus;
413
+ loadingTick = 0;
414
+ await sendIntermediate(true);
415
+ };
416
+ const sendLoadingTick = async () => {
417
+ if (assistantText.trim() && !statusText) {
418
+ return;
419
+ }
420
+ loadingTick += 1;
421
+ await sendIntermediate(true);
422
+ };
423
+ const sendFinal = async (content, withMedia = false) => {
424
+ const media = withMedia
425
+ ? this.extractMediaDirectives(content)
426
+ : { cleanedText: content, directives: [] };
427
+ // Media-only reply (agent emitted just MEDIA:/FILE: directives, so
428
+ // cleanedText is empty): send media FIRST, then derive an honest final
429
+ // text from the real send result — a placeholder only after media
430
+ // actually went out, otherwise a failure note. This avoids closing the
431
+ // stream with a blank bubble or a premature "media generated" claim.
432
+ if (!media.cleanedText && media.directives.length > 0) {
433
+ const result = await this.sendMediaDirectives(message, media.directives);
434
+ const finalText = result.sent > 0
435
+ ? "已生成媒体内容。"
436
+ : result.failures.length > 0
437
+ ? formatFailureSummary(result.failures)
438
+ : // Output disabled (no allowlist): media was stripped and nothing
439
+ // sent. Never close the stream with an empty bubble.
440
+ "本轮没有可显示的文本内容。";
441
+ await this.replyStream({
442
+ message,
443
+ streamId,
444
+ agentId,
445
+ content: finalText,
446
+ finish: true,
447
+ fallbackToMarkdown: true,
448
+ });
449
+ return;
450
+ }
451
+ // Reply has visible text: send it FIRST so a media failure never blocks
452
+ // the user from seeing the textual answer, then stream media on the side.
453
+ await this.replyStream({
454
+ message,
455
+ streamId,
456
+ agentId,
457
+ content: media.cleanedText,
458
+ finish: true,
459
+ fallbackToMarkdown: true,
460
+ });
461
+ const { failures } = await this.sendMediaDirectives(message, media.directives);
462
+ if (failures.length > 0) {
463
+ await this.sendMarkdownFallback(message.chatId, formatFailureSummary(failures));
464
+ }
465
+ };
466
+ const stream = this.agentManager.streamAgent(agentId, prompt);
467
+ const iterator = stream[Symbol.asyncIterator]();
468
+ let pendingNext = iterator.next();
469
+ try {
470
+ while (true) {
471
+ const next = await Promise.race([
472
+ pendingNext.then((result) => ({ type: "event", result })),
473
+ sleep(this.loadingTickIntervalMs).then(() => ({ type: "loading_tick" })),
474
+ ]);
475
+ if (next.type === "loading_tick") {
476
+ await sendLoadingTick();
477
+ continue;
478
+ }
479
+ const { value: event, done } = next.result;
480
+ if (done) {
481
+ break;
482
+ }
483
+ if (event.type === "timeline") {
484
+ if (event.item.type === "assistant_message") {
485
+ assistantText += event.item.text;
486
+ const hadStatus = statusText !== null;
487
+ statusText = null;
488
+ loadingTick = 0;
489
+ await sendIntermediate(hadStatus);
490
+ }
491
+ else {
492
+ const nextStatus = formatTimelineStatus(event.item);
493
+ if (nextStatus) {
494
+ await sendStatus(nextStatus);
495
+ }
496
+ }
497
+ }
498
+ else if (event.type === "turn_completed") {
499
+ await sendFinal(formatFinalReply(agentId, assistantText), true);
500
+ return;
501
+ }
502
+ else if (event.type === "turn_failed") {
503
+ await sendFinal(`Seawork 处理失败:${formatTurnFailedEvent(event)}`);
504
+ return;
505
+ }
506
+ else if (event.type === "turn_canceled") {
507
+ await sendFinal(formatTurnCanceledEvent(event));
508
+ return;
509
+ }
510
+ pendingNext = iterator.next();
511
+ }
512
+ }
513
+ finally {
514
+ await iterator.return?.(undefined);
515
+ }
516
+ await sendFinal(formatFinalReply(agentId, assistantText), true);
517
+ }
518
+ resolveConfiguredAgentId() {
519
+ if (this.config.agentMode !== "existing") {
520
+ return null;
521
+ }
522
+ const agentId = this.config.agentId?.trim();
523
+ if (!agentId) {
524
+ return null;
525
+ }
526
+ return agentId;
527
+ }
528
+ async loadConversationAgent(agentId) {
529
+ const existing = this.agentManager.getAgent(agentId);
530
+ if (existing) {
531
+ return existing;
532
+ }
533
+ if (this.loadAgent) {
534
+ return this.loadAgent(agentId);
535
+ }
536
+ throw new Error(`Configured WeCom agent is not loaded: ${agentId}`);
537
+ }
538
+ async createConversationAgent(message) {
539
+ const normalized = normalizeWeComAgentConfig({
540
+ provider: this.config.provider,
541
+ modeId: this.config.modeId,
542
+ model: this.config.model,
543
+ thinkingOptionId: this.config.thinkingOptionId,
544
+ });
545
+ const provider = normalized.provider;
546
+ const seaworkHome = this.config.seaworkHome ?? path.join(os.homedir(), ".seawork");
547
+ const cwd = resolveWeComOpenClawAgentCwd(this.config.cwd, seaworkHome);
548
+ if (cwd === defaultWeComOpenClawWorkspace()) {
549
+ await mkdir(cwd, { recursive: true });
550
+ }
551
+ return this.agentManager.createAgent({
552
+ provider,
553
+ cwd,
554
+ ...(normalized.modeId ? { modeId: normalized.modeId } : {}),
555
+ ...(normalized.model ? { model: normalized.model } : {}),
556
+ ...(normalized.thinkingOptionId ? { thinkingOptionId: normalized.thinkingOptionId } : {}),
557
+ title: `WeCom ${message.chatType}:${message.chatId}`,
558
+ systemPrompt: [
559
+ "你正在通过企业微信智能机器人接收用户请求。",
560
+ "这条链路复用 OpenClaw 官方企业微信智能机器人协议。",
561
+ "回复要适合直接发送到企业微信,优先使用简洁的中文 Markdown。",
562
+ ].join("\n"),
563
+ }, undefined, {
564
+ labels: {
565
+ "seawork.created-by": "wecom-openclaw",
566
+ "wecom.openclaw.chat-id": message.chatId,
567
+ "wecom.openclaw.chat-type": message.chatType,
568
+ "wecom.openclaw.sender-id": message.senderId,
569
+ },
570
+ });
571
+ }
572
+ maxInboundMediaBytes() {
573
+ return Math.max(0, this.config.maxInboundMediaBytes ?? DEFAULT_MAX_INBOUND_MEDIA_BYTES);
574
+ }
575
+ mediaDownloadTimeoutMs() {
576
+ return Math.max(0, this.config.mediaDownloadTimeoutMs ?? DEFAULT_MEDIA_DOWNLOAD_TIMEOUT_MS);
577
+ }
578
+ mediaAggregateWindowMs() {
579
+ return Math.max(0, this.config.mediaAggregateWindowMs ?? DEFAULT_MEDIA_AGGREGATE_WINDOW_MS);
580
+ }
581
+ mediaOutputAllowedRoots() {
582
+ return (this.config.mediaOutputAllowedRoots ?? [])
583
+ .map((root) => root.trim())
584
+ .filter(Boolean)
585
+ .map((root) => path.resolve(root));
586
+ }
587
+ // Build the agent prompt from inbound text + image attachments. Images are
588
+ // always downloaded so the timeline can persist and render inbound thumbnails.
589
+ // Only image-capable providers receive image content blocks; everything else
590
+ // (non-image files, unsupported providers, download failures) is summarized
591
+ // into the text so the agent still has context.
592
+ async buildPromptInput(message) {
593
+ const provider = this.config.provider ?? DEFAULT_AGENT_PROVIDER;
594
+ const supportsImages = IMAGE_CAPABLE_PROVIDERS.has(provider);
595
+ const notes = [];
596
+ const imageBlocks = [];
597
+ const recordedImages = [];
598
+ const images = message.attachments.filter((att) => att.kind === "image");
599
+ const files = message.attachments.filter((att) => att.kind === "file");
600
+ if (files.length > 0) {
601
+ notes.push(`(收到 ${files.length} 个文件附件,当前链路暂不解析文件内容,已忽略)`);
602
+ }
603
+ for (const image of images) {
604
+ const downloaded = await this.downloadImageBlock(image);
605
+ if (!downloaded.buffer) {
606
+ notes.push(`(一张图片${IMAGE_FAILURE_REASONS[downloaded.reason]},已忽略)`);
607
+ continue;
608
+ }
609
+ if (supportsImages) {
610
+ imageBlocks.push({
611
+ type: "image",
612
+ data: downloaded.buffer.toString("base64"),
613
+ mimeType: downloaded.mimeType,
614
+ });
615
+ }
616
+ // Persist for UI rendering even when the provider cannot consume image
617
+ // blocks. A persist failure only loses the thumbnail; the agent still gets
618
+ // either the structured image block or the text summary below.
619
+ const persisted = await this.persistInboundImage(downloaded.buffer, downloaded.mimeType, downloaded.filename);
620
+ if (persisted) {
621
+ recordedImages.push(persisted.metadata);
622
+ }
623
+ }
624
+ if (images.length > 0 && !supportsImages) {
625
+ notes.push(`(收到 ${images.length} 张图片,当前 provider「${provider}」不支持图片输入)`);
626
+ }
627
+ // The text recorded on the timeline and sent to the agent stays identical.
628
+ // Persisted image metadata is attached separately on the user_message, so no
629
+ // host file path ever needs to appear in the prompt text.
630
+ const agentText = [message.text, ...notes]
631
+ .filter((part) => part.trim())
632
+ .join("\n\n")
633
+ .trim();
634
+ const recordText = agentText || (images.length > 0 ? "[图片]" : "");
635
+ if (imageBlocks.length === 0) {
636
+ // Keep the original string path so providers without structured input and
637
+ // existing behavior are untouched.
638
+ return { input: agentText, recordText, images: recordedImages };
639
+ }
640
+ const blocks = [];
641
+ if (agentText) {
642
+ blocks.push({ type: "text", text: agentText });
643
+ }
644
+ blocks.push(...imageBlocks);
645
+ return { input: blocks, recordText, images: recordedImages };
646
+ }
647
+ async downloadImageBlock(attachment) {
648
+ const wsClient = this.wsClient;
649
+ if (!wsClient) {
650
+ return { buffer: null, reason: "error" };
651
+ }
652
+ try {
653
+ const timeoutMs = this.mediaDownloadTimeoutMs();
654
+ const download = wsClient.downloadFile(attachment.url, attachment.aesKey);
655
+ const result = timeoutMs > 0 ? await withTimeout(download, timeoutMs) : await download;
656
+ if (result.buffer.length > this.maxInboundMediaBytes()) {
657
+ this.logger.warn({ bytes: result.buffer.length, limit: this.maxInboundMediaBytes() }, "Dropped oversized OpenClaw WeCom inbound image");
658
+ return { buffer: null, reason: "oversize" };
659
+ }
660
+ return {
661
+ buffer: result.buffer,
662
+ mimeType: guessImageMimeType(result.filename),
663
+ filename: result.filename,
664
+ reason: null,
665
+ };
666
+ }
667
+ catch (error) {
668
+ const timedOut = error instanceof Error && error.message.includes("timed out");
669
+ this.logger.warn({ err: error }, "Failed to download OpenClaw WeCom inbound image");
670
+ return { buffer: null, reason: timedOut ? "timeout" : "error" };
671
+ }
672
+ }
673
+ // Persist an inbound image into the desktop-managed attachment dir so the
674
+ // desktop UI can render it. The desktop main process only serves previews for
675
+ // files under `$SEAWORK_HOME/desktop-attachments/`. The metadata's storageKey
676
+ // is the bare filename (opaque id) — NOT the absolute path — so the daemon's
677
+ // host filesystem layout never leaks to timeline subscribers; the desktop
678
+ // resolves the filename against its own managed dir. Returns null on write
679
+ // failure — the agent still receives the image block regardless.
680
+ async persistInboundImage(buffer, mimeType, filename) {
681
+ const seaworkHome = this.config.seaworkHome ?? path.join(os.homedir(), ".seawork");
682
+ const dir = path.join(seaworkHome, "desktop-attachments");
683
+ const id = randomUUID();
684
+ const ext = mimeExtension(mimeType);
685
+ const storedName = `${id}${ext}`;
686
+ const filePath = path.join(dir, storedName);
687
+ try {
688
+ await mkdir(dir, { recursive: true });
689
+ await writeFile(filePath, buffer);
690
+ }
691
+ catch (error) {
692
+ this.logger.warn({ err: error }, "Failed to persist OpenClaw WeCom inbound image");
693
+ return null;
694
+ }
695
+ return {
696
+ metadata: {
697
+ id,
698
+ mimeType,
699
+ storageType: "desktop-file",
700
+ // Opaque id (filename only) — desktop resolves it locally.
701
+ storageKey: storedName,
702
+ fileName: filename ?? null,
703
+ byteSize: buffer.length,
704
+ createdAt: this.now(),
705
+ },
706
+ };
707
+ }
708
+ extractMediaDirectives(text) {
709
+ const allowedRoots = this.mediaOutputAllowedRoots();
710
+ const directives = [];
711
+ const keptLines = [];
712
+ for (const line of text.split("\n")) {
713
+ const match = MEDIA_DIRECTIVE_PATTERN.exec(line.trim());
714
+ if (!match) {
715
+ keptLines.push(line);
716
+ continue;
717
+ }
718
+ // Always strip directive lines — even when output is disabled — so a raw
719
+ // host path can never leak into the visible WeCom reply.
720
+ const directive = match[1];
721
+ const filePath = match[2].trim();
722
+ // Only accept absolute paths: a relative path would resolve against the
723
+ // daemon's (unstable) cwd, making allowlist matching nondeterministic.
724
+ const allowed = allowedRoots.length > 0 &&
725
+ path.isAbsolute(filePath) &&
726
+ isPathWithinRoots(filePath, allowedRoots);
727
+ directives.push({
728
+ kind: directive === "MEDIA" ? "image" : "file",
729
+ path: filePath,
730
+ allowed,
731
+ });
732
+ }
733
+ return { cleanedText: keptLines.join("\n").trim(), directives };
734
+ }
735
+ // Upload + reply each allowed media directive. Returns human-readable failure
736
+ // summaries (path not allowlisted, file missing, upload error). When output is
737
+ // globally disabled (no allowlist configured), directives are silently
738
+ // ignored — the lines were already stripped from the visible text.
739
+ async sendMediaDirectives(message, directives) {
740
+ const allowedRoots = this.mediaOutputAllowedRoots();
741
+ if (allowedRoots.length === 0) {
742
+ return { sent: 0, failures: [] };
743
+ }
744
+ const failures = [];
745
+ let sent = 0;
746
+ const wsClient = this.wsClient;
747
+ for (const directive of directives) {
748
+ // Only ever expose the basename to the remote user; the full host path is
749
+ // kept to the server log to avoid leaking the local filesystem layout.
750
+ const name = path.basename(directive.path);
751
+ if (!directive.allowed) {
752
+ failures.push(`${name}(路径不在允许的目录内,已拒绝)`);
753
+ continue;
754
+ }
755
+ // Resolve symlinks before the final boundary check: a symlink living
756
+ // inside an allowlisted root must not be able to point at a file outside
757
+ // it. The sync resolve()/relative() check above can't see through links.
758
+ const realPath = await this.resolveAllowedRealPath(directive.path, allowedRoots);
759
+ if (!realPath) {
760
+ failures.push(`${name}(路径不在允许的目录内,已拒绝)`);
761
+ continue;
762
+ }
763
+ if (!wsClient?.isConnected) {
764
+ failures.push(`${name}(连接已断开,未发送)`);
765
+ continue;
766
+ }
767
+ try {
768
+ const buffer = await readFile(realPath);
769
+ const mediaType = directive.kind;
770
+ const uploaded = await wsClient.uploadMedia(buffer, {
771
+ type: mediaType,
772
+ filename: name,
773
+ });
774
+ await wsClient.replyMedia(message.frame, mediaType, uploaded.media_id);
775
+ sent += 1;
776
+ }
777
+ catch (error) {
778
+ this.logger.warn({ err: error, path: directive.path }, "Failed to send OpenClaw WeCom output media");
779
+ failures.push(`${name}(发送失败)`);
780
+ }
781
+ }
782
+ return { sent, failures };
783
+ }
784
+ // Return the symlink-resolved real path if it still lives inside an
785
+ // allowlisted root (also symlink-resolved), else null. Guards against a
786
+ // symlink inside an allowed dir escaping to an arbitrary host file.
787
+ async resolveAllowedRealPath(filePath, allowedRoots) {
788
+ let realFile;
789
+ try {
790
+ realFile = await realpath(filePath);
791
+ }
792
+ catch {
793
+ return null;
794
+ }
795
+ for (const root of allowedRoots) {
796
+ let realRoot;
797
+ try {
798
+ realRoot = await realpath(root);
799
+ }
800
+ catch {
801
+ continue;
802
+ }
803
+ if (isPathWithinRoots(realFile, [realRoot])) {
804
+ return realFile;
805
+ }
806
+ }
807
+ return null;
808
+ }
809
+ async sendMarkdown(chatId, content) {
810
+ const wsClient = this.wsClient;
811
+ if (!wsClient?.isConnected) {
812
+ throw new Error("OpenClaw WeCom WebSocket is not connected");
813
+ }
814
+ for (const chunk of splitByUtf8Bytes(content, MAX_MARKDOWN_BYTES)) {
815
+ await wsClient.sendMessage(chatId, {
816
+ msgtype: "markdown",
817
+ markdown: { content: chunk },
818
+ });
819
+ }
820
+ }
821
+ async replyStream(options) {
822
+ const wsClient = this.wsClient;
823
+ const content = truncateByUtf8Bytes(options.content, MAX_STREAM_BYTES, STREAM_TRUNCATION_SUFFIX);
824
+ if (!wsClient?.isConnected) {
825
+ const error = new Error("OpenClaw WeCom WebSocket is not connected");
826
+ this.logger.warn({
827
+ err: error,
828
+ agentId: options.agentId,
829
+ chatId: options.message.chatId,
830
+ streamId: options.streamId,
831
+ finish: options.finish,
832
+ }, "Failed to send OpenClaw WeCom stream reply");
833
+ if (options.finish && options.fallbackToMarkdown) {
834
+ await this.sendMarkdownFallback(options.message.chatId, content);
835
+ }
836
+ return false;
837
+ }
838
+ try {
839
+ this.logger.debug({
840
+ agentId: options.agentId,
841
+ chatId: options.message.chatId,
842
+ streamId: options.streamId,
843
+ finish: options.finish,
844
+ msgId: options.message.frame.body?.msgid,
845
+ elapsedSinceMessageMs: this.now() - options.message.receivedAt,
846
+ contentBytes: Buffer.byteLength(content, "utf8"),
847
+ }, "Sending OpenClaw WeCom stream reply");
848
+ await wsClient.replyStream(options.message.frame, options.streamId, content, options.finish);
849
+ return true;
850
+ }
851
+ catch (error) {
852
+ this.logger.warn({
853
+ err: error,
854
+ agentId: options.agentId,
855
+ chatId: options.message.chatId,
856
+ streamId: options.streamId,
857
+ finish: options.finish,
858
+ }, "Failed to send OpenClaw WeCom stream reply");
859
+ if (options.finish && options.fallbackToMarkdown) {
860
+ await this.sendMarkdownFallback(options.message.chatId, content);
861
+ }
862
+ return false;
863
+ }
864
+ }
865
+ async sendMarkdownFallback(chatId, content) {
866
+ try {
867
+ await this.sendMarkdown(chatId, content);
868
+ }
869
+ catch (error) {
870
+ this.logger.warn({ err: error, chatId }, "Failed to send OpenClaw WeCom markdown fallback");
871
+ }
872
+ }
873
+ updateConversationAgent(key, agentId) {
874
+ const state = this.conversations.get(key);
875
+ if (!state) {
876
+ return;
877
+ }
878
+ state.agentId = agentId;
879
+ state.lastActivityAt = this.now();
880
+ }
881
+ markConversationIdle(key, pending) {
882
+ const state = this.conversations.get(key);
883
+ if (!state || state.pending !== pending) {
884
+ return;
885
+ }
886
+ if (this.config.agentMode === "existing") {
887
+ this.conversations.delete(key);
888
+ return;
889
+ }
890
+ state.active = false;
891
+ state.lastActivityAt = this.now();
892
+ this.pruneConversations(state.lastActivityAt);
893
+ }
894
+ pruneConversations(now = this.now()) {
895
+ for (const [key, state] of this.conversations) {
896
+ if (!state.active && now - state.lastActivityAt >= this.conversationIdleTtlMs) {
897
+ this.conversations.delete(key);
898
+ }
899
+ }
900
+ while (this.conversations.size > this.maxConversations) {
901
+ if (!this.evictOldestIdleConversation()) {
902
+ break;
903
+ }
904
+ }
905
+ }
906
+ evictOldestIdleConversation() {
907
+ let oldestKey = null;
908
+ let oldestActivity = Number.POSITIVE_INFINITY;
909
+ for (const [key, state] of this.conversations) {
910
+ if (state.active || state.lastActivityAt >= oldestActivity) {
911
+ continue;
912
+ }
913
+ oldestKey = key;
914
+ oldestActivity = state.lastActivityAt;
915
+ }
916
+ if (!oldestKey) {
917
+ return false;
918
+ }
919
+ this.conversations.delete(oldestKey);
920
+ return true;
921
+ }
922
+ }
923
+ const IMAGE_FAILURE_REASONS = {
924
+ oversize: "超过大小上限",
925
+ timeout: "下载超时",
926
+ error: "下载失败",
927
+ };
928
+ function extractText(body) {
929
+ const parts = [];
930
+ if (body.msgtype === MessageType.Text && body.text?.content) {
931
+ parts.push(body.text.content);
932
+ }
933
+ else if (body.msgtype === MessageType.Voice && body.voice?.content) {
934
+ parts.push(body.voice.content);
935
+ }
936
+ else if (body.msgtype === MessageType.Mixed) {
937
+ parts.push(body.mixed.msg_item
938
+ .flatMap((item) => item.msgtype === "text" && item.text?.content ? [item.text.content] : [])
939
+ .join("\n"));
940
+ }
941
+ const quoteText = extractQuoteText(body);
942
+ if (quoteText) {
943
+ parts.push(quoteText);
944
+ }
945
+ return parts.filter(Boolean).join("\n");
946
+ }
947
+ function extractQuoteText(body) {
948
+ const quote = body.quote;
949
+ if (!quote) {
950
+ return "";
951
+ }
952
+ if (quote.msgtype === "text" && quote.text?.content) {
953
+ return `引用:${quote.text.content}`;
954
+ }
955
+ if (quote.msgtype === "voice" && quote.voice?.content) {
956
+ return `引用:${quote.voice.content}`;
957
+ }
958
+ if (quote.msgtype === "mixed" && quote.mixed?.msg_item) {
959
+ const text = quote.mixed.msg_item
960
+ .flatMap((item) => (item.msgtype === "text" && item.text?.content ? [item.text.content] : []))
961
+ .join("\n");
962
+ return text ? `引用:${text}` : "";
963
+ }
964
+ return "";
965
+ }
966
+ function extractAttachments(body) {
967
+ const attachments = [];
968
+ const pushImage = (image) => {
969
+ if (image?.url) {
970
+ attachments.push({ kind: "image", url: image.url, aesKey: image.aeskey });
971
+ }
972
+ };
973
+ const pushFile = (file) => {
974
+ if (file?.url) {
975
+ attachments.push({ kind: "file", url: file.url, aesKey: file.aeskey });
976
+ }
977
+ };
978
+ // Mixed items are typed as text/image only, but the wire allows extra fields
979
+ // (BaseMessage is `[key: string]: any`), so read a `file` item defensively to
980
+ // avoid silently dropping file attachments embedded in mixed messages.
981
+ const collectMixedItems = (items) => {
982
+ for (const item of items) {
983
+ if (item.msgtype === "image") {
984
+ pushImage(item.image);
985
+ }
986
+ else if (item.msgtype === "file") {
987
+ pushFile(item.file);
988
+ }
989
+ }
990
+ };
991
+ if (body.msgtype === MessageType.Image) {
992
+ pushImage(body.image);
993
+ }
994
+ else if (body.msgtype === MessageType.File) {
995
+ pushFile(body.file);
996
+ }
997
+ else if (body.msgtype === MessageType.Mixed) {
998
+ collectMixedItems(body.mixed.msg_item);
999
+ }
1000
+ const quote = body.quote;
1001
+ if (quote?.msgtype === "image") {
1002
+ pushImage(quote.image);
1003
+ }
1004
+ else if (quote?.msgtype === "file") {
1005
+ pushFile(quote.file);
1006
+ }
1007
+ else if (quote?.msgtype === "mixed" && quote.mixed?.msg_item) {
1008
+ collectMixedItems(quote.mixed.msg_item);
1009
+ }
1010
+ return attachments;
1011
+ }
1012
+ function guessImageMimeType(filename) {
1013
+ const ext = filename ? path.extname(filename).toLowerCase() : "";
1014
+ switch (ext) {
1015
+ case ".png":
1016
+ return "image/png";
1017
+ case ".gif":
1018
+ return "image/gif";
1019
+ case ".webp":
1020
+ return "image/webp";
1021
+ case ".bmp":
1022
+ return "image/bmp";
1023
+ default:
1024
+ return "image/jpeg";
1025
+ }
1026
+ }
1027
+ function mimeExtension(mimeType) {
1028
+ switch (mimeType) {
1029
+ case "image/png":
1030
+ return ".png";
1031
+ case "image/gif":
1032
+ return ".gif";
1033
+ case "image/webp":
1034
+ return ".webp";
1035
+ case "image/bmp":
1036
+ return ".bmp";
1037
+ default:
1038
+ return ".jpg";
1039
+ }
1040
+ }
1041
+ function isPathWithinRoots(filePath, roots) {
1042
+ const resolved = path.resolve(filePath);
1043
+ return roots.some((root) => {
1044
+ const relative = path.relative(root, resolved);
1045
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
1046
+ });
1047
+ }
1048
+ function withTimeout(promise, ms) {
1049
+ return new Promise((resolve, reject) => {
1050
+ const timer = setTimeout(() => reject(new Error(`download timed out after ${ms}ms`)), ms);
1051
+ timer.unref?.();
1052
+ promise.then((value) => {
1053
+ clearTimeout(timer);
1054
+ resolve(value);
1055
+ }, (error) => {
1056
+ clearTimeout(timer);
1057
+ reject(error);
1058
+ });
1059
+ });
1060
+ }
1061
+ function formatFailureSummary(failures) {
1062
+ return ["部分媒体未能发送:", ...failures.map((item) => `- ${item}`)].join("\n");
1063
+ }
1064
+ function normalizeAllowlist(values) {
1065
+ return new Set((values ?? []).map((value) => value.trim()).filter(Boolean));
1066
+ }
1067
+ function mergeAllowlist(current, incoming) {
1068
+ return [
1069
+ ...new Set([...(current ?? []), ...(incoming ?? [])].map((value) => value.trim()).filter(Boolean)),
1070
+ ];
1071
+ }
1072
+ function hasExplicitAllowlist(config) {
1073
+ return (normalizeAllowlist(config.allowUsers).size > 0 || normalizeAllowlist(config.allowChats).size > 0);
1074
+ }
1075
+ function normalizeWeComAgentConfig(input) {
1076
+ const provider = isValidAgentProvider(input.provider ?? "")
1077
+ ? input.provider
1078
+ : DEFAULT_AGENT_PROVIDER;
1079
+ const definition = getAgentProviderDefinition(provider);
1080
+ const validModeIds = new Set(definition.modes.map((mode) => mode.id));
1081
+ const requestedModeId = input.modeId?.trim();
1082
+ const modeId = requestedModeId && validModeIds.has(requestedModeId)
1083
+ ? requestedModeId
1084
+ : (definition.defaultModeId ?? undefined);
1085
+ const model = input.model?.trim() || definition.defaultModel;
1086
+ const thinkingOptionId = normalizeThinkingOptionIdForProvider(provider, input.thinkingOptionId);
1087
+ return {
1088
+ provider,
1089
+ ...(modeId ? { modeId } : {}),
1090
+ ...(model ? { model } : {}),
1091
+ ...(thinkingOptionId ? { thinkingOptionId } : {}),
1092
+ };
1093
+ }
1094
+ function normalizeThinkingOptionIdForProvider(provider, thinkingOptionId) {
1095
+ const normalized = thinkingOptionId?.trim();
1096
+ if (!normalized || normalized === "default") {
1097
+ return undefined;
1098
+ }
1099
+ if (provider === "codex") {
1100
+ return ["minimal", "low", "medium", "high", "xhigh"].includes(normalized)
1101
+ ? normalized
1102
+ : undefined;
1103
+ }
1104
+ if (provider === "claude") {
1105
+ return ["low", "medium", "high", "max"].includes(normalized) ? normalized : undefined;
1106
+ }
1107
+ return undefined;
1108
+ }
1109
+ function buildConnectionKey(config) {
1110
+ return JSON.stringify({
1111
+ enabled: config.enabled,
1112
+ botId: config.botId ?? null,
1113
+ secret: config.secret ?? null,
1114
+ websocketUrl: config.websocketUrl ?? null,
1115
+ pluginVersion: config.pluginVersion ?? null,
1116
+ });
1117
+ }
1118
+ function splitByUtf8Bytes(text, maxBytes) {
1119
+ const chunks = [];
1120
+ let current = "";
1121
+ let currentBytes = 0;
1122
+ for (const char of text) {
1123
+ const charBytes = Buffer.byteLength(char, "utf8");
1124
+ if (current && currentBytes + charBytes > maxBytes) {
1125
+ chunks.push(current);
1126
+ current = "";
1127
+ currentBytes = 0;
1128
+ }
1129
+ current += char;
1130
+ currentBytes += charBytes;
1131
+ }
1132
+ if (current) {
1133
+ chunks.push(current);
1134
+ }
1135
+ return chunks;
1136
+ }
1137
+ function truncateByUtf8Bytes(text, maxBytes, suffix) {
1138
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) {
1139
+ return text;
1140
+ }
1141
+ const suffixBytes = Buffer.byteLength(suffix, "utf8");
1142
+ if (suffixBytes >= maxBytes) {
1143
+ return splitByUtf8Bytes(suffix, maxBytes)[0] ?? "";
1144
+ }
1145
+ const targetBytes = maxBytes - suffixBytes;
1146
+ let result = "";
1147
+ let resultBytes = 0;
1148
+ for (const char of text) {
1149
+ const charBytes = Buffer.byteLength(char, "utf8");
1150
+ if (resultBytes + charBytes > targetBytes) {
1151
+ break;
1152
+ }
1153
+ result += char;
1154
+ resultBytes += charBytes;
1155
+ }
1156
+ return `${result}${suffix}`;
1157
+ }
1158
+ function formatProcessingReply(agentId, loadingTick = 0) {
1159
+ const loadingText = formatLoadingText("已收到,Seawork 正在处理", loadingTick);
1160
+ return agentId ? `${loadingText}\n\nAgent: ${agentId}` : loadingText;
1161
+ }
1162
+ function formatStreamingReply(agentId, assistantText, statusText, loadingTick) {
1163
+ const content = assistantText.trim();
1164
+ const status = statusText?.trim();
1165
+ if (!status) {
1166
+ return content || formatProcessingReply(agentId, loadingTick);
1167
+ }
1168
+ const loadingStatus = formatLoadingText(status, loadingTick);
1169
+ if (!content) {
1170
+ return `${formatProcessingReply(agentId, loadingTick)}\n\n_${loadingStatus}_`;
1171
+ }
1172
+ return `${content}\n\n_${loadingStatus}_`;
1173
+ }
1174
+ function formatFinalReply(agentId, text) {
1175
+ const content = text.trim();
1176
+ return content || `Agent ${agentId} 已完成本轮处理,但没有返回文本。`;
1177
+ }
1178
+ function formatTimelineStatus(item) {
1179
+ if (item.type === "reasoning") {
1180
+ return "正在推理";
1181
+ }
1182
+ if (item.type === "tool_call") {
1183
+ if (item.status === "running") {
1184
+ return "正在执行工具";
1185
+ }
1186
+ if (item.status === "completed") {
1187
+ return "工具执行完成,继续处理";
1188
+ }
1189
+ if (item.status === "failed") {
1190
+ return "工具执行失败,继续处理";
1191
+ }
1192
+ return "工具执行已取消,继续处理";
1193
+ }
1194
+ if (item.type === "compaction") {
1195
+ return item.status === "loading" ? "正在整理上下文" : "上下文整理完成,继续处理";
1196
+ }
1197
+ return null;
1198
+ }
1199
+ function formatLoadingText(text, loadingTick) {
1200
+ const suffix = LOADING_ELLIPSIS_FRAMES[loadingTick % LOADING_ELLIPSIS_FRAMES.length];
1201
+ return `${text}${suffix}`;
1202
+ }
1203
+ function formatTurnFailedEvent(event) {
1204
+ const base = event.error.trim();
1205
+ const parts = [base.length > 0 ? base : "Provider run failed"];
1206
+ const code = event.code?.trim();
1207
+ if (code) {
1208
+ parts.push(`code: ${code}`);
1209
+ }
1210
+ const diagnostic = event.diagnostic?.trim();
1211
+ if (diagnostic && diagnostic !== base) {
1212
+ parts.push(diagnostic);
1213
+ }
1214
+ return parts.join("\n\n");
1215
+ }
1216
+ function formatTurnCanceledEvent(event) {
1217
+ const reason = event.reason.trim();
1218
+ return reason ? `Seawork 已取消本轮处理:${reason}` : "Seawork 已取消本轮处理。";
1219
+ }
1220
+ function formatError(error) {
1221
+ if (error instanceof Error) {
1222
+ return error.message;
1223
+ }
1224
+ return String(error);
1225
+ }
1226
+ function sleep(ms) {
1227
+ return new Promise((resolve) => setTimeout(resolve, ms));
1228
+ }
1229
+ //# sourceMappingURL=bridge.js.map