@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
@@ -1,5 +1,6 @@
1
1
  import { exec, execFile } from "node:child_process";
2
2
  import { homedir, tmpdir } from "node:os";
3
+ import { TTLCache } from "@isaacs/ttlcache";
3
4
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4
5
  import { experimental_createMCPClient } from "ai";
5
6
  import equal from "fast-deep-equal";
@@ -10,11 +11,10 @@ import { v4 as uuidv4 } from "uuid";
10
11
  import { z } from "zod";
11
12
  import { decodeTerminalResizePayload, encodeTerminalSnapshotPayload, encodeTerminalStreamFrame, TerminalStreamOpcode, } from "../shared/terminal-stream-protocol.js";
12
13
  import { captureTerminalLines } from "../terminal/terminal.js";
13
- import { commitChanges, createPullRequest, getCheckoutDiff, getCheckoutStatus, listBranchSuggestions, mergeFromBase, mergeToBase, pullCurrentBranch, pushCurrentBranch, } from "../utils/checkout-git.js";
14
+ import { commitChanges, createPullRequest, getCachedCheckoutShortstat, getCheckoutDiff, getCheckoutStatus, hasWorkingTreeChanges, invalidateCheckoutShortstatCache, listBranchSuggestions, mergeFromBase, mergeToBase, pullCurrentBranch, pushCurrentBranch, warmCheckoutShortstatInBackground, } from "../utils/checkout-git.js";
14
15
  import { searchHomeDirectories, searchWorkspaceEntries } from "../utils/directory-suggestions.js";
15
16
  import { expandTilde } from "../utils/path.js";
16
17
  import { getProjectIcon } from "../utils/project-icon.js";
17
- import { latencyTracker } from "./latency-tracker.js";
18
18
  import { scheduleAgentMetadataGeneration } from "./agent/agent-metadata-generator.js";
19
19
  import { resolveEffectiveThinkingOptionId, toAgentPayload } from "./agent/agent-projections.js";
20
20
  import { DEFAULT_STRUCTURED_GENERATION_PROVIDERS, generateStructuredAgentResponseWithFallback, StructuredAgentFallbackError, StructuredAgentResponseError, } from "./agent/agent-response-loop.js";
@@ -33,22 +33,29 @@ import { notifyChatMentions } from "./chat/chat-mentions.js";
33
33
  import { ChatServiceError } from "./chat/chat-service.js";
34
34
  import { READ_ONLY_GIT_ENV, toCheckoutError } from "./checkout-git-utils.js";
35
35
  import { resolveClientMessageId } from "./client-message-id.js";
36
- import { fetchGitForgeIssues } from "./git-forge/service.js";
37
- import { redactGitForgeTokens, rehydratePatchTokens } from "./git-forge/token-mask.js";
38
36
  import { DictationStreamManager, } from "./dictation/dictation-stream-manager.js";
39
37
  import { listAvailableEditorTargets, openInEditorTarget } from "./editor-targets.js";
40
38
  import { createDirectoryEntry, createFileEntry, getDownloadableFileInfo, listDirectoryEntries, readExplorerFile, } from "./file-explorer/service.js";
41
- import { WorkspaceIgnoreStore } from "./workspace-ignore/store.js";
42
39
  import { handleGetPublicIssue, handleListIssueComments, handleListPublicIssues, handlePostIssueComment, handleToggleIssueReaction, } from "./forum-handler.js";
40
+ import { discoverGitForgeIssueWork, fetchGitForgeIssues, transitionGitForgeIssueWork, } from "./git-forge/service.js";
41
+ import { redactGitForgeTokens, rehydratePatchTokens } from "./git-forge/token-mask.js";
42
+ import { fetchWeComOpenClawQrCode, queryWeComOpenClawQrResult, } from "./integrations/wecom-openclaw/qr.js";
43
+ import { resolveWeComOpenClawAgentCwd } from "./integrations/wecom-openclaw/workspace.js";
44
+ import { latencyTracker } from "./latency-tracker.js";
45
+ import { handleHubTestRunCancel, handleHubTestRunStart } from "./library/hub-test-run.js";
46
+ import { filterLibrarySkillCommands } from "./library/library-injection.js";
47
+ import { handleLibraryRequest } from "./library/library-rpc.js";
43
48
  import { handleListMyIssues } from "./list-my-issues-handler.js";
44
- import { isLegacyEditorTargetId, serializeAgentStreamEvent, } from "./messages.js";
49
+ import { isLegacyEditorTargetId, PROVIDER_ENV_VALUE_REDACTED, serializeAgentStreamEvent, } from "./messages.js";
45
50
  import { buildConfigOverrides, buildSessionConfig, extractTimestamps, } from "./persistence-hooks.js";
51
+ import { redactProviderEnv, rehydratePatchProviderEnv } from "./provider-runtime-settings-mask.js";
46
52
  import { toResolver } from "./speech/provider-resolver.js";
47
53
  import { createVoiceTurnController, } from "./voice/voice-turn-controller.js";
48
54
  import { buildVoiceModeSystemPrompt, stripVoiceModeSystemPrompt, wrapSpokenInput, } from "./voice-config.js";
49
55
  import { isVoicePermissionAllowed } from "./voice-permission-policy.js";
56
+ import { WorkspaceIgnoreStore } from "./workspace-ignore/store.js";
50
57
  import { createPersistedProjectRecord, createPersistedWorkspaceRecord, } from "./workspace-registry.js";
51
- import { buildProjectPlacementForCwd, deriveProjectKind, deriveProjectRootPath, deriveWorkspaceDisplayName, deriveWorkspaceId, deriveWorkspaceKind, detectStaleWorkspaces, normalizeWorkspaceId as normalizePersistedWorkspaceId, } from "./workspace-registry-model.js";
58
+ import { buildProjectPlacementForCwd, deriveProjectKind, deriveProjectRootPath, deriveWorkspaceDisplayName, deriveWorkspaceId, deriveWorkspaceKind, detectStaleWorkspaces, normalizeWorkspaceId as normalizePersistedWorkspaceId, normalizeWorkspaceLookupKey, } from "./workspace-registry-model.js";
52
59
  import { runAsyncWorktreeBootstrap } from "./worktree-bootstrap.js";
53
60
  import { assertSafeGitRef as assertWorktreeSafeGitRef, buildAgentSessionConfig as buildWorktreeAgentSessionConfig, createSeaworkWorktreeInBackground as createWorktreeInBackgroundSession, handleCreateSeaworkWorktreeRequest as handleCreateWorktreeRequest, handleSeaworkWorktreeArchiveRequest as handleWorktreeArchiveRequest, handleSeaworkWorktreeListRequest as handleWorktreeListRequest, killTerminalsUnderPath as killWorktreeTerminalsUnderPath, registerPendingWorktreeWorkspace as registerPendingWorktreeWorkspaceSession, } from "./worktree-session.js";
54
61
  const execAsync = promisify(exec);
@@ -84,11 +91,30 @@ function clientSupportsAllProviders(appVersion) {
84
91
  function clientSupportsFlexibleEditorIds(appVersion) {
85
92
  return isAppVersionAtLeast(appVersion, MIN_VERSION_FLEXIBLE_EDITOR_IDS);
86
93
  }
94
+ function clientSupportsTimelineErrorItems(appVersion) {
95
+ return isAppVersionAtLeast(appVersion, "1.0.34");
96
+ }
97
+ function clientSupportsTimelineReconnectingItems(appVersion) {
98
+ return isAppVersionAtLeast(appVersion, "1.0.39");
99
+ }
100
+ function legacyTimelineProjectionOptions(appVersion) {
101
+ return {
102
+ legacySystemErrorsAsAssistantMessage: !clientSupportsTimelineErrorItems(appVersion),
103
+ legacyReconnectingAsReasoning: !clientSupportsTimelineReconnectingItems(appVersion),
104
+ };
105
+ }
87
106
  const MAX_TERMINAL_STREAM_SLOTS = 256;
88
107
  // Reuse the cached PR snapshot for this long before re-shelling out to `gh`.
89
108
  // Background watchers + periodic fetches keep it warm; the client polls every
90
109
  // 15s, so this dedupes overlapping panels without delaying real changes.
91
110
  const CHECKOUT_PR_STATUS_CACHE_TTL_MS = 10000;
111
+ const AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS = 60000;
112
+ const AVAILABLE_EDITOR_TARGETS_CACHE_KEY = "available";
113
+ // Coalesces repeated buildProjectPlacement calls (each spawns 3+ git children)
114
+ // across concurrent fetch_agents / fetch_workspaces requests. Slow disks or
115
+ // missing-external-drive cwds previously caused tens of seconds of stacked git
116
+ // invocations per cwd; see issue #635 / #770.
117
+ const PROJECT_PLACEMENT_CACHE_TTL_MS = 5000;
92
118
  function computeCheckoutPrAgeMs(snap) {
93
119
  const t = snap?.github.refreshedAt;
94
120
  if (!t)
@@ -232,6 +258,164 @@ function toAgentPersistenceHandle(logger, handle) {
232
258
  metadata: handle.metadata,
233
259
  };
234
260
  }
261
+ const OFFLINE_TIMELINE_EPOCH = "offline-persisted";
262
+ const DEFAULT_OFFLINE_TIMELINE_FETCH_LIMIT = 200;
263
+ function buildOfflineTimelineRows(items, timestamp) {
264
+ return items.map((item, index) => ({
265
+ seq: index + 1,
266
+ timestamp,
267
+ item,
268
+ }));
269
+ }
270
+ function selectOfflineTimelineWindow(params) {
271
+ const rows = buildOfflineTimelineRows(params.items, params.timestamp);
272
+ const direction = params.direction;
273
+ const limit = params.limit === undefined
274
+ ? DEFAULT_OFFLINE_TIMELINE_FETCH_LIMIT
275
+ : Math.max(0, Math.floor(params.limit));
276
+ const minSeq = rows.length ? rows[0].seq : 0;
277
+ const maxSeq = rows.length ? rows[rows.length - 1].seq : 0;
278
+ const nextSeq = maxSeq + 1;
279
+ const window = { minSeq, maxSeq, nextSeq };
280
+ const cloneRows = (selected) => selected.map((row) => ({ ...row }));
281
+ const selectAll = limit === 0;
282
+ if (params.cursor && params.cursor.epoch !== OFFLINE_TIMELINE_EPOCH) {
283
+ return {
284
+ agentProvider: params.agentProvider,
285
+ epoch: OFFLINE_TIMELINE_EPOCH,
286
+ reset: true,
287
+ staleCursor: true,
288
+ gap: false,
289
+ window,
290
+ hasOlder: false,
291
+ hasNewer: false,
292
+ rows: cloneRows(rows),
293
+ };
294
+ }
295
+ if (direction === "after" && params.cursor && rows.length > 0 && params.cursor.seq < minSeq - 1) {
296
+ return {
297
+ agentProvider: params.agentProvider,
298
+ epoch: OFFLINE_TIMELINE_EPOCH,
299
+ reset: true,
300
+ staleCursor: false,
301
+ gap: true,
302
+ window,
303
+ hasOlder: false,
304
+ hasNewer: false,
305
+ rows: cloneRows(rows),
306
+ };
307
+ }
308
+ if (rows.length === 0) {
309
+ return {
310
+ agentProvider: params.agentProvider,
311
+ epoch: OFFLINE_TIMELINE_EPOCH,
312
+ reset: false,
313
+ staleCursor: false,
314
+ gap: false,
315
+ window,
316
+ hasOlder: false,
317
+ hasNewer: false,
318
+ rows: [],
319
+ };
320
+ }
321
+ if (direction === "tail") {
322
+ const selected = selectAll || limit >= rows.length ? rows : rows.slice(rows.length - limit);
323
+ return {
324
+ agentProvider: params.agentProvider,
325
+ epoch: OFFLINE_TIMELINE_EPOCH,
326
+ reset: false,
327
+ staleCursor: false,
328
+ gap: false,
329
+ window,
330
+ hasOlder: selected.length > 0 && selected[0].seq > minSeq,
331
+ hasNewer: false,
332
+ rows: cloneRows(selected),
333
+ };
334
+ }
335
+ if (direction === "after") {
336
+ const baseSeq = params.cursor?.seq ?? 0;
337
+ const startIdx = rows.findIndex((row) => row.seq > baseSeq);
338
+ if (startIdx < 0) {
339
+ return {
340
+ agentProvider: params.agentProvider,
341
+ epoch: OFFLINE_TIMELINE_EPOCH,
342
+ reset: false,
343
+ staleCursor: false,
344
+ gap: false,
345
+ window,
346
+ hasOlder: baseSeq >= minSeq,
347
+ hasNewer: false,
348
+ rows: [],
349
+ };
350
+ }
351
+ const selected = selectAll ? rows.slice(startIdx) : rows.slice(startIdx, startIdx + limit);
352
+ const lastSelected = selected[selected.length - 1];
353
+ return {
354
+ agentProvider: params.agentProvider,
355
+ epoch: OFFLINE_TIMELINE_EPOCH,
356
+ reset: false,
357
+ staleCursor: false,
358
+ gap: false,
359
+ window,
360
+ hasOlder: selected[0].seq > minSeq,
361
+ hasNewer: Boolean(lastSelected && lastSelected.seq < maxSeq),
362
+ rows: cloneRows(selected),
363
+ };
364
+ }
365
+ const beforeSeq = params.cursor?.seq ?? nextSeq;
366
+ const endExclusive = rows.findIndex((row) => row.seq >= beforeSeq);
367
+ const boundedRows = endExclusive < 0 ? rows : rows.slice(0, endExclusive);
368
+ const selected = selectAll || limit >= boundedRows.length
369
+ ? boundedRows
370
+ : boundedRows.slice(boundedRows.length - limit);
371
+ return {
372
+ agentProvider: params.agentProvider,
373
+ epoch: OFFLINE_TIMELINE_EPOCH,
374
+ reset: false,
375
+ staleCursor: false,
376
+ gap: false,
377
+ window,
378
+ hasOlder: selected.length > 0 && selected[0].seq > minSeq,
379
+ hasNewer: endExclusive >= 0,
380
+ rows: cloneRows(selected),
381
+ };
382
+ }
383
+ function projectOfflineTimelineFetchResponse(params) {
384
+ let hasOlder = params.model.hasOlder;
385
+ let hasNewer = params.model.hasNewer;
386
+ let startCursor = null;
387
+ let endCursor = null;
388
+ let entries;
389
+ if (params.shouldLimitByProjectedWindow) {
390
+ const projectedLimit = Math.max(1, Math.floor(params.requestedLimit ?? 1));
391
+ const projectedWindow = selectTimelineWindowByProjectedLimit({
392
+ rows: params.model.rows,
393
+ provider: params.model.agentProvider,
394
+ direction: params.direction,
395
+ limit: projectedLimit,
396
+ collapseToolLifecycle: false,
397
+ });
398
+ entries = projectTimelineRows(projectedWindow.selectedRows, params.model.agentProvider, params.projection, params.projectionOptions);
399
+ if (projectedWindow.minSeq !== null && projectedWindow.maxSeq !== null) {
400
+ startCursor = { epoch: params.model.epoch, seq: projectedWindow.minSeq };
401
+ endCursor = { epoch: params.model.epoch, seq: projectedWindow.maxSeq };
402
+ hasOlder = projectedWindow.minSeq > params.model.window.minSeq;
403
+ hasNewer = false;
404
+ }
405
+ }
406
+ else {
407
+ const firstRow = params.model.rows[0];
408
+ const lastRow = params.model.rows[params.model.rows.length - 1];
409
+ startCursor = firstRow ? { epoch: params.model.epoch, seq: firstRow.seq } : null;
410
+ endCursor = lastRow ? { epoch: params.model.epoch, seq: lastRow.seq } : null;
411
+ entries = projectTimelineRows(params.model.rows, params.model.agentProvider, params.projection, params.projectionOptions);
412
+ }
413
+ return { entries, startCursor, endCursor, hasOlder, hasNewer };
414
+ }
415
+ export const __sessionTimelineInternals = {
416
+ projectOfflineTimelineFetchResponse,
417
+ selectOfflineTimelineWindow,
418
+ };
235
419
  /**
236
420
  * Session represents a single connected client session.
237
421
  * It owns all state management, orchestration logic, and message processing.
@@ -259,11 +443,14 @@ export class Session {
259
443
  // Per-session MCP client and tools
260
444
  this.agentMcpClient = null;
261
445
  this.agentTools = null;
446
+ /** In-flight Hub "test run" sandboxes, keyed by requestId, for cancellation. */
447
+ this.hubTestRuns = new Map();
262
448
  this.unsubscribeAgentEvents = null;
263
449
  this.agentUpdatesSubscription = null;
264
450
  this.workspaceUpdatesSubscription = null;
265
451
  this.clientActivity = null;
266
452
  this.MOBILE_BACKGROUND_STREAM_GRACE_MS = 60000;
453
+ this.wecomOpenClawQr = null;
267
454
  this.unsubscribeProviderSnapshotEvents = null;
268
455
  this.subscribedTerminalDirectories = new Set();
269
456
  this.unsubscribeTerminalsChanged = null;
@@ -273,6 +460,18 @@ export class Session {
273
460
  this.nextTerminalSlot = 0;
274
461
  this.inflightRequests = 0;
275
462
  this.peakInflightRequests = 0;
463
+ this.availableEditorTargetsCache = new TTLCache({
464
+ ttl: AVAILABLE_EDITOR_TARGETS_CACHE_TTL_MS,
465
+ max: 1,
466
+ checkAgeOnGet: true,
467
+ });
468
+ this.availableEditorTargetsInflight = null;
469
+ this.projectPlacementCache = new TTLCache({
470
+ ttl: PROJECT_PLACEMENT_CACHE_TTL_MS,
471
+ max: 500,
472
+ checkAgeOnGet: true,
473
+ });
474
+ this.projectPlacementInflight = new Map();
276
475
  this.bugReportWsMessageRing = [];
277
476
  this.checkoutDiffSubscriptions = new Map();
278
477
  this.workspaceGitSubscriptions = new Map();
@@ -285,7 +484,7 @@ export class Session {
285
484
  attention: 3,
286
485
  done: 4,
287
486
  };
288
- const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, seaworkHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, } = options;
487
+ const { clientId, appVersion, onMessage, onBinaryMessage, onLifecycleIntent, logger, downloadTokenStore, pushTokenStore, seaworkHome, agentManager, agentStorage, projectRegistry, workspaceRegistry, chatService, scheduleService, loopService, checkoutDiffManager, workspaceGitService, daemonConfigStore, mcpBaseUrl, stt, tts, terminalManager, providerSnapshotManager, voice, voiceBridge, dictation, agentProviderRuntimeSettings, library, broadcastLibraryUpdate, } = options;
289
488
  this.clientId = clientId;
290
489
  this.appVersion = appVersion;
291
490
  this.sessionId = uuidv4();
@@ -306,6 +505,8 @@ export class Session {
306
505
  this.workspaceGitService = workspaceGitService;
307
506
  this.daemonConfigStore = daemonConfigStore;
308
507
  this.mcpBaseUrl = mcpBaseUrl ?? null;
508
+ this.library = library ?? null;
509
+ this.broadcastLibraryUpdate = broadcastLibraryUpdate ?? null;
309
510
  this.terminalManager = terminalManager;
310
511
  this.providerSnapshotManager = providerSnapshotManager ?? null;
311
512
  if (this.terminalManager) {
@@ -574,15 +775,20 @@ export class Session {
574
775
  }
575
776
  }
576
777
  }
577
- const serializedEvent = serializeAgentStreamEvent(event.event);
778
+ const serializedEvent = serializeAgentStreamEvent(event.event, {
779
+ ...legacyTimelineProjectionOptions(this.appVersion),
780
+ });
578
781
  if (!serializedEvent) {
579
782
  return;
580
783
  }
581
- // Log timing for key stream events
784
+ // Log timing for key stream events. "timeline" is intentionally
785
+ // excluded: it fires once per delta, so a long turn floods daemon.log
786
+ // and evicts the real turn_failed error from the bug-report tail
787
+ // window (#1215). Keep only the once-per-turn markers.
582
788
  const evtType = event.event.type;
583
- if (evtType === "timeline" ||
584
- evtType === "turn_completed" ||
789
+ if (evtType === "turn_completed" ||
585
790
  evtType === "turn_failed" ||
791
+ evtType === "turn_canceled" ||
586
792
  evtType === "usage_updated") {
587
793
  this.sessionLogger.info({
588
794
  agentId: event.agentId,
@@ -717,6 +923,7 @@ export class Session {
717
923
  async ensureAgentLoaded(agentId) {
718
924
  const existing = this.agentManager.getAgent(agentId);
719
925
  if (existing) {
926
+ await this.agentManager.hydrateTimelineFromProvider(agentId);
720
927
  return existing;
721
928
  }
722
929
  const inflight = pendingAgentInitializations.get(agentId);
@@ -753,6 +960,36 @@ export class Session {
753
960
  }
754
961
  }
755
962
  }
963
+ async loadOfflineTimelineForStoredAgent(params) {
964
+ const record = await this.agentStorage.get(params.agentId);
965
+ if (!record) {
966
+ return null;
967
+ }
968
+ const handle = toAgentPersistenceHandle(this.sessionLogger, record.persistence);
969
+ if (!handle) {
970
+ return null;
971
+ }
972
+ const provider = coerceAgentProvider(this.sessionLogger, record.provider, record.id);
973
+ const client = this.providerRegistry[provider]?.createClient(this.sessionLogger);
974
+ if (!client?.loadPersistedTimeline) {
975
+ return null;
976
+ }
977
+ const items = await client.loadPersistedTimeline(handle);
978
+ if (items.length === 0) {
979
+ return null;
980
+ }
981
+ return {
982
+ record,
983
+ model: selectOfflineTimelineWindow({
984
+ agentProvider: provider,
985
+ items,
986
+ direction: params.direction,
987
+ cursor: params.cursor,
988
+ limit: params.limit,
989
+ timestamp: this.resolveStoredAgentPayloadUpdatedAt(record),
990
+ }),
991
+ };
992
+ }
756
993
  // TODO: Remove once all app store clients are on >=0.1.45.
757
994
  isProviderVisibleToClient(provider) {
758
995
  if (clientSupportsAllProviders(this.appVersion))
@@ -852,10 +1089,27 @@ export class Session {
852
1089
  }
853
1090
  }
854
1091
  async buildProjectPlacement(cwd) {
855
- return buildProjectPlacementForCwd({
1092
+ const cached = this.projectPlacementCache.get(cwd);
1093
+ if (cached) {
1094
+ return cached;
1095
+ }
1096
+ const inflight = this.projectPlacementInflight.get(cwd);
1097
+ if (inflight) {
1098
+ return inflight;
1099
+ }
1100
+ const promise = buildProjectPlacementForCwd({
856
1101
  cwd,
857
1102
  seaworkHome: this.seaworkHome,
1103
+ })
1104
+ .then((placement) => {
1105
+ this.projectPlacementCache.set(cwd, placement);
1106
+ return placement;
1107
+ })
1108
+ .finally(() => {
1109
+ this.projectPlacementInflight.delete(cwd);
858
1110
  });
1111
+ this.projectPlacementInflight.set(cwd, promise);
1112
+ return promise;
859
1113
  }
860
1114
  buildPersistedProjectRecord(input) {
861
1115
  return createPersistedProjectRecord({
@@ -1106,6 +1360,36 @@ export class Session {
1106
1360
  case "toggle_issue_reaction_request":
1107
1361
  await this.handleToggleIssueReactionRequest(msg);
1108
1362
  break;
1363
+ case "library_list_request":
1364
+ case "library_upsert_mcp_request":
1365
+ case "library_upsert_skill_request":
1366
+ case "library_delete_request":
1367
+ case "library_set_autoattach_request":
1368
+ case "library_import_existing_request":
1369
+ case "library_install_from_hub_request":
1370
+ case "library_uninstall_from_hub_request": {
1371
+ const response = await handleLibraryRequest(msg, {
1372
+ library: this.library,
1373
+ seaworkHome: this.seaworkHome,
1374
+ logger: this.sessionLogger,
1375
+ broadcastUpdate: (snapshot) => this.broadcastLibraryUpdate?.(snapshot),
1376
+ });
1377
+ this.emit(response);
1378
+ break;
1379
+ }
1380
+ case "hub_test_run_start_request": {
1381
+ const done = await handleHubTestRunStart(msg, {
1382
+ codexClient: this.providerRegistry.codex.createClient(this.sessionLogger),
1383
+ logger: this.sessionLogger,
1384
+ emitChunk: (chunk) => this.emit(chunk),
1385
+ inFlight: this.hubTestRuns,
1386
+ });
1387
+ this.emit(done);
1388
+ break;
1389
+ }
1390
+ case "hub_test_run_cancel_request":
1391
+ handleHubTestRunCancel(msg.requestId, this.hubTestRuns);
1392
+ break;
1109
1393
  case "archive_agent_request":
1110
1394
  await this.handleArchiveAgentRequest(msg.agentId, msg.requestId);
1111
1395
  break;
@@ -1129,25 +1413,41 @@ export class Session {
1129
1413
  type: "get_daemon_config_response",
1130
1414
  payload: {
1131
1415
  requestId: msg.requestId,
1132
- config: redactGitForgeTokens(this.daemonConfigStore.get()),
1416
+ config: this.redactDaemonConfig(this.daemonConfigStore.get()),
1133
1417
  },
1134
1418
  });
1135
1419
  break;
1136
1420
  case "set_daemon_config_request": {
1137
- const rehydrated = rehydratePatchTokens({
1421
+ const tokenRehydrated = rehydratePatchTokens({
1138
1422
  patch: msg.config,
1139
1423
  current: this.daemonConfigStore.get(),
1140
1424
  });
1141
- const next = this.daemonConfigStore.patch(rehydrated);
1425
+ const providerEnvRehydrated = rehydratePatchProviderEnv({
1426
+ patch: tokenRehydrated,
1427
+ current: this.daemonConfigStore.get(),
1428
+ });
1429
+ const rehydrated = rehydratePatchWeComOpenClawSecret({
1430
+ patch: providerEnvRehydrated,
1431
+ current: this.daemonConfigStore.get(),
1432
+ });
1433
+ const cwdResolved = resolvePatchWeComOpenClawAgentCwd({
1434
+ patch: rehydrated,
1435
+ seaworkHome: this.seaworkHome,
1436
+ });
1437
+ const promptInjectionResolved = resolvePatchPromptInjectionCwds(cwdResolved);
1438
+ const next = this.daemonConfigStore.patch(promptInjectionResolved);
1142
1439
  this.emit({
1143
1440
  type: "set_daemon_config_response",
1144
1441
  payload: {
1145
1442
  requestId: msg.requestId,
1146
- config: redactGitForgeTokens(next),
1443
+ config: this.redactDaemonConfig(next),
1147
1444
  },
1148
1445
  });
1149
1446
  break;
1150
1447
  }
1448
+ case "wecom_openclaw_qr_request":
1449
+ await this.handleWeComOpenClawQrRequest(msg.requestId, msg.poll === true);
1450
+ break;
1151
1451
  case "fetch_git_forge_issues_request": {
1152
1452
  const result = await fetchGitForgeIssues({ workspaceId: msg.workspaceId, cwd: msg.cwd }, {
1153
1453
  configStore: this.daemonConfigStore,
@@ -1162,6 +1462,41 @@ export class Session {
1162
1462
  });
1163
1463
  break;
1164
1464
  }
1465
+ case "discover_git_forge_issue_work_request": {
1466
+ const result = await discoverGitForgeIssueWork({ workspaceId: msg.workspaceId, cwd: msg.cwd, number: msg.number }, {
1467
+ configStore: this.daemonConfigStore,
1468
+ workspaceRegistry: this.workspaceRegistry,
1469
+ agentManager: this.agentManager,
1470
+ seaworkHome: this.seaworkHome,
1471
+ });
1472
+ this.emit({
1473
+ type: "discover_git_forge_issue_work_response",
1474
+ payload: {
1475
+ requestId: msg.requestId,
1476
+ result,
1477
+ },
1478
+ });
1479
+ break;
1480
+ }
1481
+ case "transition_git_forge_issue_work_request": {
1482
+ const result = await transitionGitForgeIssueWork({
1483
+ workspaceId: msg.workspaceId,
1484
+ cwd: msg.cwd,
1485
+ number: msg.number,
1486
+ source: msg.source,
1487
+ }, {
1488
+ configStore: this.daemonConfigStore,
1489
+ workspaceRegistry: this.workspaceRegistry,
1490
+ });
1491
+ this.emit({
1492
+ type: "transition_git_forge_issue_work_response",
1493
+ payload: {
1494
+ requestId: msg.requestId,
1495
+ result,
1496
+ },
1497
+ });
1498
+ break;
1499
+ }
1165
1500
  case "dictation_stream_start":
1166
1501
  {
1167
1502
  const unavailable = this.resolveVoiceFeatureUnavailableContext("dictation");
@@ -1448,6 +1783,12 @@ export class Session {
1448
1783
  case "schedule/resume":
1449
1784
  await this.handleScheduleResumeRequest(msg);
1450
1785
  break;
1786
+ case "schedule/update":
1787
+ await this.handleScheduleUpdateRequest(msg);
1788
+ break;
1789
+ case "schedule/run-now":
1790
+ await this.handleScheduleRunNowRequest(msg);
1791
+ break;
1451
1792
  case "schedule/delete":
1452
1793
  await this.handleScheduleDeleteRequest(msg);
1453
1794
  break;
@@ -1646,6 +1987,7 @@ export class Session {
1646
1987
  focusedAgentId: this.clientActivity?.focusedAgentId,
1647
1988
  }),
1648
1989
  getWsMessageRing: () => this.getBugReportWsMessageRing(),
1990
+ getRecentTurnTraces: () => this.agentManager.getBugReportTurnTraces(),
1649
1991
  });
1650
1992
  this.emit(response);
1651
1993
  }
@@ -2186,7 +2528,7 @@ export class Session {
2186
2528
  /**
2187
2529
  * Handle text message to agent (with optional image + document attachments)
2188
2530
  */
2189
- async handleSendAgentMessage(agentId, text, messageId, images, runOptions, options, documents) {
2531
+ async handleSendAgentMessage(agentId, text, messageId, images, runOptions, options, documents, imageAttachments) {
2190
2532
  this.sessionLogger.info({
2191
2533
  agentId,
2192
2534
  textPreview: text.substring(0, 50),
@@ -2195,7 +2537,14 @@ export class Session {
2195
2537
  }, `Sending text to agent ${agentId}${images && images.length > 0 ? ` with ${images.length} image attachment(s)` : ""}${documents && documents.length > 0 ? ` with ${documents.length} document attachment(s)` : ""}`);
2196
2538
  await this.unarchiveAgentState(agentId);
2197
2539
  try {
2198
- await this.ensureAgentLoaded(agentId);
2540
+ if (options?.skipAgentLoad) {
2541
+ if (!this.agentManager.getAgent(agentId)) {
2542
+ throw new Error(`Agent not found: ${agentId}`);
2543
+ }
2544
+ }
2545
+ else {
2546
+ await this.ensureAgentLoaded(agentId);
2547
+ }
2199
2548
  }
2200
2549
  catch (error) {
2201
2550
  this.handleAgentRunError(agentId, error, "Failed to initialize agent before sending prompt");
@@ -2207,12 +2556,26 @@ export class Session {
2207
2556
  try {
2208
2557
  this.agentManager.recordUserMessage(agentId, text, {
2209
2558
  messageId,
2559
+ images: imageAttachments,
2210
2560
  emitState: false,
2211
2561
  });
2212
2562
  }
2213
2563
  catch (error) {
2214
2564
  this.sessionLogger.error({ err: error, agentId }, `Failed to record user message for agent ${agentId}`);
2215
2565
  }
2566
+ // Out-of-band slash commands (e.g. /goal) are handled as a side effect
2567
+ // without allocating a turn. Spoken input is never treated as a command.
2568
+ if (!options?.spokenInput) {
2569
+ const oob = await this.agentManager.tryRunOutOfBand(agentId, text);
2570
+ if (oob) {
2571
+ await this.agentManager.appendTimelineItem(agentId, {
2572
+ type: "assistant_message",
2573
+ text: oob.response,
2574
+ });
2575
+ this.agentManager.emitAgentState(agentId);
2576
+ return { ok: true };
2577
+ }
2578
+ }
2216
2579
  const promptText = options?.spokenInput ? wrapSpokenInput(text) : text;
2217
2580
  const prompt = this.buildAgentPrompt(promptText, images, documents);
2218
2581
  return this.startAgentStream(agentId, prompt, runOptions);
@@ -2221,7 +2584,7 @@ export class Session {
2221
2584
  * Handle create agent request
2222
2585
  */
2223
2586
  async handleCreateAgentRequest(msg) {
2224
- const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, documents, labels, } = msg;
2587
+ const { config, worktreeName, requestId, initialPrompt, clientMessageId, outputSchema, git, images, imageAttachments, documents, labels, } = msg;
2225
2588
  this.sessionLogger.info({ cwd: config.cwd, provider: config.provider, worktreeName }, `Creating agent in ${config.cwd} (${config.provider})${worktreeName ? ` with worktree ${worktreeName}` : ""}`);
2226
2589
  try {
2227
2590
  const trimmedPrompt = initialPrompt?.trim();
@@ -2250,7 +2613,7 @@ export class Session {
2250
2613
  seaworkHome: this.seaworkHome,
2251
2614
  logger: this.sessionLogger,
2252
2615
  });
2253
- const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined, undefined, documents);
2616
+ const started = await this.handleSendAgentMessage(snapshot.id, trimmedPrompt, resolveClientMessageId(clientMessageId), images, outputSchema ? { outputSchema } : undefined, { skipAgentLoad: true }, documents, imageAttachments);
2254
2617
  if (!started.ok) {
2255
2618
  throw new Error(started.error);
2256
2619
  }
@@ -2428,6 +2791,7 @@ export class Session {
2428
2791
  const sessions = await this.agentManager.listPersistedAgents({
2429
2792
  ...(provider ? { provider } : {}),
2430
2793
  ...(fetchLimit !== undefined ? { limit: fetchLimit } : {}),
2794
+ ...(includeTimeline ? { includeTimeline: true } : {}),
2431
2795
  });
2432
2796
  // Only claim sessions that are currently live — archived Seawork records
2433
2797
  // should reappear in the Resume list so the user can pick them back up
@@ -2552,34 +2916,99 @@ export class Session {
2552
2916
  }, config, gitOptions, legacyWorktreeName, _labels);
2553
2917
  }
2554
2918
  async handleListProviderModelsRequest(msg) {
2919
+ const cwd = msg.cwd ? expandTilde(msg.cwd) : undefined;
2555
2920
  const fetchedAt = new Date().toISOString();
2556
- try {
2557
- const models = await this.providerRegistry[msg.provider].fetchModels({
2558
- cwd: msg.cwd ? expandTilde(msg.cwd) : undefined,
2559
- });
2921
+ const providerDefinition = this.providerRegistry[msg.provider];
2922
+ const manager = this.providerSnapshotManager;
2923
+ if (!providerDefinition) {
2560
2924
  this.emit({
2561
2925
  type: "list_provider_models_response",
2562
2926
  payload: {
2563
2927
  provider: msg.provider,
2564
- models,
2565
- error: null,
2928
+ models: [],
2929
+ error: `Unknown provider: ${msg.provider}`,
2566
2930
  fetchedAt,
2567
2931
  requestId: msg.requestId,
2568
2932
  },
2569
2933
  });
2934
+ return;
2570
2935
  }
2571
- catch (error) {
2572
- this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
2936
+ if (!manager) {
2937
+ try {
2938
+ const models = await providerDefinition.fetchModels({ cwd });
2939
+ this.emit({
2940
+ type: "list_provider_models_response",
2941
+ payload: {
2942
+ provider: msg.provider,
2943
+ models,
2944
+ error: null,
2945
+ fetchedAt,
2946
+ requestId: msg.requestId,
2947
+ },
2948
+ });
2949
+ }
2950
+ catch (error) {
2951
+ this.sessionLogger.error({ err: error, provider: msg.provider }, `Failed to list models for ${msg.provider}`);
2952
+ this.emit({
2953
+ type: "list_provider_models_response",
2954
+ payload: {
2955
+ provider: msg.provider,
2956
+ models: [],
2957
+ error: error?.message ?? String(error),
2958
+ fetchedAt,
2959
+ requestId: msg.requestId,
2960
+ },
2961
+ });
2962
+ }
2963
+ return;
2964
+ }
2965
+ // Route through the snapshot manager so simultaneous calls dedupe through
2966
+ // the in-flight warmup and cached entries are served without re-spawning.
2967
+ const findEntry = () => manager.getSnapshot(cwd).find((candidate) => candidate.provider === msg.provider);
2968
+ let entry = findEntry();
2969
+ if (!entry || entry.status === "loading") {
2970
+ await manager.ensureSnapshot(cwd);
2971
+ entry = findEntry();
2972
+ }
2973
+ if (!entry) {
2573
2974
  this.emit({
2574
2975
  type: "list_provider_models_response",
2575
2976
  payload: {
2576
2977
  provider: msg.provider,
2577
- error: error?.message ?? String(error),
2978
+ models: [],
2979
+ error: `Unknown provider: ${msg.provider}`,
2578
2980
  fetchedAt,
2579
2981
  requestId: msg.requestId,
2580
2982
  },
2581
2983
  });
2984
+ return;
2985
+ }
2986
+ if (entry.status === "ready") {
2987
+ this.emit({
2988
+ type: "list_provider_models_response",
2989
+ payload: {
2990
+ provider: msg.provider,
2991
+ models: entry.models ?? [],
2992
+ error: null,
2993
+ fetchedAt: entry.fetchedAt ?? fetchedAt,
2994
+ requestId: msg.requestId,
2995
+ },
2996
+ });
2997
+ return;
2582
2998
  }
2999
+ const errorMessage = entry.status === "error"
3000
+ ? (entry.error ?? `Failed to list models for ${msg.provider}`)
3001
+ : `Provider ${msg.provider} is not available`;
3002
+ this.emit({
3003
+ type: "list_provider_models_response",
3004
+ payload: {
3005
+ provider: msg.provider,
3006
+ models: [],
3007
+ error: errorMessage,
3008
+ fetchedAt,
3009
+ requestId: msg.requestId,
3010
+ },
3011
+ });
2583
3012
  }
2584
3013
  async handleListProviderModesRequest(msg) {
2585
3014
  const fetchedAt = new Date().toISOString();
@@ -2621,6 +3050,12 @@ export class Session {
2621
3050
  ...(draftConfig.featureValues ? { featureValues: draftConfig.featureValues } : {}),
2622
3051
  };
2623
3052
  }
3053
+ async filterLibraryCommands(commands, config) {
3054
+ if (!this.library || (config.provider !== "claude" && config.provider !== "codex")) {
3055
+ return commands;
3056
+ }
3057
+ return filterLibrarySkillCommands(commands, await this.library.load(), config.provider, config.perAgentDisabled);
3058
+ }
2624
3059
  async handleListProviderFeaturesRequest(msg) {
2625
3060
  const fetchedAt = new Date().toISOString();
2626
3061
  try {
@@ -2710,6 +3145,94 @@ export class Session {
2710
3145
  },
2711
3146
  });
2712
3147
  }
3148
+ redactDaemonConfig(config) {
3149
+ const redacted = redactProviderEnv(redactGitForgeTokens(config));
3150
+ const wecomOpenClaw = redacted.integrations?.wecomOpenClaw;
3151
+ if (!wecomOpenClaw?.secret) {
3152
+ return redacted;
3153
+ }
3154
+ return {
3155
+ ...redacted,
3156
+ integrations: {
3157
+ ...redacted.integrations,
3158
+ wecomOpenClaw: {
3159
+ ...wecomOpenClaw,
3160
+ secret: "<configured>",
3161
+ },
3162
+ },
3163
+ };
3164
+ }
3165
+ async handleWeComOpenClawQrRequest(requestId, poll) {
3166
+ try {
3167
+ if (!this.wecomOpenClawQr || !poll) {
3168
+ this.wecomOpenClawQr = await fetchWeComOpenClawQrCode();
3169
+ }
3170
+ const qr = this.wecomOpenClawQr;
3171
+ const credentials = await queryWeComOpenClawQrResult(qr.scode);
3172
+ if (!credentials) {
3173
+ this.emit({
3174
+ type: "wecom_openclaw_qr_response",
3175
+ payload: {
3176
+ requestId,
3177
+ status: "pending",
3178
+ scode: qr.scode,
3179
+ authUrl: qr.authUrl,
3180
+ qrPageUrl: qr.qrPageUrl,
3181
+ config: null,
3182
+ error: null,
3183
+ },
3184
+ });
3185
+ return;
3186
+ }
3187
+ const currentWeCom = this.daemonConfigStore.get().integrations?.wecomOpenClaw ?? {};
3188
+ const allowUsers = mergeUniqueNonEmptyStrings(currentWeCom.allowUsers, credentials.authorizedUserId ? [credentials.authorizedUserId] : []);
3189
+ const hasAllowlist = allowUsers.length > 0 || normalizeNonEmptyStrings(currentWeCom.allowChats).length > 0;
3190
+ const pendingAllowFirstUser = !hasAllowlist;
3191
+ const cwd = resolveWeComOpenClawAgentCwd(currentWeCom.cwd, this.seaworkHome);
3192
+ const next = this.daemonConfigStore.patch({
3193
+ integrations: {
3194
+ wecomOpenClaw: {
3195
+ ...currentWeCom,
3196
+ enabled: hasAllowlist || pendingAllowFirstUser,
3197
+ botId: credentials.botId,
3198
+ secret: credentials.secret,
3199
+ cwd,
3200
+ provider: currentWeCom.provider ?? DEFAULT_AGENT_PROVIDER,
3201
+ agentMode: currentWeCom.agentMode ?? "new",
3202
+ allowUsers,
3203
+ pendingAllowFirstUser,
3204
+ },
3205
+ },
3206
+ });
3207
+ this.wecomOpenClawQr = null;
3208
+ this.emit({
3209
+ type: "wecom_openclaw_qr_response",
3210
+ payload: {
3211
+ requestId,
3212
+ status: "success",
3213
+ scode: qr.scode,
3214
+ authUrl: qr.authUrl,
3215
+ qrPageUrl: qr.qrPageUrl,
3216
+ config: this.redactDaemonConfig(next),
3217
+ error: null,
3218
+ },
3219
+ });
3220
+ }
3221
+ catch (error) {
3222
+ this.emit({
3223
+ type: "wecom_openclaw_qr_response",
3224
+ payload: {
3225
+ requestId,
3226
+ status: "error",
3227
+ scode: this.wecomOpenClawQr?.scode ?? null,
3228
+ authUrl: this.wecomOpenClawQr?.authUrl ?? null,
3229
+ qrPageUrl: this.wecomOpenClawQr?.qrPageUrl ?? null,
3230
+ config: null,
3231
+ error: formatUnknownError(error),
3232
+ },
3233
+ });
3234
+ }
3235
+ }
2713
3236
  async handleRefreshProvidersSnapshotRequest(msg) {
2714
3237
  this.providerSnapshotManager?.refresh(msg.cwd ? expandTilde(msg.cwd) : undefined);
2715
3238
  this.emit({
@@ -2877,22 +3400,16 @@ export class Session {
2877
3400
  }
2878
3401
  }
2879
3402
  async ensureCleanWorkingTree(cwd) {
2880
- const dirty = await this.isWorkingTreeDirty(cwd);
2881
- if (dirty) {
2882
- throw new Error("Working directory has uncommitted changes. Commit or stash before switching branches.");
2883
- }
2884
- }
2885
- async isWorkingTreeDirty(cwd) {
3403
+ let dirty;
2886
3404
  try {
2887
- const { stdout } = await execAsync("git status --porcelain", {
2888
- cwd,
2889
- env: READ_ONLY_GIT_ENV,
2890
- });
2891
- return stdout.trim().length > 0;
3405
+ dirty = await hasWorkingTreeChanges(cwd);
2892
3406
  }
2893
3407
  catch (error) {
2894
3408
  throw new Error(`Unable to inspect git status for ${cwd}: ${error.message}`);
2895
3409
  }
3410
+ if (dirty) {
3411
+ throw new Error("Working directory has uncommitted changes. Commit or stash before switching branches.");
3412
+ }
2896
3413
  }
2897
3414
  async checkoutExistingBranch(cwd, branch) {
2898
3415
  this.assertSafeGitRef(branch, "branch");
@@ -3100,6 +3617,7 @@ export class Session {
3100
3617
  lastActivityAt: new Date(msg.lastActivityAt),
3101
3618
  appVisible: msg.appVisible,
3102
3619
  appVisibilityChangedAt,
3620
+ desktopRuntime: msg.desktopRuntime ?? null,
3103
3621
  };
3104
3622
  }
3105
3623
  /**
@@ -3119,7 +3637,8 @@ export class Session {
3119
3637
  const agents = this.agentManager.listAgents();
3120
3638
  const agent = agents.find((a) => a.id === agentId);
3121
3639
  if (agent?.session?.listCommands) {
3122
- const commands = await agent.session.listCommands();
3640
+ await this.agentManager.refreshLibrarySkillPolicy(agent.id);
3641
+ const commands = await this.filterLibraryCommands(await agent.session.listCommands(), agent.config);
3123
3642
  this.emit({
3124
3643
  type: "list_commands_response",
3125
3644
  payload: {
@@ -3133,7 +3652,7 @@ export class Session {
3133
3652
  }
3134
3653
  if (!agent && draftConfig) {
3135
3654
  const sessionConfig = this.buildDraftAgentSessionConfig(draftConfig);
3136
- const commands = await this.agentManager.listDraftCommands(sessionConfig);
3655
+ const commands = await this.filterLibraryCommands(await this.agentManager.listDraftCommands(sessionConfig), sessionConfig);
3137
3656
  this.emit({
3138
3657
  type: "list_commands_response",
3139
3658
  payload: {
@@ -3479,6 +3998,8 @@ export class Session {
3479
3998
  try {
3480
3999
  await this.checkoutExistingBranch(cwd, branch);
3481
4000
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4001
+ invalidateCheckoutShortstatCache(cwd);
4002
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3482
4003
  // Push a workspace_update immediately so the sidebar/header reflect
3483
4004
  // the new branch name without waiting for the background git watcher.
3484
4005
  await this.emitWorkspaceUpdateForCwd(cwd);
@@ -3514,7 +4035,9 @@ export class Session {
3514
4035
  ? `${Session.SEAWORK_STASH_PREFIX} ${branchLabel}`
3515
4036
  : `${Session.SEAWORK_STASH_PREFIX} unnamed`;
3516
4037
  await execFileAsync("git", ["stash", "push", "--include-untracked", "-m", message], { cwd });
4038
+ invalidateCheckoutShortstatCache(cwd);
3517
4039
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4040
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3518
4041
  this.emit({
3519
4042
  type: "stash_save_response",
3520
4043
  payload: { cwd, success: true, error: null, requestId },
@@ -3531,7 +4054,9 @@ export class Session {
3531
4054
  const { cwd, stashIndex, requestId } = msg;
3532
4055
  try {
3533
4056
  await execFileAsync("git", ["stash", "pop", `stash@{${stashIndex}}`], { cwd });
4057
+ invalidateCheckoutShortstatCache(cwd);
3534
4058
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4059
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3535
4060
  this.emit({
3536
4061
  type: "stash_pop_response",
3537
4062
  payload: { cwd, success: true, error: null, requestId },
@@ -3599,7 +4124,9 @@ export class Session {
3599
4124
  message,
3600
4125
  addAll: msg.addAll ?? true,
3601
4126
  });
4127
+ invalidateCheckoutShortstatCache(cwd);
3602
4128
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4129
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3603
4130
  this.emit({
3604
4131
  type: "checkout_commit_response",
3605
4132
  payload: {
@@ -3643,11 +4170,7 @@ export class Session {
3643
4170
  }
3644
4171
  }
3645
4172
  if (msg.requireCleanTarget) {
3646
- const { stdout } = await execAsync("git status --porcelain", {
3647
- cwd,
3648
- env: READ_ONLY_GIT_ENV,
3649
- });
3650
- if (stdout.trim().length > 0) {
4173
+ if (await hasWorkingTreeChanges(cwd)) {
3651
4174
  throw new Error("Working directory has uncommitted changes.");
3652
4175
  }
3653
4176
  }
@@ -3658,11 +4181,13 @@ export class Session {
3658
4181
  if (baseRef.startsWith("origin/")) {
3659
4182
  baseRef = baseRef.slice("origin/".length);
3660
4183
  }
3661
- await mergeToBase(cwd, {
4184
+ const mutatedCwd = await mergeToBase(cwd, {
3662
4185
  baseRef,
3663
4186
  mode: msg.strategy === "squash" ? "squash" : "merge",
3664
4187
  }, { seaworkHome: this.seaworkHome });
3665
4188
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4189
+ invalidateCheckoutShortstatCache(mutatedCwd);
4190
+ void this.workspaceGitService.refresh(mutatedCwd, { priority: "high" });
3666
4191
  this.emit({
3667
4192
  type: "checkout_merge_response",
3668
4193
  payload: {
@@ -3689,11 +4214,7 @@ export class Session {
3689
4214
  const { cwd, requestId } = msg;
3690
4215
  try {
3691
4216
  if (msg.requireCleanTarget ?? true) {
3692
- const { stdout } = await execAsync("git status --porcelain", {
3693
- cwd,
3694
- env: READ_ONLY_GIT_ENV,
3695
- });
3696
- if (stdout.trim().length > 0) {
4217
+ if (await hasWorkingTreeChanges(cwd)) {
3697
4218
  throw new Error("Working directory has uncommitted changes.");
3698
4219
  }
3699
4220
  }
@@ -3702,6 +4223,8 @@ export class Session {
3702
4223
  requireCleanTarget: msg.requireCleanTarget ?? true,
3703
4224
  });
3704
4225
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4226
+ invalidateCheckoutShortstatCache(cwd);
4227
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3705
4228
  this.emit({
3706
4229
  type: "checkout_merge_from_base_response",
3707
4230
  payload: {
@@ -3729,6 +4252,8 @@ export class Session {
3729
4252
  try {
3730
4253
  await pullCurrentBranch(cwd);
3731
4254
  this.checkoutDiffManager.scheduleRefreshForCwd(cwd);
4255
+ invalidateCheckoutShortstatCache(cwd);
4256
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3732
4257
  this.emit({
3733
4258
  type: "checkout_pull_response",
3734
4259
  payload: {
@@ -3755,6 +4280,8 @@ export class Session {
3755
4280
  const { cwd, requestId } = msg;
3756
4281
  try {
3757
4282
  await pushCurrentBranch(cwd);
4283
+ invalidateCheckoutShortstatCache(cwd);
4284
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3758
4285
  this.emit({
3759
4286
  type: "checkout_push_response",
3760
4287
  payload: {
@@ -3794,6 +4321,8 @@ export class Session {
3794
4321
  body,
3795
4322
  base: msg.baseRef,
3796
4323
  });
4324
+ invalidateCheckoutShortstatCache(cwd);
4325
+ void this.workspaceGitService.refresh(cwd, { priority: "high" });
3797
4326
  this.emit({
3798
4327
  type: "checkout_pr_create_response",
3799
4328
  payload: {
@@ -3938,7 +4467,17 @@ export class Session {
3938
4467
  }
3939
4468
  }
3940
4469
  catch (error) {
3941
- this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
4470
+ // Workspace on a removable drive that's been ejected log at WARN so
4471
+ // a power user with a flaky external disk doesn't fill daemon.log with
4472
+ // ERROR-level entries for an expected condition. See issue #662 / #632.
4473
+ const code = error?.code;
4474
+ const isMissingPath = code === "ENOENT";
4475
+ if (isMissingPath) {
4476
+ this.sessionLogger.warn({ err: error, cwd, path: requestedPath }, `Workspace offline for file explorer request: ${cwd}`);
4477
+ }
4478
+ else {
4479
+ this.sessionLogger.error({ err: error, cwd, path: requestedPath }, `Failed to fulfill file explorer request for workspace ${cwd}`);
4480
+ }
3942
4481
  this.emit({
3943
4482
  type: "file_explorer_response",
3944
4483
  payload: {
@@ -4490,7 +5029,8 @@ export class Session {
4490
5029
  placementByCwd.set(cwd, placementPromise);
4491
5030
  return placementPromise;
4492
5031
  };
4493
- let candidates = [...agents];
5032
+ const includeArchived = filter?.includeArchived ?? false;
5033
+ let candidates = includeArchived ? [...agents] : agents.filter((a) => !a.archivedAt);
4494
5034
  candidates.sort((left, right) => this.compareFetchAgentsAgents(left, right, sort));
4495
5035
  const cursorToken = request.page?.cursor;
4496
5036
  if (cursorToken) {
@@ -4552,6 +5092,19 @@ export class Session {
4552
5092
  }
4553
5093
  async describeWorkspaceRecord(workspace, projectRecord) {
4554
5094
  const resolvedProjectRecord = projectRecord ?? (await this.projectRegistry.get(workspace.projectId));
5095
+ let diffStat = null;
5096
+ const cachedShortstat = getCachedCheckoutShortstat(workspace.cwd);
5097
+ if (cachedShortstat !== undefined) {
5098
+ diffStat = cachedShortstat;
5099
+ }
5100
+ else {
5101
+ const cwd = workspace.cwd;
5102
+ warmCheckoutShortstatInBackground(cwd, undefined, () => {
5103
+ this.emitWorkspaceUpdateForCwd(cwd).catch((error) => {
5104
+ this.sessionLogger.warn({ error, cwd }, "Failed to emit workspace update after shortstat warm");
5105
+ });
5106
+ });
5107
+ }
4555
5108
  return {
4556
5109
  id: workspace.workspaceId,
4557
5110
  projectId: workspace.projectId,
@@ -4562,7 +5115,7 @@ export class Session {
4562
5115
  name: workspace.displayName,
4563
5116
  status: "done",
4564
5117
  activityAt: null,
4565
- diffStat: null,
5118
+ diffStat,
4566
5119
  };
4567
5120
  }
4568
5121
  buildWorkspaceGitRuntimePayload(snapshot) {
@@ -4605,7 +5158,10 @@ export class Session {
4605
5158
  return {
4606
5159
  ...base,
4607
5160
  name: displayName,
4608
- diffStat: snapshot?.git.diffStat ?? null,
5161
+ // Prefer the live git-runtime snapshot, but fall back to the shortstat
5162
+ // cache that describeWorkspaceRecord warmed so the background update is
5163
+ // not discarded when the snapshot has no diffStat yet.
5164
+ diffStat: snapshot?.git.diffStat ?? base.diffStat,
4609
5165
  gitRuntime: snapshot ? this.buildWorkspaceGitRuntimePayload(snapshot) : undefined,
4610
5166
  githubRuntime: snapshot ? this.buildWorkspaceGitHubRuntimePayload(snapshot) : undefined,
4611
5167
  };
@@ -4630,17 +5186,17 @@ export class Session {
4630
5186
  const workspaceIds = options.workspaceIds
4631
5187
  ? new Set(Array.from(options.workspaceIds, (workspaceId) => normalizePersistedWorkspaceId(workspaceId)))
4632
5188
  : null;
4633
- for (const workspace of activeRecords) {
4634
- if (workspaceIds && !workspaceIds.has(workspace.workspaceId)) {
4635
- continue;
4636
- }
5189
+ const filteredRecords = workspaceIds
5190
+ ? activeRecords.filter((workspace) => workspaceIds.has(workspace.workspaceId))
5191
+ : activeRecords;
5192
+ await Promise.all(filteredRecords.map(async (workspace) => {
4637
5193
  const projectRecord = activeProjects.get(workspace.projectId) ?? null;
4638
5194
  descriptorsByWorkspaceId.set(workspace.workspaceId, await this.buildWorkspaceDescriptor({
4639
5195
  workspace,
4640
5196
  projectRecord,
4641
5197
  includeGitData: options.includeGitData,
4642
5198
  }));
4643
- }
5199
+ }));
4644
5200
  for (const agent of agents) {
4645
5201
  if (agent.archivedAt) {
4646
5202
  continue;
@@ -4891,7 +5447,6 @@ export class Session {
4891
5447
  buildPersistedWorkspaceRecord: (input) => this.buildPersistedWorkspaceRecord(input),
4892
5448
  buildProjectPlacement: (cwd) => this.buildProjectPlacement(cwd),
4893
5449
  projectRegistry: this.projectRegistry,
4894
- syncWorkspaceGitWatchTarget: (cwd, syncOptions) => this.syncWorkspaceGitWatchTarget(cwd, syncOptions),
4895
5450
  workspaceRegistry: this.workspaceRegistry,
4896
5451
  archiveProjectRecordIfEmpty: (projectId, archivedAt) => this.archiveProjectRecordIfEmpty(projectId, archivedAt),
4897
5452
  }, options);
@@ -4910,6 +5465,46 @@ export class Session {
4910
5465
  await this.projectRegistry.archive(existing.projectId, nextArchivedAt);
4911
5466
  }
4912
5467
  }
5468
+ async findWorkspaceRecordForRequest(workspaceId) {
5469
+ const normalizedWorkspaceId = normalizePersistedWorkspaceId(workspaceId);
5470
+ const normalized = await this.workspaceRegistry.get(normalizedWorkspaceId);
5471
+ if (normalized) {
5472
+ return normalized;
5473
+ }
5474
+ if (normalizedWorkspaceId !== workspaceId) {
5475
+ const raw = await this.workspaceRegistry.get(workspaceId);
5476
+ if (raw) {
5477
+ return raw;
5478
+ }
5479
+ }
5480
+ const lookupKey = normalizeWorkspaceLookupKey(workspaceId);
5481
+ const records = await this.workspaceRegistry.list();
5482
+ return (records.find((record) => normalizeWorkspaceLookupKey(record.workspaceId) === lookupKey) ??
5483
+ null);
5484
+ }
5485
+ emitWorkspaceRemoveForId(workspaceId) {
5486
+ const subscription = this.workspaceUpdatesSubscription;
5487
+ const id = workspaceId.trim();
5488
+ if (!subscription || !id) {
5489
+ return;
5490
+ }
5491
+ this.bufferOrEmitWorkspaceUpdate(subscription, {
5492
+ kind: "remove",
5493
+ id,
5494
+ });
5495
+ }
5496
+ emitWorkspaceRemoveAliasesForRequest(requestedWorkspaceId, persistedWorkspaceId) {
5497
+ this.emitWorkspaceRemoveForId(persistedWorkspaceId);
5498
+ const requested = requestedWorkspaceId.trim();
5499
+ if (!requested || requested === persistedWorkspaceId) {
5500
+ return;
5501
+ }
5502
+ if (normalizeWorkspaceLookupKey(requestedWorkspaceId) !==
5503
+ normalizeWorkspaceLookupKey(persistedWorkspaceId)) {
5504
+ return;
5505
+ }
5506
+ this.emitWorkspaceRemoveForId(requested);
5507
+ }
4913
5508
  async reconcileAndEmitWorkspaceUpdates() {
4914
5509
  if (!this.workspaceUpdatesSubscription) {
4915
5510
  return;
@@ -5125,8 +5720,30 @@ export class Session {
5125
5720
  });
5126
5721
  }
5127
5722
  }
5723
+ async resolveAvailableEditorTargets() {
5724
+ return listAvailableEditorTargets();
5725
+ }
5726
+ async getMemoizedAvailableEditorTargets() {
5727
+ const cached = this.availableEditorTargetsCache.get(AVAILABLE_EDITOR_TARGETS_CACHE_KEY);
5728
+ if (cached) {
5729
+ return cached;
5730
+ }
5731
+ if (this.availableEditorTargetsInflight) {
5732
+ return this.availableEditorTargetsInflight;
5733
+ }
5734
+ const inflight = this.resolveAvailableEditorTargets()
5735
+ .then((editors) => {
5736
+ this.availableEditorTargetsCache.set(AVAILABLE_EDITOR_TARGETS_CACHE_KEY, editors);
5737
+ return editors;
5738
+ })
5739
+ .finally(() => {
5740
+ this.availableEditorTargetsInflight = null;
5741
+ });
5742
+ this.availableEditorTargetsInflight = inflight;
5743
+ return inflight;
5744
+ }
5128
5745
  async getAvailableEditorTargets() {
5129
- return this.filterEditorsForClient(await listAvailableEditorTargets());
5746
+ return this.filterEditorsForClient(await this.getMemoizedAvailableEditorTargets());
5130
5747
  }
5131
5748
  async openEditorTarget(options) {
5132
5749
  await openInEditorTarget(options);
@@ -5194,6 +5811,7 @@ export class Session {
5194
5811
  describeWorkspaceRecord: (workspace) => this.describeWorkspaceRecordWithGitData(workspace),
5195
5812
  emit: (message) => this.emit(message),
5196
5813
  registerPendingWorktreeWorkspace: (options) => this.registerPendingWorktreeWorkspace(options),
5814
+ syncWorkspaceGitWatchTarget: (cwd, syncOptions) => this.syncWorkspaceGitWatchTarget(cwd, syncOptions),
5197
5815
  sessionLogger: this.sessionLogger,
5198
5816
  createSeaworkWorktreeInBackground: (options) => this.createSeaworkWorktreeInBackground(options),
5199
5817
  }, request);
@@ -5320,14 +5938,27 @@ export class Session {
5320
5938
  }
5321
5939
  async handleArchiveWorkspaceRequest(request) {
5322
5940
  try {
5323
- const existing = await this.workspaceRegistry.get(request.workspaceId);
5324
- if (!existing || existing.archivedAt) {
5941
+ const existing = await this.findWorkspaceRecordForRequest(request.workspaceId);
5942
+ if (!existing) {
5325
5943
  this.emit({
5326
5944
  type: "archive_workspace_response",
5327
5945
  payload: {
5328
5946
  requestId: request.requestId,
5329
5947
  workspaceId: request.workspaceId,
5330
- archivedAt: existing?.archivedAt ?? null,
5948
+ archivedAt: null,
5949
+ error: null,
5950
+ },
5951
+ });
5952
+ return;
5953
+ }
5954
+ if (existing.archivedAt) {
5955
+ this.emitWorkspaceRemoveAliasesForRequest(request.workspaceId, existing.workspaceId);
5956
+ this.emit({
5957
+ type: "archive_workspace_response",
5958
+ payload: {
5959
+ requestId: request.requestId,
5960
+ workspaceId: request.workspaceId,
5961
+ archivedAt: existing.archivedAt,
5331
5962
  error: null,
5332
5963
  },
5333
5964
  });
@@ -5337,8 +5968,8 @@ export class Session {
5337
5968
  throw new Error("Use worktree archive for Seawork worktrees");
5338
5969
  }
5339
5970
  const archivedAt = new Date().toISOString();
5340
- await this.archiveWorkspaceRecord(request.workspaceId, archivedAt);
5341
- await this.emitWorkspaceUpdatesForWorkspaceIds([request.workspaceId]);
5971
+ await this.archiveWorkspaceRecord(existing.workspaceId, archivedAt);
5972
+ this.emitWorkspaceRemoveAliasesForRequest(request.workspaceId, existing.workspaceId);
5342
5973
  this.emit({
5343
5974
  type: "archive_workspace_response",
5344
5975
  payload: {
@@ -5397,7 +6028,7 @@ export class Session {
5397
6028
  const requestedLimit = msg.limit;
5398
6029
  const limit = requestedLimit ?? (direction === "after" ? 0 : undefined);
5399
6030
  const shouldLimitByProjectedWindow = projection === "canonical" &&
5400
- direction === "tail" &&
6031
+ (direction === "tail" || direction === "before") &&
5401
6032
  typeof requestedLimit === "number" &&
5402
6033
  requestedLimit > 0;
5403
6034
  const cursor = msg.cursor
@@ -5433,13 +6064,17 @@ export class Session {
5433
6064
  });
5434
6065
  while (timeline.hasOlder) {
5435
6066
  const needsMoreProjectedEntries = projectedWindow.projectedEntries.length < projectedLimit;
5436
- const firstLoadedRow = timeline.rows[0];
5437
- const firstSelectedRow = projectedWindow.selectedRows[0];
5438
- const startsAtLoadedBoundary = firstLoadedRow != null &&
5439
- firstSelectedRow != null &&
5440
- firstSelectedRow.seq === firstLoadedRow.seq;
5441
- const boundaryIsAssistantChunk = startsAtLoadedBoundary && firstLoadedRow.item.type === "assistant_message";
5442
- if (!needsMoreProjectedEntries && !boundaryIsAssistantChunk) {
6067
+ // Both tail and before select the newest projected entries within the
6068
+ // loaded window, so a canonical-limit cut can only land at the OLDEST
6069
+ // end (rows[0]); the newest end abuts the cursor and is chunk-safe.
6070
+ const oldestLoadedRow = timeline.rows[0];
6071
+ const oldestSelectedRow = projectedWindow.selectedRows[0];
6072
+ const selectedStartsAtOldestLoadedBoundary = oldestLoadedRow != null &&
6073
+ oldestSelectedRow != null &&
6074
+ oldestSelectedRow.seq === oldestLoadedRow.seq;
6075
+ const oldestBoundaryIsAssistantChunk = selectedStartsAtOldestLoadedBoundary &&
6076
+ oldestLoadedRow.item.type === "assistant_message";
6077
+ if (!needsMoreProjectedEntries && !oldestBoundaryIsAssistantChunk) {
5443
6078
  break;
5444
6079
  }
5445
6080
  const maxRows = Math.max(0, timeline.window.maxSeq - timeline.window.minSeq + 1);
@@ -5462,12 +6097,16 @@ export class Session {
5462
6097
  });
5463
6098
  }
5464
6099
  const selectedRows = projectedWindow.selectedRows;
5465
- entries = projectTimelineRows(selectedRows, snapshot.provider, projection);
6100
+ entries = projectTimelineRows(selectedRows, snapshot.provider, projection, {
6101
+ ...legacyTimelineProjectionOptions(this.appVersion),
6102
+ });
5466
6103
  if (projectedWindow.minSeq !== null && projectedWindow.maxSeq !== null) {
5467
6104
  startCursor = { epoch: timeline.epoch, seq: projectedWindow.minSeq };
5468
6105
  endCursor = { epoch: timeline.epoch, seq: projectedWindow.maxSeq };
5469
6106
  hasOlder = projectedWindow.minSeq > timeline.window.minSeq;
5470
- hasNewer = false;
6107
+ // For tail this is always false (the window IS the newest rows); for
6108
+ // before it is true whenever newer rows remain above the page.
6109
+ hasNewer = projectedWindow.maxSeq < timeline.window.maxSeq;
5471
6110
  }
5472
6111
  }
5473
6112
  else {
@@ -5475,7 +6114,9 @@ export class Session {
5475
6114
  const lastRow = timeline.rows[timeline.rows.length - 1];
5476
6115
  startCursor = firstRow ? { epoch: timeline.epoch, seq: firstRow.seq } : null;
5477
6116
  endCursor = lastRow ? { epoch: timeline.epoch, seq: lastRow.seq } : null;
5478
- entries = projectTimelineRows(timeline.rows, snapshot.provider, projection);
6117
+ entries = projectTimelineRows(timeline.rows, snapshot.provider, projection, {
6118
+ ...legacyTimelineProjectionOptions(this.appVersion),
6119
+ });
5479
6120
  }
5480
6121
  this.emit({
5481
6122
  type: "fetch_agent_timeline_response",
@@ -5500,7 +6141,62 @@ export class Session {
5500
6141
  });
5501
6142
  }
5502
6143
  catch (error) {
5503
- this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle fetch_agent_timeline_request");
6144
+ // Workspaces on external/removable drives can vanish between sessions;
6145
+ // this is a known/expected condition for resume requests and shouldn't
6146
+ // pollute the daemon log at ERROR. See issue #662 / #632.
6147
+ const message = error instanceof Error ? error.message : String(error);
6148
+ const isMissingCwd = /Working directory does not exist/.test(message);
6149
+ let offlineTimeline = null;
6150
+ try {
6151
+ offlineTimeline = await this.loadOfflineTimelineForStoredAgent({
6152
+ agentId: msg.agentId,
6153
+ direction,
6154
+ cursor,
6155
+ limit: shouldLimitByProjectedWindow && typeof requestedLimit === "number" ? 0 : limit,
6156
+ });
6157
+ }
6158
+ catch (offlineError) {
6159
+ this.sessionLogger.warn({ err: offlineError, agentId: msg.agentId }, "Failed to load offline persisted agent timeline");
6160
+ }
6161
+ if (offlineTimeline) {
6162
+ const projected = projectOfflineTimelineFetchResponse({
6163
+ model: offlineTimeline.model,
6164
+ direction,
6165
+ projection,
6166
+ shouldLimitByProjectedWindow,
6167
+ requestedLimit,
6168
+ projectionOptions: legacyTimelineProjectionOptions(this.appVersion),
6169
+ });
6170
+ this.sessionLogger.warn({ err: error, agentId: msg.agentId, provider: offlineTimeline.model.agentProvider }, "Loaded persisted agent timeline without resuming provider session");
6171
+ this.emit({
6172
+ type: "fetch_agent_timeline_response",
6173
+ payload: {
6174
+ requestId: msg.requestId,
6175
+ agentId: msg.agentId,
6176
+ agent: this.buildStoredAgentPayload(offlineTimeline.record),
6177
+ direction,
6178
+ projection,
6179
+ epoch: offlineTimeline.model.epoch,
6180
+ reset: offlineTimeline.model.reset,
6181
+ staleCursor: offlineTimeline.model.staleCursor,
6182
+ gap: offlineTimeline.model.gap,
6183
+ window: offlineTimeline.model.window,
6184
+ startCursor: projected.startCursor,
6185
+ endCursor: projected.endCursor,
6186
+ hasOlder: projected.hasOlder,
6187
+ hasNewer: projected.hasNewer,
6188
+ entries: projected.entries,
6189
+ error: null,
6190
+ },
6191
+ });
6192
+ return;
6193
+ }
6194
+ if (isMissingCwd) {
6195
+ this.sessionLogger.warn({ err: error, agentId: msg.agentId }, "Workspace offline while loading agent timeline");
6196
+ }
6197
+ else {
6198
+ this.sessionLogger.error({ err: error, agentId: msg.agentId }, "Failed to handle fetch_agent_timeline_request");
6199
+ }
5504
6200
  this.emit({
5505
6201
  type: "fetch_agent_timeline_response",
5506
6202
  payload: {
@@ -5555,6 +6251,7 @@ export class Session {
5555
6251
  try {
5556
6252
  this.agentManager.recordUserMessage(agentId, msg.text, {
5557
6253
  messageId: msg.messageId,
6254
+ images: msg.imageAttachments,
5558
6255
  emitState: false,
5559
6256
  });
5560
6257
  }
@@ -5562,6 +6259,22 @@ export class Session {
5562
6259
  this.sessionLogger.error({ err: error, agentId }, "Failed to record user message for send_agent_message_request");
5563
6260
  }
5564
6261
  const prompt = this.buildAgentPrompt(msg.text, msg.images, msg.documents);
6262
+ if (typeof prompt === "string") {
6263
+ const oob = await this.agentManager.tryRunOutOfBand(agentId, prompt);
6264
+ if (oob) {
6265
+ await this.agentManager.appendTimelineItem(agentId, {
6266
+ type: "assistant_message",
6267
+ text: oob.response,
6268
+ });
6269
+ this.agentManager.emitAgentState(agentId);
6270
+ latencyTracker.dropPending(agentId);
6271
+ this.emit({
6272
+ type: "send_agent_message_response",
6273
+ payload: { requestId: msg.requestId, agentId, accepted: true, error: null },
6274
+ });
6275
+ return;
6276
+ }
6277
+ }
5565
6278
  this.sessionLogger.info({
5566
6279
  agentId,
5567
6280
  messageId: msg.messageId,
@@ -6609,6 +7322,38 @@ export class Session {
6609
7322
  this.emitScheduleRpcError(request, error);
6610
7323
  }
6611
7324
  }
7325
+ async handleScheduleUpdateRequest(request) {
7326
+ try {
7327
+ const schedule = await this.scheduleService.update(request.scheduleId, request.patch);
7328
+ this.emit({
7329
+ type: "schedule/update/response",
7330
+ payload: {
7331
+ requestId: request.requestId,
7332
+ schedule: this.toScheduleSummary(schedule),
7333
+ error: null,
7334
+ },
7335
+ });
7336
+ }
7337
+ catch (error) {
7338
+ this.emitScheduleRpcError(request, error);
7339
+ }
7340
+ }
7341
+ async handleScheduleRunNowRequest(request) {
7342
+ try {
7343
+ const run = await this.scheduleService.triggerNow(request.scheduleId);
7344
+ this.emit({
7345
+ type: "schedule/run-now/response",
7346
+ payload: {
7347
+ requestId: request.requestId,
7348
+ run,
7349
+ error: null,
7350
+ },
7351
+ });
7352
+ }
7353
+ catch (error) {
7354
+ this.emitScheduleRpcError(request, error);
7355
+ }
7356
+ }
6612
7357
  emitLoopRpcError(request, error) {
6613
7358
  const message = error instanceof Error ? error.message : String(error);
6614
7359
  this.sessionLogger.error({ err: error, requestType: request.type }, "Loop request failed");
@@ -7137,4 +7882,72 @@ export class Session {
7137
7882
  // Stash handlers
7138
7883
  // ---------------------------------------------------------------------------
7139
7884
  Session.SEAWORK_STASH_PREFIX = "seawork-auto-stash:";
7885
+ function formatUnknownError(error) {
7886
+ if (error instanceof Error) {
7887
+ return error.message;
7888
+ }
7889
+ if (typeof error === "string") {
7890
+ return error;
7891
+ }
7892
+ return "Unknown error";
7893
+ }
7894
+ function normalizeNonEmptyStrings(values) {
7895
+ return (values ?? []).map((value) => value.trim()).filter(Boolean);
7896
+ }
7897
+ function mergeUniqueNonEmptyStrings(current, incoming) {
7898
+ return [
7899
+ ...new Set([...normalizeNonEmptyStrings(current), ...normalizeNonEmptyStrings(incoming)]),
7900
+ ];
7901
+ }
7902
+ function rehydratePatchWeComOpenClawSecret(input) {
7903
+ const incoming = input.patch.integrations?.wecomOpenClaw;
7904
+ if (!incoming || incoming.secret !== PROVIDER_ENV_VALUE_REDACTED) {
7905
+ return input.patch;
7906
+ }
7907
+ const currentSecret = input.current.integrations?.wecomOpenClaw?.secret;
7908
+ if (!currentSecret) {
7909
+ return input.patch;
7910
+ }
7911
+ return {
7912
+ ...input.patch,
7913
+ integrations: {
7914
+ ...input.patch.integrations,
7915
+ wecomOpenClaw: {
7916
+ ...incoming,
7917
+ secret: currentSecret,
7918
+ },
7919
+ },
7920
+ };
7921
+ }
7922
+ function resolvePatchWeComOpenClawAgentCwd(input) {
7923
+ const incoming = input.patch.integrations?.wecomOpenClaw;
7924
+ if (!incoming || incoming.cwd === undefined) {
7925
+ return input.patch;
7926
+ }
7927
+ return {
7928
+ ...input.patch,
7929
+ integrations: {
7930
+ ...input.patch.integrations,
7931
+ wecomOpenClaw: {
7932
+ ...incoming,
7933
+ cwd: resolveWeComOpenClawAgentCwd(incoming.cwd, input.seaworkHome),
7934
+ },
7935
+ },
7936
+ };
7937
+ }
7938
+ // Normalize per-project cwds (tilde-expand + path.resolve) at storage time so
7939
+ // they match the agent's already-expanded cwd in resolvePromptInjection.
7940
+ function resolvePatchPromptInjectionCwds(patch) {
7941
+ const projects = patch.promptInjection?.projects;
7942
+ if (!projects) {
7943
+ return patch;
7944
+ }
7945
+ return {
7946
+ ...patch,
7947
+ promptInjection: {
7948
+ ...patch.promptInjection,
7949
+ projects: projects.map((p) => ({ ...p, cwd: resolve(expandTilde(p.cwd)) })),
7950
+ },
7951
+ };
7952
+ }
7140
7953
  //# sourceMappingURL=session.js.map