@geminixiang/mikan 1.0.0-beta.9 → 1.0.0-beta.91

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 (998) hide show
  1. package/CHANGELOG.md +342 -424
  2. package/README.md +102 -42
  3. package/dist/adapters/commands/admin.d.ts.map +1 -0
  4. package/dist/adapters/commands/admin.js +33 -0
  5. package/dist/adapters/commands/admin.js.map +1 -0
  6. package/dist/adapters/commands/auto-reply.d.ts +5 -0
  7. package/dist/adapters/commands/auto-reply.d.ts.map +1 -0
  8. package/dist/adapters/commands/auto-reply.js +47 -0
  9. package/dist/adapters/commands/auto-reply.js.map +1 -0
  10. package/dist/adapters/commands/login.d.ts +6 -0
  11. package/dist/adapters/commands/login.d.ts.map +1 -0
  12. package/dist/adapters/commands/login.js +158 -0
  13. package/dist/adapters/commands/login.js.map +1 -0
  14. package/dist/adapters/commands/manifest.d.ts +17 -0
  15. package/dist/adapters/commands/manifest.d.ts.map +1 -0
  16. package/dist/adapters/commands/manifest.js +121 -0
  17. package/dist/adapters/commands/manifest.js.map +1 -0
  18. package/dist/adapters/commands/model.d.ts +9 -0
  19. package/dist/adapters/commands/model.d.ts.map +1 -0
  20. package/dist/adapters/commands/model.js +136 -0
  21. package/dist/adapters/commands/model.js.map +1 -0
  22. package/dist/adapters/commands/new.d.ts.map +1 -0
  23. package/dist/adapters/commands/new.js +39 -0
  24. package/dist/adapters/commands/new.js.map +1 -0
  25. package/dist/adapters/commands/registry.d.ts +4 -0
  26. package/dist/adapters/commands/registry.d.ts.map +1 -0
  27. package/dist/adapters/commands/registry.js +35 -0
  28. package/dist/adapters/commands/registry.js.map +1 -0
  29. package/dist/adapters/commands/sandbox.d.ts +6 -0
  30. package/dist/adapters/commands/sandbox.d.ts.map +1 -0
  31. package/dist/adapters/commands/sandbox.js +125 -0
  32. package/dist/adapters/commands/sandbox.js.map +1 -0
  33. package/dist/adapters/commands/session-view.d.ts.map +1 -0
  34. package/dist/adapters/commands/session-view.js +46 -0
  35. package/dist/adapters/commands/session-view.js.map +1 -0
  36. package/dist/adapters/commands/types.d.ts +105 -0
  37. package/dist/adapters/commands/types.d.ts.map +1 -0
  38. package/dist/adapters/commands/types.js.map +1 -0
  39. package/dist/adapters/commands/utils.d.ts +11 -0
  40. package/dist/adapters/commands/utils.d.ts.map +1 -0
  41. package/dist/adapters/commands/utils.js +43 -0
  42. package/dist/adapters/commands/utils.js.map +1 -0
  43. package/dist/adapters/discord/bot.d.ts +14 -18
  44. package/dist/adapters/discord/bot.d.ts.map +1 -1
  45. package/dist/adapters/discord/bot.js +234 -303
  46. package/dist/adapters/discord/bot.js.map +1 -1
  47. package/dist/adapters/discord/components.d.ts +4 -0
  48. package/dist/adapters/discord/components.d.ts.map +1 -0
  49. package/dist/adapters/discord/components.js +9 -0
  50. package/dist/adapters/discord/components.js.map +1 -0
  51. package/dist/adapters/discord/context.d.ts +3 -7
  52. package/dist/adapters/discord/context.d.ts.map +1 -1
  53. package/dist/adapters/discord/context.js +57 -219
  54. package/dist/adapters/discord/context.js.map +1 -1
  55. package/dist/adapters/discord/format.d.ts +2 -0
  56. package/dist/adapters/discord/format.d.ts.map +1 -0
  57. package/dist/adapters/discord/format.js +47 -0
  58. package/dist/adapters/discord/format.js.map +1 -0
  59. package/dist/adapters/discord/types.d.ts +91 -1
  60. package/dist/adapters/discord/types.d.ts.map +1 -1
  61. package/dist/adapters/discord/types.js.map +1 -1
  62. package/dist/adapters/github/activity.d.ts +3 -0
  63. package/dist/adapters/github/activity.d.ts.map +1 -0
  64. package/dist/adapters/github/activity.js +148 -0
  65. package/dist/adapters/github/activity.js.map +1 -0
  66. package/dist/adapters/github/bot.d.ts +24 -37
  67. package/dist/adapters/github/bot.d.ts.map +1 -1
  68. package/dist/adapters/github/bot.js +209 -397
  69. package/dist/adapters/github/bot.js.map +1 -1
  70. package/dist/adapters/github/client.d.ts +32 -49
  71. package/dist/adapters/github/client.d.ts.map +1 -1
  72. package/dist/adapters/github/client.js +108 -118
  73. package/dist/adapters/github/client.js.map +1 -1
  74. package/dist/adapters/github/context.d.ts +3 -8
  75. package/dist/adapters/github/context.d.ts.map +1 -1
  76. package/dist/adapters/github/context.js +74 -138
  77. package/dist/adapters/github/context.js.map +1 -1
  78. package/dist/adapters/github/github-ops.d.ts +14 -0
  79. package/dist/adapters/github/github-ops.d.ts.map +1 -0
  80. package/dist/adapters/github/github-ops.js +218 -0
  81. package/dist/adapters/github/github-ops.js.map +1 -0
  82. package/dist/adapters/github/ids.d.ts +2 -23
  83. package/dist/adapters/github/ids.d.ts.map +1 -1
  84. package/dist/adapters/github/ids.js +6 -36
  85. package/dist/adapters/github/ids.js.map +1 -1
  86. package/dist/adapters/github/policy.d.ts +19 -0
  87. package/dist/adapters/github/policy.d.ts.map +1 -0
  88. package/dist/adapters/github/policy.js +79 -0
  89. package/dist/adapters/github/policy.js.map +1 -0
  90. package/dist/adapters/github/tool-pack.d.ts +4 -0
  91. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  92. package/dist/adapters/github/tool-pack.js +40 -0
  93. package/dist/adapters/github/tool-pack.js.map +1 -0
  94. package/dist/adapters/github/tools/checks.d.ts +14 -0
  95. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  96. package/dist/adapters/github/tools/checks.js +78 -0
  97. package/dist/adapters/github/tools/checks.js.map +1 -0
  98. package/dist/adapters/github/tools/issue.d.ts +18 -0
  99. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  100. package/dist/adapters/github/tools/issue.js +44 -0
  101. package/dist/adapters/github/tools/issue.js.map +1 -0
  102. package/dist/adapters/github/tools/pr.d.ts +17 -0
  103. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  104. package/dist/adapters/github/tools/pr.js +40 -0
  105. package/dist/adapters/github/tools/pr.js.map +1 -0
  106. package/dist/adapters/github/tools/read.d.ts +17 -0
  107. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  108. package/dist/adapters/github/tools/read.js +134 -0
  109. package/dist/adapters/github/tools/read.js.map +1 -0
  110. package/dist/adapters/github/tools/review-reply.d.ts +14 -0
  111. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  112. package/dist/adapters/github/tools/review-reply.js +34 -0
  113. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  114. package/dist/adapters/github/types.d.ts +200 -57
  115. package/dist/adapters/github/types.d.ts.map +1 -1
  116. package/dist/adapters/github/types.js.map +1 -1
  117. package/dist/adapters/github/webhook.d.ts +7 -0
  118. package/dist/adapters/github/webhook.d.ts.map +1 -0
  119. package/dist/adapters/github/webhook.js +104 -0
  120. package/dist/adapters/github/webhook.js.map +1 -0
  121. package/dist/adapters/intake.d.ts +4 -10
  122. package/dist/adapters/intake.d.ts.map +1 -1
  123. package/dist/adapters/intake.js +85 -32
  124. package/dist/adapters/intake.js.map +1 -1
  125. package/dist/adapters/markdown-tables.d.ts +8 -0
  126. package/dist/adapters/markdown-tables.d.ts.map +1 -0
  127. package/dist/adapters/markdown-tables.js +111 -0
  128. package/dist/adapters/markdown-tables.js.map +1 -0
  129. package/dist/adapters/messages.d.ts +13 -0
  130. package/dist/adapters/messages.d.ts.map +1 -0
  131. package/dist/adapters/messages.js +39 -0
  132. package/dist/adapters/messages.js.map +1 -0
  133. package/dist/adapters/progressive-renderer.d.ts +7 -0
  134. package/dist/adapters/progressive-renderer.d.ts.map +1 -0
  135. package/dist/adapters/progressive-renderer.js +426 -0
  136. package/dist/adapters/progressive-renderer.js.map +1 -0
  137. package/dist/adapters/shared.d.ts +27 -53
  138. package/dist/adapters/shared.d.ts.map +1 -1
  139. package/dist/adapters/shared.js +167 -91
  140. package/dist/adapters/shared.js.map +1 -1
  141. package/dist/adapters/slack/assistant.d.ts +21 -0
  142. package/dist/adapters/slack/assistant.d.ts.map +1 -0
  143. package/dist/adapters/slack/assistant.js +103 -0
  144. package/dist/adapters/slack/assistant.js.map +1 -0
  145. package/dist/adapters/slack/auto-reply-context.d.ts +14 -0
  146. package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
  147. package/dist/adapters/slack/auto-reply-context.js +21 -0
  148. package/dist/adapters/slack/auto-reply-context.js.map +1 -0
  149. package/dist/adapters/slack/blocks.d.ts +5 -0
  150. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  151. package/dist/adapters/slack/blocks.js +144 -145
  152. package/dist/adapters/slack/blocks.js.map +1 -1
  153. package/dist/adapters/slack/bot.d.ts +60 -40
  154. package/dist/adapters/slack/bot.d.ts.map +1 -1
  155. package/dist/adapters/slack/bot.js +968 -588
  156. package/dist/adapters/slack/bot.js.map +1 -1
  157. package/dist/adapters/slack/context.d.ts +3 -9
  158. package/dist/adapters/slack/context.d.ts.map +1 -1
  159. package/dist/adapters/slack/context.js +8 -17
  160. package/dist/adapters/slack/context.js.map +1 -1
  161. package/dist/adapters/slack/jev-context.d.ts +17 -0
  162. package/dist/adapters/slack/jev-context.d.ts.map +1 -0
  163. package/dist/adapters/slack/jev-context.js +57 -0
  164. package/dist/adapters/slack/jev-context.js.map +1 -0
  165. package/dist/adapters/slack/markdown.d.ts +2 -0
  166. package/dist/adapters/slack/markdown.d.ts.map +1 -0
  167. package/dist/adapters/slack/markdown.js +43 -0
  168. package/dist/adapters/slack/markdown.js.map +1 -0
  169. package/dist/adapters/slack/persona.d.ts +6 -0
  170. package/dist/adapters/slack/persona.d.ts.map +1 -0
  171. package/dist/adapters/slack/persona.js +13 -0
  172. package/dist/adapters/slack/persona.js.map +1 -0
  173. package/dist/adapters/slack/response-lifecycle.d.ts +3 -4
  174. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  175. package/dist/adapters/slack/response-lifecycle.js +222 -348
  176. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  177. package/dist/adapters/slack/session.d.ts +3 -2
  178. package/dist/adapters/slack/session.d.ts.map +1 -1
  179. package/dist/adapters/slack/session.js +9 -13
  180. package/dist/adapters/slack/session.js.map +1 -1
  181. package/dist/adapters/slack/stream-limits.d.ts +10 -0
  182. package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
  183. package/dist/adapters/slack/stream-limits.js +22 -0
  184. package/dist/adapters/slack/stream-limits.js.map +1 -0
  185. package/dist/adapters/slack/task-intent.d.ts +19 -0
  186. package/dist/adapters/slack/task-intent.d.ts.map +1 -0
  187. package/dist/adapters/slack/task-intent.js +65 -0
  188. package/dist/adapters/slack/task-intent.js.map +1 -0
  189. package/dist/adapters/slack/task-status.d.ts +7 -0
  190. package/dist/adapters/slack/task-status.d.ts.map +1 -0
  191. package/dist/adapters/slack/task-status.js +96 -0
  192. package/dist/adapters/slack/task-status.js.map +1 -0
  193. package/dist/adapters/slack/tool-pack.d.ts +4 -0
  194. package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
  195. package/dist/adapters/slack/tool-pack.js +40 -0
  196. package/dist/adapters/slack/tool-pack.js.map +1 -0
  197. package/dist/adapters/slack/tools/blockkit.d.ts +17 -0
  198. package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
  199. package/dist/adapters/slack/tools/blockkit.js +98 -0
  200. package/dist/adapters/slack/tools/blockkit.js.map +1 -0
  201. package/dist/adapters/slack/types.d.ts +91 -5
  202. package/dist/adapters/slack/types.d.ts.map +1 -1
  203. package/dist/adapters/slack/types.js.map +1 -1
  204. package/dist/adapters/slack/update-diagnostics.d.ts +13 -0
  205. package/dist/adapters/slack/update-diagnostics.d.ts.map +1 -0
  206. package/dist/adapters/slack/update-diagnostics.js +128 -0
  207. package/dist/adapters/slack/update-diagnostics.js.map +1 -0
  208. package/dist/adapters/telegram/bot.d.ts +12 -19
  209. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  210. package/dist/adapters/telegram/bot.js +114 -239
  211. package/dist/adapters/telegram/bot.js.map +1 -1
  212. package/dist/adapters/telegram/context.d.ts +3 -7
  213. package/dist/adapters/telegram/context.d.ts.map +1 -1
  214. package/dist/adapters/telegram/context.js +49 -191
  215. package/dist/adapters/telegram/context.js.map +1 -1
  216. package/dist/adapters/telegram/types.d.ts +24 -1
  217. package/dist/adapters/telegram/types.d.ts.map +1 -1
  218. package/dist/adapters/telegram/types.js.map +1 -1
  219. package/dist/adapters/types.d.ts +92 -17
  220. package/dist/adapters/types.d.ts.map +1 -1
  221. package/dist/adapters/types.js.map +1 -1
  222. package/dist/adapters/web/admin/client-assets.d.ts +4 -0
  223. package/dist/adapters/web/admin/client-assets.d.ts.map +1 -0
  224. package/dist/adapters/web/admin/client-assets.js +1766 -0
  225. package/dist/adapters/web/admin/client-assets.js.map +1 -0
  226. package/dist/adapters/web/admin/portal.d.ts +26 -0
  227. package/dist/adapters/web/admin/portal.d.ts.map +1 -0
  228. package/dist/adapters/web/admin/portal.js +1490 -0
  229. package/dist/adapters/web/admin/portal.js.map +1 -0
  230. package/dist/adapters/web/admin/provider-models.d.ts +10 -0
  231. package/dist/adapters/web/admin/provider-models.d.ts.map +1 -0
  232. package/dist/adapters/web/admin/provider-models.js +103 -0
  233. package/dist/adapters/web/admin/provider-models.js.map +1 -0
  234. package/dist/adapters/web/admin/types.d.ts +54 -0
  235. package/dist/adapters/web/admin/types.d.ts.map +1 -0
  236. package/dist/adapters/web/admin/types.js.map +1 -0
  237. package/dist/adapters/web/login/oauth.d.ts +4 -0
  238. package/dist/adapters/web/login/oauth.d.ts.map +1 -0
  239. package/dist/adapters/web/login/oauth.js +219 -0
  240. package/dist/adapters/web/login/oauth.js.map +1 -0
  241. package/dist/adapters/web/login/portal.d.ts +10 -0
  242. package/dist/adapters/web/login/portal.d.ts.map +1 -0
  243. package/dist/adapters/web/login/portal.js +1336 -0
  244. package/dist/adapters/web/login/portal.js.map +1 -0
  245. package/dist/adapters/web/login/types.d.ts +34 -0
  246. package/dist/adapters/web/login/types.d.ts.map +1 -0
  247. package/dist/adapters/web/login/types.js.map +1 -0
  248. package/dist/adapters/web/portal-shell.d.ts +9 -0
  249. package/dist/adapters/web/portal-shell.d.ts.map +1 -0
  250. package/dist/adapters/web/portal-shell.js +428 -0
  251. package/dist/adapters/web/portal-shell.js.map +1 -0
  252. package/dist/adapters/web/server.d.ts +35 -0
  253. package/dist/adapters/web/server.d.ts.map +1 -0
  254. package/dist/adapters/web/server.js +80 -0
  255. package/dist/adapters/web/server.js.map +1 -0
  256. package/dist/adapters/web/session-view/portal.d.ts +19 -0
  257. package/dist/adapters/web/session-view/portal.d.ts.map +1 -0
  258. package/dist/adapters/web/session-view/portal.js +1833 -0
  259. package/dist/adapters/web/session-view/portal.js.map +1 -0
  260. package/dist/adapters/web/session-view/types.d.ts +45 -0
  261. package/dist/adapters/web/session-view/types.d.ts.map +1 -0
  262. package/dist/adapters/web/session-view/types.js.map +1 -0
  263. package/dist/adapters/web/token-store.d.ts +10 -0
  264. package/dist/adapters/web/token-store.d.ts.map +1 -0
  265. package/dist/adapters/web/token-store.js +42 -0
  266. package/dist/adapters/web/token-store.js.map +1 -0
  267. package/dist/adapters/web/types.d.ts.map +1 -0
  268. package/dist/adapters/web/types.js.map +1 -0
  269. package/dist/cli/arg-grammar.d.ts +8 -0
  270. package/dist/cli/arg-grammar.d.ts.map +1 -0
  271. package/dist/cli/arg-grammar.js +47 -0
  272. package/dist/cli/arg-grammar.js.map +1 -0
  273. package/dist/cli/boot.d.ts +4 -0
  274. package/dist/cli/boot.d.ts.map +1 -0
  275. package/dist/cli/boot.js +79 -0
  276. package/dist/cli/boot.js.map +1 -0
  277. package/dist/cli/download.d.ts.map +1 -0
  278. package/dist/cli/download.js +84 -0
  279. package/dist/cli/download.js.map +1 -0
  280. package/dist/cli/migrate.d.ts +3 -0
  281. package/dist/cli/migrate.d.ts.map +1 -0
  282. package/dist/cli/migrate.js +74 -0
  283. package/dist/cli/migrate.js.map +1 -0
  284. package/dist/cli/office.d.ts +2 -0
  285. package/dist/cli/office.d.ts.map +1 -0
  286. package/dist/cli/office.js +30 -0
  287. package/dist/cli/office.js.map +1 -0
  288. package/dist/cli/onboard.d.ts +7 -0
  289. package/dist/cli/onboard.d.ts.map +1 -0
  290. package/dist/cli/onboard.js +192 -0
  291. package/dist/cli/onboard.js.map +1 -0
  292. package/dist/cli/process-lifecycle.d.ts +14 -0
  293. package/dist/cli/process-lifecycle.d.ts.map +1 -0
  294. package/dist/cli/process-lifecycle.js +61 -0
  295. package/dist/cli/process-lifecycle.js.map +1 -0
  296. package/dist/cli/types.d.ts +20 -0
  297. package/dist/cli/types.d.ts.map +1 -0
  298. package/dist/cli/types.js.map +1 -0
  299. package/dist/env-manifest.d.ts +14 -0
  300. package/dist/env-manifest.d.ts.map +1 -0
  301. package/dist/env-manifest.js +255 -0
  302. package/dist/env-manifest.js.map +1 -0
  303. package/dist/events/index.d.ts +105 -0
  304. package/dist/events/index.d.ts.map +1 -0
  305. package/dist/events/index.js +252 -0
  306. package/dist/events/index.js.map +1 -0
  307. package/dist/events/scheduler.d.ts +24 -0
  308. package/dist/events/scheduler.d.ts.map +1 -0
  309. package/dist/events/scheduler.js +221 -0
  310. package/dist/events/scheduler.js.map +1 -0
  311. package/dist/file-guards.d.ts +20 -0
  312. package/dist/file-guards.d.ts.map +1 -0
  313. package/dist/file-guards.js +166 -0
  314. package/dist/file-guards.js.map +1 -0
  315. package/dist/harness/budget-stop.d.ts +3 -0
  316. package/dist/harness/budget-stop.d.ts.map +1 -0
  317. package/dist/harness/budget-stop.js +36 -0
  318. package/dist/harness/budget-stop.js.map +1 -0
  319. package/dist/harness/execution-env.d.ts +4 -0
  320. package/dist/harness/execution-env.d.ts.map +1 -0
  321. package/dist/harness/execution-env.js +245 -0
  322. package/dist/harness/execution-env.js.map +1 -0
  323. package/dist/harness/execution-resolver.d.ts +23 -0
  324. package/dist/harness/execution-resolver.d.ts.map +1 -0
  325. package/dist/harness/execution-resolver.js +152 -0
  326. package/dist/harness/execution-resolver.js.map +1 -0
  327. package/dist/harness/http.d.ts +0 -12
  328. package/dist/harness/http.d.ts.map +1 -1
  329. package/dist/harness/http.js +0 -34
  330. package/dist/harness/http.js.map +1 -1
  331. package/dist/harness/index.d.ts +18 -17
  332. package/dist/harness/index.d.ts.map +1 -1
  333. package/dist/harness/index.js +11 -15
  334. package/dist/harness/index.js.map +1 -1
  335. package/dist/harness/jev.d.ts +67 -0
  336. package/dist/harness/jev.d.ts.map +1 -0
  337. package/dist/harness/jev.js +135 -0
  338. package/dist/harness/jev.js.map +1 -0
  339. package/dist/harness/loop-guard.d.ts +10 -0
  340. package/dist/harness/loop-guard.d.ts.map +1 -0
  341. package/dist/harness/loop-guard.js +111 -0
  342. package/dist/harness/loop-guard.js.map +1 -0
  343. package/dist/harness/mcp-result.d.ts +21 -0
  344. package/dist/harness/mcp-result.d.ts.map +1 -0
  345. package/dist/harness/mcp-result.js +204 -0
  346. package/dist/harness/mcp-result.js.map +1 -0
  347. package/dist/harness/mcp.d.ts +17 -0
  348. package/dist/harness/mcp.d.ts.map +1 -0
  349. package/dist/harness/mcp.js +339 -0
  350. package/dist/harness/mcp.js.map +1 -0
  351. package/dist/harness/models.d.ts +2 -19
  352. package/dist/harness/models.d.ts.map +1 -1
  353. package/dist/harness/models.js +43 -95
  354. package/dist/harness/models.js.map +1 -1
  355. package/dist/harness/open-connector.d.ts +3 -0
  356. package/dist/harness/open-connector.d.ts.map +1 -0
  357. package/dist/harness/open-connector.js +106 -0
  358. package/dist/harness/open-connector.js.map +1 -0
  359. package/dist/harness/presenter.d.ts +11 -0
  360. package/dist/harness/presenter.d.ts.map +1 -0
  361. package/dist/harness/presenter.js +867 -0
  362. package/dist/harness/presenter.js.map +1 -0
  363. package/dist/harness/prompt.d.ts +17 -0
  364. package/dist/harness/prompt.d.ts.map +1 -0
  365. package/dist/harness/prompt.js +348 -0
  366. package/dist/harness/prompt.js.map +1 -0
  367. package/dist/harness/run-events.d.ts +10 -0
  368. package/dist/harness/run-events.d.ts.map +1 -0
  369. package/dist/harness/run-events.js +126 -0
  370. package/dist/harness/run-events.js.map +1 -0
  371. package/dist/harness/runner.d.ts +2 -123
  372. package/dist/harness/runner.d.ts.map +1 -1
  373. package/dist/harness/runner.js +570 -414
  374. package/dist/harness/runner.js.map +1 -1
  375. package/dist/harness/session.d.ts +102 -0
  376. package/dist/harness/session.d.ts.map +1 -0
  377. package/dist/harness/session.js +725 -0
  378. package/dist/harness/session.js.map +1 -0
  379. package/dist/harness/skills.d.ts +7 -41
  380. package/dist/harness/skills.d.ts.map +1 -1
  381. package/dist/harness/skills.js +120 -69
  382. package/dist/harness/skills.js.map +1 -1
  383. package/dist/harness/subagent-profiles.d.ts +3 -0
  384. package/dist/harness/subagent-profiles.d.ts.map +1 -0
  385. package/dist/harness/subagent-profiles.js +260 -0
  386. package/dist/harness/subagent-profiles.js.map +1 -0
  387. package/dist/harness/subagent.d.ts +39 -0
  388. package/dist/harness/subagent.d.ts.map +1 -0
  389. package/dist/harness/subagent.js +613 -0
  390. package/dist/harness/subagent.js.map +1 -0
  391. package/dist/harness/tool-args.d.ts +2 -0
  392. package/dist/harness/tool-args.d.ts.map +1 -0
  393. package/dist/harness/tool-args.js +20 -0
  394. package/dist/harness/tool-args.js.map +1 -0
  395. package/dist/harness/tools/attach.d.ts +13 -0
  396. package/dist/harness/tools/attach.d.ts.map +1 -0
  397. package/dist/harness/tools/attach.js +39 -0
  398. package/dist/harness/tools/attach.js.map +1 -0
  399. package/dist/harness/tools/codemode.d.ts +5 -0
  400. package/dist/harness/tools/codemode.d.ts.map +1 -0
  401. package/dist/harness/tools/codemode.js +229 -0
  402. package/dist/harness/tools/codemode.js.map +1 -0
  403. package/dist/harness/tools/event.d.ts +28 -0
  404. package/dist/harness/tools/event.d.ts.map +1 -0
  405. package/dist/harness/tools/event.js +183 -0
  406. package/dist/harness/tools/event.js.map +1 -0
  407. package/dist/harness/tools/generate-image.d.ts +20 -0
  408. package/dist/harness/tools/generate-image.d.ts.map +1 -0
  409. package/dist/harness/tools/generate-image.js +82 -0
  410. package/dist/harness/tools/generate-image.js.map +1 -0
  411. package/dist/harness/tools/history.d.ts +20 -0
  412. package/dist/harness/tools/history.d.ts.map +1 -0
  413. package/dist/harness/tools/history.js +293 -0
  414. package/dist/harness/tools/history.js.map +1 -0
  415. package/dist/harness/tools/host-fn-tool.d.ts +14 -0
  416. package/dist/harness/tools/host-fn-tool.d.ts.map +1 -0
  417. package/dist/harness/tools/host-fn-tool.js +39 -0
  418. package/dist/harness/tools/host-fn-tool.js.map +1 -0
  419. package/dist/harness/tools/index.d.ts +34 -0
  420. package/dist/harness/tools/index.d.ts.map +1 -0
  421. package/dist/harness/tools/index.js +49 -0
  422. package/dist/harness/tools/index.js.map +1 -0
  423. package/dist/harness/tools/jev-browser.d.ts +41 -0
  424. package/dist/harness/tools/jev-browser.d.ts.map +1 -0
  425. package/dist/harness/tools/jev-browser.js +570 -0
  426. package/dist/harness/tools/jev-browser.js.map +1 -0
  427. package/dist/harness/tools/jev.d.ts +15 -0
  428. package/dist/harness/tools/jev.d.ts.map +1 -0
  429. package/dist/harness/tools/jev.js +98 -0
  430. package/dist/harness/tools/jev.js.map +1 -0
  431. package/dist/harness/tools/pi-tools.d.ts +8 -0
  432. package/dist/harness/tools/pi-tools.d.ts.map +1 -0
  433. package/dist/harness/tools/pi-tools.js +83 -0
  434. package/dist/harness/tools/pi-tools.js.map +1 -0
  435. package/dist/harness/tools/react.d.ts +12 -0
  436. package/dist/harness/tools/react.d.ts.map +1 -0
  437. package/dist/harness/tools/react.js +25 -0
  438. package/dist/harness/tools/react.js.map +1 -0
  439. package/dist/harness/tools/sandbox.d.ts +24 -0
  440. package/dist/harness/tools/sandbox.d.ts.map +1 -0
  441. package/dist/harness/tools/sandbox.js +79 -0
  442. package/dist/harness/tools/sandbox.js.map +1 -0
  443. package/dist/harness/tools/secret-redaction.d.ts +9 -0
  444. package/dist/harness/tools/secret-redaction.d.ts.map +1 -0
  445. package/dist/harness/tools/secret-redaction.js +88 -0
  446. package/dist/harness/tools/secret-redaction.js.map +1 -0
  447. package/dist/harness/tools/subagent.d.ts +51 -0
  448. package/dist/harness/tools/subagent.d.ts.map +1 -0
  449. package/dist/harness/tools/subagent.js +581 -0
  450. package/dist/harness/tools/subagent.js.map +1 -0
  451. package/dist/harness/tools/task.d.ts +22 -0
  452. package/dist/harness/tools/task.d.ts.map +1 -0
  453. package/dist/harness/tools/task.js +80 -0
  454. package/dist/harness/tools/task.js.map +1 -0
  455. package/dist/harness/tools/tool-search.d.ts +6 -0
  456. package/dist/harness/tools/tool-search.d.ts.map +1 -0
  457. package/dist/harness/tools/tool-search.js +89 -0
  458. package/dist/harness/tools/tool-search.js.map +1 -0
  459. package/dist/harness/tools/types.d.ts +14 -0
  460. package/dist/harness/tools/types.d.ts.map +1 -0
  461. package/dist/harness/tools/types.js.map +1 -0
  462. package/dist/harness/types.d.ts +488 -29
  463. package/dist/harness/types.d.ts.map +1 -1
  464. package/dist/harness/types.js +1 -1
  465. package/dist/harness/types.js.map +1 -1
  466. package/dist/index.d.ts +31 -9
  467. package/dist/index.d.ts.map +1 -1
  468. package/dist/index.js +15 -7
  469. package/dist/index.js.map +1 -1
  470. package/dist/log.d.ts +0 -4
  471. package/dist/log.d.ts.map +1 -1
  472. package/dist/log.js +3 -28
  473. package/dist/log.js.map +1 -1
  474. package/dist/main.js +282 -258
  475. package/dist/main.js.map +1 -1
  476. package/dist/memory-capture/index.d.ts +15 -0
  477. package/dist/memory-capture/index.d.ts.map +1 -0
  478. package/dist/memory-capture/index.js +198 -0
  479. package/dist/memory-capture/index.js.map +1 -0
  480. package/dist/memory-capture/types.d.ts +27 -0
  481. package/dist/memory-capture/types.d.ts.map +1 -0
  482. package/dist/memory-capture/types.js +2 -0
  483. package/dist/memory-capture/types.js.map +1 -0
  484. package/dist/migrations/actor-vaults.d.ts +3 -0
  485. package/dist/migrations/actor-vaults.d.ts.map +1 -0
  486. package/dist/migrations/actor-vaults.js +43 -0
  487. package/dist/migrations/actor-vaults.js.map +1 -0
  488. package/dist/migrations/conversation-settings.d.ts +3 -0
  489. package/dist/migrations/conversation-settings.d.ts.map +1 -0
  490. package/dist/migrations/conversation-settings.js +31 -0
  491. package/dist/migrations/conversation-settings.js.map +1 -0
  492. package/dist/migrations/dream-checkpoints.d.ts +3 -0
  493. package/dist/migrations/dream-checkpoints.d.ts.map +1 -0
  494. package/dist/migrations/dream-checkpoints.js +23 -0
  495. package/dist/migrations/dream-checkpoints.js.map +1 -0
  496. package/dist/migrations/index.d.ts +14 -0
  497. package/dist/migrations/index.d.ts.map +1 -0
  498. package/dist/migrations/index.js +105 -0
  499. package/dist/migrations/index.js.map +1 -0
  500. package/dist/migrations/models-json.d.ts +3 -0
  501. package/dist/migrations/models-json.d.ts.map +1 -0
  502. package/dist/migrations/models-json.js +18 -0
  503. package/dist/migrations/models-json.js.map +1 -0
  504. package/dist/migrations/office-layout.d.ts +4 -0
  505. package/dist/migrations/office-layout.d.ts.map +1 -0
  506. package/dist/migrations/office-layout.js +94 -0
  507. package/dist/migrations/office-layout.js.map +1 -0
  508. package/dist/migrations/office-sessions.d.ts +3 -0
  509. package/dist/migrations/office-sessions.d.ts.map +1 -0
  510. package/dist/migrations/office-sessions.js +81 -0
  511. package/dist/migrations/office-sessions.js.map +1 -0
  512. package/dist/migrations/private-office-dirs.d.ts +3 -0
  513. package/dist/migrations/private-office-dirs.d.ts.map +1 -0
  514. package/dist/migrations/private-office-dirs.js +24 -0
  515. package/dist/migrations/private-office-dirs.js.map +1 -0
  516. package/dist/migrations/sandbox-containers.d.ts +3 -0
  517. package/dist/migrations/sandbox-containers.d.ts.map +1 -0
  518. package/dist/migrations/sandbox-containers.js +36 -0
  519. package/dist/migrations/sandbox-containers.js.map +1 -0
  520. package/dist/migrations/session-files.d.ts +28 -0
  521. package/dist/migrations/session-files.d.ts.map +1 -0
  522. package/dist/migrations/session-files.js +80 -0
  523. package/dist/migrations/session-files.js.map +1 -0
  524. package/dist/migrations/session-v4.d.ts +77 -0
  525. package/dist/migrations/session-v4.d.ts.map +1 -0
  526. package/dist/migrations/session-v4.js +177 -0
  527. package/dist/migrations/session-v4.js.map +1 -0
  528. package/dist/migrations/sessions-sqlite.d.ts +3 -0
  529. package/dist/migrations/sessions-sqlite.d.ts.map +1 -0
  530. package/dist/migrations/sessions-sqlite.js +215 -0
  531. package/dist/migrations/sessions-sqlite.js.map +1 -0
  532. package/dist/migrations/sessions-v3.d.ts +8 -0
  533. package/dist/migrations/sessions-v3.d.ts.map +1 -0
  534. package/dist/migrations/sessions-v3.js +350 -0
  535. package/dist/migrations/sessions-v3.js.map +1 -0
  536. package/dist/migrations/types.d.ts +25 -0
  537. package/dist/migrations/types.d.ts.map +1 -0
  538. package/dist/migrations/types.js +2 -0
  539. package/dist/migrations/types.js.map +1 -0
  540. package/dist/migrations/workspace-events.d.ts +3 -0
  541. package/dist/migrations/workspace-events.d.ts.map +1 -0
  542. package/dist/migrations/workspace-events.js +43 -0
  543. package/dist/migrations/workspace-events.js.map +1 -0
  544. package/dist/observability/index.d.ts +30 -0
  545. package/dist/observability/index.d.ts.map +1 -0
  546. package/dist/observability/index.js +273 -0
  547. package/dist/observability/index.js.map +1 -0
  548. package/dist/observability/instrument.js +9 -6
  549. package/dist/observability/instrument.js.map +1 -1
  550. package/dist/observability/otel.d.ts +10 -0
  551. package/dist/observability/otel.d.ts.map +1 -0
  552. package/dist/observability/otel.js +182 -0
  553. package/dist/observability/otel.js.map +1 -0
  554. package/dist/observability/sentry.d.ts +30 -18
  555. package/dist/observability/sentry.d.ts.map +1 -1
  556. package/dist/observability/sentry.js +94 -119
  557. package/dist/observability/sentry.js.map +1 -1
  558. package/dist/observability/types.d.ts +30 -19
  559. package/dist/observability/types.d.ts.map +1 -1
  560. package/dist/observability/types.js.map +1 -1
  561. package/dist/office/index.d.ts +49 -0
  562. package/dist/office/index.d.ts.map +1 -0
  563. package/dist/office/index.js +395 -0
  564. package/dist/office/index.js.map +1 -0
  565. package/dist/office/projection.d.ts +11 -0
  566. package/dist/office/projection.d.ts.map +1 -0
  567. package/dist/office/projection.js +145 -0
  568. package/dist/office/projection.js.map +1 -0
  569. package/dist/office/types.d.ts +44 -0
  570. package/dist/office/types.d.ts.map +1 -0
  571. package/dist/office/types.js +2 -0
  572. package/dist/office/types.js.map +1 -0
  573. package/dist/runtime/conversation-runtime.d.ts +0 -1
  574. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  575. package/dist/runtime/conversation-runtime.js +365 -155
  576. package/dist/runtime/conversation-runtime.js.map +1 -1
  577. package/dist/runtime/session-lifecycle.d.ts +65 -0
  578. package/dist/runtime/session-lifecycle.d.ts.map +1 -0
  579. package/dist/runtime/session-lifecycle.js +384 -0
  580. package/dist/runtime/session-lifecycle.js.map +1 -0
  581. package/dist/runtime/types.d.ts +42 -20
  582. package/dist/runtime/types.d.ts.map +1 -1
  583. package/dist/runtime/types.js.map +1 -1
  584. package/dist/sandbox/cloudflare.d.ts +4 -0
  585. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  586. package/dist/sandbox/cloudflare.js +59 -55
  587. package/dist/sandbox/cloudflare.js.map +1 -1
  588. package/dist/sandbox/container.d.ts +3 -0
  589. package/dist/sandbox/container.d.ts.map +1 -1
  590. package/dist/sandbox/container.js +39 -20
  591. package/dist/sandbox/container.js.map +1 -1
  592. package/dist/sandbox/host.d.ts +3 -0
  593. package/dist/sandbox/host.d.ts.map +1 -1
  594. package/dist/sandbox/host.js +42 -35
  595. package/dist/sandbox/host.js.map +1 -1
  596. package/dist/sandbox/identity.d.ts +12 -0
  597. package/dist/sandbox/identity.d.ts.map +1 -0
  598. package/dist/sandbox/identity.js +42 -0
  599. package/dist/sandbox/identity.js.map +1 -0
  600. package/dist/sandbox/index.d.ts +5 -15
  601. package/dist/sandbox/index.d.ts.map +1 -1
  602. package/dist/sandbox/index.js +5 -51
  603. package/dist/sandbox/index.js.map +1 -1
  604. package/dist/sandbox/layout.d.ts +5 -0
  605. package/dist/sandbox/layout.d.ts.map +1 -0
  606. package/dist/sandbox/layout.js +13 -0
  607. package/dist/sandbox/layout.js.map +1 -0
  608. package/dist/sandbox/provisioner.d.ts +64 -0
  609. package/dist/sandbox/provisioner.d.ts.map +1 -0
  610. package/dist/sandbox/provisioner.js +546 -0
  611. package/dist/sandbox/provisioner.js.map +1 -0
  612. package/dist/sandbox/registry.d.ts +12 -0
  613. package/dist/sandbox/registry.d.ts.map +1 -0
  614. package/dist/sandbox/registry.js +93 -0
  615. package/dist/sandbox/registry.js.map +1 -0
  616. package/dist/sandbox/types.d.ts +23 -25
  617. package/dist/sandbox/types.d.ts.map +1 -1
  618. package/dist/sandbox/types.js.map +1 -1
  619. package/dist/sandbox/utils.d.ts +21 -0
  620. package/dist/sandbox/utils.d.ts.map +1 -1
  621. package/dist/sandbox/utils.js +100 -8
  622. package/dist/sandbox/utils.js.map +1 -1
  623. package/dist/sessions/chat-history-sync.d.ts +19 -0
  624. package/dist/sessions/chat-history-sync.d.ts.map +1 -0
  625. package/dist/sessions/chat-history-sync.js +459 -0
  626. package/dist/sessions/chat-history-sync.js.map +1 -0
  627. package/dist/sessions/compaction-summary.d.ts +4 -0
  628. package/dist/sessions/compaction-summary.d.ts.map +1 -0
  629. package/dist/sessions/compaction-summary.js +16 -0
  630. package/dist/sessions/compaction-summary.js.map +1 -0
  631. package/dist/sessions/history-line.d.ts +10 -0
  632. package/dist/sessions/history-line.d.ts.map +1 -0
  633. package/dist/sessions/history-line.js +29 -0
  634. package/dist/sessions/history-line.js.map +1 -0
  635. package/dist/sessions/session-key.d.ts +19 -0
  636. package/dist/sessions/session-key.d.ts.map +1 -0
  637. package/dist/sessions/session-key.js +91 -0
  638. package/dist/sessions/session-key.js.map +1 -0
  639. package/dist/sessions/session-store.d.ts +73 -0
  640. package/dist/sessions/session-store.d.ts.map +1 -0
  641. package/dist/sessions/session-store.js +721 -0
  642. package/dist/sessions/session-store.js.map +1 -0
  643. package/dist/sessions/types.d.ts +90 -34
  644. package/dist/sessions/types.d.ts.map +1 -1
  645. package/dist/sessions/types.js +2 -1
  646. package/dist/sessions/types.js.map +1 -1
  647. package/dist/settings/apply.d.ts +9 -0
  648. package/dist/settings/apply.d.ts.map +1 -0
  649. package/dist/settings/apply.js +31 -0
  650. package/dist/settings/apply.js.map +1 -0
  651. package/dist/settings/index.d.ts +28 -0
  652. package/dist/settings/index.d.ts.map +1 -0
  653. package/dist/settings/index.js +322 -0
  654. package/dist/settings/index.js.map +1 -0
  655. package/dist/types.d.ts +209 -191
  656. package/dist/types.d.ts.map +1 -1
  657. package/dist/types.js +1 -3
  658. package/dist/types.js.map +1 -1
  659. package/dist/unknown-values.d.ts +3 -0
  660. package/dist/unknown-values.d.ts.map +1 -0
  661. package/dist/unknown-values.js +7 -0
  662. package/dist/unknown-values.js.map +1 -0
  663. package/dist/vault/index.d.ts +28 -11
  664. package/dist/vault/index.d.ts.map +1 -1
  665. package/dist/vault/index.js +291 -118
  666. package/dist/vault/index.js.map +1 -1
  667. package/dist/vault/types.d.ts +5 -19
  668. package/dist/vault/types.d.ts.map +1 -1
  669. package/dist/vault/types.js.map +1 -1
  670. package/package.json +87 -41
  671. package/dist/adapter.d.ts +0 -2
  672. package/dist/adapter.d.ts.map +0 -1
  673. package/dist/adapter.js +0 -2
  674. package/dist/adapter.js.map +0 -1
  675. package/dist/adapters/github/repo.d.ts +0 -19
  676. package/dist/adapters/github/repo.d.ts.map +0 -1
  677. package/dist/adapters/github/repo.js +0 -58
  678. package/dist/adapters/github/repo.js.map +0 -1
  679. package/dist/adapters/slack/tools/attach.d.ts +0 -12
  680. package/dist/adapters/slack/tools/attach.d.ts.map +0 -1
  681. package/dist/adapters/slack/tools/attach.js +0 -39
  682. package/dist/adapters/slack/tools/attach.js.map +0 -1
  683. package/dist/adapters/streaming.d.ts +0 -18
  684. package/dist/adapters/streaming.d.ts.map +0 -1
  685. package/dist/adapters/streaming.js +0 -44
  686. package/dist/adapters/streaming.js.map +0 -1
  687. package/dist/adapters/telegram/html.d.ts +0 -3
  688. package/dist/adapters/telegram/html.d.ts.map +0 -1
  689. package/dist/adapters/telegram/html.js +0 -98
  690. package/dist/adapters/telegram/html.js.map +0 -1
  691. package/dist/agent-events.d.ts +0 -5
  692. package/dist/agent-events.d.ts.map +0 -1
  693. package/dist/agent-events.js +0 -26
  694. package/dist/agent-events.js.map +0 -1
  695. package/dist/agent.d.ts +0 -39
  696. package/dist/agent.d.ts.map +0 -1
  697. package/dist/agent.js +0 -1299
  698. package/dist/agent.js.map +0 -1
  699. package/dist/cli/ext-git.d.ts +0 -22
  700. package/dist/cli/ext-git.d.ts.map +0 -1
  701. package/dist/cli/ext-git.js +0 -76
  702. package/dist/cli/ext-git.js.map +0 -1
  703. package/dist/cli/ext.d.ts +0 -2
  704. package/dist/cli/ext.d.ts.map +0 -1
  705. package/dist/cli/ext.js +0 -217
  706. package/dist/cli/ext.js.map +0 -1
  707. package/dist/commands/admin.d.ts.map +0 -1
  708. package/dist/commands/admin.js +0 -30
  709. package/dist/commands/admin.js.map +0 -1
  710. package/dist/commands/auto-reply.d.ts +0 -8
  711. package/dist/commands/auto-reply.d.ts.map +0 -1
  712. package/dist/commands/auto-reply.js +0 -76
  713. package/dist/commands/auto-reply.js.map +0 -1
  714. package/dist/commands/extensions.d.ts +0 -14
  715. package/dist/commands/extensions.d.ts.map +0 -1
  716. package/dist/commands/extensions.js +0 -55
  717. package/dist/commands/extensions.js.map +0 -1
  718. package/dist/commands/login.d.ts +0 -5
  719. package/dist/commands/login.d.ts.map +0 -1
  720. package/dist/commands/login.js +0 -115
  721. package/dist/commands/login.js.map +0 -1
  722. package/dist/commands/model.d.ts +0 -10
  723. package/dist/commands/model.d.ts.map +0 -1
  724. package/dist/commands/model.js +0 -102
  725. package/dist/commands/model.js.map +0 -1
  726. package/dist/commands/new.d.ts.map +0 -1
  727. package/dist/commands/new.js +0 -25
  728. package/dist/commands/new.js.map +0 -1
  729. package/dist/commands/parse.d.ts +0 -7
  730. package/dist/commands/parse.d.ts.map +0 -1
  731. package/dist/commands/parse.js +0 -14
  732. package/dist/commands/parse.js.map +0 -1
  733. package/dist/commands/registry.d.ts +0 -5
  734. package/dist/commands/registry.d.ts.map +0 -1
  735. package/dist/commands/registry.js +0 -31
  736. package/dist/commands/registry.js.map +0 -1
  737. package/dist/commands/sandbox.d.ts +0 -7
  738. package/dist/commands/sandbox.d.ts.map +0 -1
  739. package/dist/commands/sandbox.js +0 -78
  740. package/dist/commands/sandbox.js.map +0 -1
  741. package/dist/commands/session-view.d.ts.map +0 -1
  742. package/dist/commands/session-view.js +0 -41
  743. package/dist/commands/session-view.js.map +0 -1
  744. package/dist/commands/types.d.ts +0 -66
  745. package/dist/commands/types.d.ts.map +0 -1
  746. package/dist/commands/types.js.map +0 -1
  747. package/dist/commands/utils.d.ts +0 -11
  748. package/dist/commands/utils.d.ts.map +0 -1
  749. package/dist/commands/utils.js +0 -29
  750. package/dist/commands/utils.js.map +0 -1
  751. package/dist/config.d.ts +0 -78
  752. package/dist/config.d.ts.map +0 -1
  753. package/dist/config.js +0 -450
  754. package/dist/config.js.map +0 -1
  755. package/dist/download.d.ts.map +0 -1
  756. package/dist/download.js +0 -89
  757. package/dist/download.js.map +0 -1
  758. package/dist/events.d.ts +0 -43
  759. package/dist/events.d.ts.map +0 -1
  760. package/dist/events.js +0 -456
  761. package/dist/events.js.map +0 -1
  762. package/dist/execution-resolver.d.ts +0 -21
  763. package/dist/execution-resolver.d.ts.map +0 -1
  764. package/dist/execution-resolver.js +0 -170
  765. package/dist/execution-resolver.js.map +0 -1
  766. package/dist/harness/auth.d.ts +0 -16
  767. package/dist/harness/auth.d.ts.map +0 -1
  768. package/dist/harness/auth.js +0 -86
  769. package/dist/harness/auth.js.map +0 -1
  770. package/dist/harness/extensions/loader.d.ts +0 -86
  771. package/dist/harness/extensions/loader.d.ts.map +0 -1
  772. package/dist/harness/extensions/loader.js +0 -525
  773. package/dist/harness/extensions/loader.js.map +0 -1
  774. package/dist/harness/extensions/registry.d.ts +0 -23
  775. package/dist/harness/extensions/registry.d.ts.map +0 -1
  776. package/dist/harness/extensions/registry.js +0 -45
  777. package/dist/harness/extensions/registry.js.map +0 -1
  778. package/dist/harness/extensions/types.d.ts +0 -248
  779. package/dist/harness/extensions/types.d.ts.map +0 -1
  780. package/dist/harness/extensions/types.js.map +0 -1
  781. package/dist/harness/session-store.d.ts +0 -77
  782. package/dist/harness/session-store.d.ts.map +0 -1
  783. package/dist/harness/session-store.js +0 -248
  784. package/dist/harness/session-store.js.map +0 -1
  785. package/dist/harness/settings.d.ts +0 -47
  786. package/dist/harness/settings.d.ts.map +0 -1
  787. package/dist/harness/settings.js +0 -29
  788. package/dist/harness/settings.js.map +0 -1
  789. package/dist/model-registry.d.ts +0 -4
  790. package/dist/model-registry.d.ts.map +0 -1
  791. package/dist/model-registry.js +0 -7
  792. package/dist/model-registry.js.map +0 -1
  793. package/dist/platform-messages.d.ts +0 -12
  794. package/dist/platform-messages.d.ts.map +0 -1
  795. package/dist/platform-messages.js +0 -36
  796. package/dist/platform-messages.js.map +0 -1
  797. package/dist/portal-shell.d.ts +0 -4
  798. package/dist/portal-shell.d.ts.map +0 -1
  799. package/dist/portal-shell.js +0 -366
  800. package/dist/portal-shell.js.map +0 -1
  801. package/dist/provisioner.d.ts +0 -61
  802. package/dist/provisioner.d.ts.map +0 -1
  803. package/dist/provisioner.js +0 -517
  804. package/dist/provisioner.js.map +0 -1
  805. package/dist/runtime/agent-run-controller.d.ts +0 -31
  806. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  807. package/dist/runtime/agent-run-controller.js +0 -195
  808. package/dist/runtime/agent-run-controller.js.map +0 -1
  809. package/dist/sandbox/errors.d.ts +0 -6
  810. package/dist/sandbox/errors.d.ts.map +0 -1
  811. package/dist/sandbox/errors.js +0 -11
  812. package/dist/sandbox/errors.js.map +0 -1
  813. package/dist/sandbox/firecracker.d.ts +0 -15
  814. package/dist/sandbox/firecracker.d.ts.map +0 -1
  815. package/dist/sandbox/firecracker.js +0 -212
  816. package/dist/sandbox/firecracker.js.map +0 -1
  817. package/dist/sandbox/image.d.ts +0 -3
  818. package/dist/sandbox/image.d.ts.map +0 -1
  819. package/dist/sandbox/image.js +0 -27
  820. package/dist/sandbox/image.js.map +0 -1
  821. package/dist/sandbox/path-context.d.ts +0 -3
  822. package/dist/sandbox/path-context.d.ts.map +0 -1
  823. package/dist/sandbox/path-context.js +0 -20
  824. package/dist/sandbox/path-context.js.map +0 -1
  825. package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
  826. package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
  827. package/dist/sessions/agent-memory-file-manager.js +0 -484
  828. package/dist/sessions/agent-memory-file-manager.js.map +0 -1
  829. package/dist/sessions/metadata.d.ts +0 -3
  830. package/dist/sessions/metadata.d.ts.map +0 -1
  831. package/dist/sessions/metadata.js +0 -11
  832. package/dist/sessions/metadata.js.map +0 -1
  833. package/dist/sessions/policy.d.ts +0 -6
  834. package/dist/sessions/policy.d.ts.map +0 -1
  835. package/dist/sessions/policy.js +0 -24
  836. package/dist/sessions/policy.js.map +0 -1
  837. package/dist/sessions/store.d.ts +0 -72
  838. package/dist/sessions/store.d.ts.map +0 -1
  839. package/dist/sessions/store.js +0 -193
  840. package/dist/sessions/store.js.map +0 -1
  841. package/dist/store.d.ts +0 -46
  842. package/dist/store.d.ts.map +0 -1
  843. package/dist/store.js +0 -177
  844. package/dist/store.js.map +0 -1
  845. package/dist/tools/bash.d.ts +0 -10
  846. package/dist/tools/bash.d.ts.map +0 -1
  847. package/dist/tools/bash.js +0 -80
  848. package/dist/tools/bash.js.map +0 -1
  849. package/dist/tools/edit.d.ts +0 -11
  850. package/dist/tools/edit.d.ts.map +0 -1
  851. package/dist/tools/edit.js +0 -131
  852. package/dist/tools/edit.js.map +0 -1
  853. package/dist/tools/event.d.ts +0 -56
  854. package/dist/tools/event.d.ts.map +0 -1
  855. package/dist/tools/event.js +0 -252
  856. package/dist/tools/event.js.map +0 -1
  857. package/dist/tools/github-pr.d.ts +0 -22
  858. package/dist/tools/github-pr.d.ts.map +0 -1
  859. package/dist/tools/github-pr.js +0 -53
  860. package/dist/tools/github-pr.js.map +0 -1
  861. package/dist/tools/index.d.ts +0 -25
  862. package/dist/tools/index.d.ts.map +0 -1
  863. package/dist/tools/index.js +0 -35
  864. package/dist/tools/index.js.map +0 -1
  865. package/dist/tools/react.d.ts +0 -15
  866. package/dist/tools/react.d.ts.map +0 -1
  867. package/dist/tools/react.js +0 -40
  868. package/dist/tools/react.js.map +0 -1
  869. package/dist/tools/read.d.ts +0 -11
  870. package/dist/tools/read.d.ts.map +0 -1
  871. package/dist/tools/read.js +0 -134
  872. package/dist/tools/read.js.map +0 -1
  873. package/dist/tools/sandbox.d.ts +0 -22
  874. package/dist/tools/sandbox.d.ts.map +0 -1
  875. package/dist/tools/sandbox.js +0 -73
  876. package/dist/tools/sandbox.js.map +0 -1
  877. package/dist/tools/truncate.d.ts +0 -33
  878. package/dist/tools/truncate.d.ts.map +0 -1
  879. package/dist/tools/truncate.js +0 -184
  880. package/dist/tools/truncate.js.map +0 -1
  881. package/dist/tools/types.d.ts +0 -54
  882. package/dist/tools/types.d.ts.map +0 -1
  883. package/dist/tools/types.js.map +0 -1
  884. package/dist/tools/write.d.ts +0 -10
  885. package/dist/tools/write.d.ts.map +0 -1
  886. package/dist/tools/write.js +0 -29
  887. package/dist/tools/write.js.map +0 -1
  888. package/dist/trigger.d.ts +0 -22
  889. package/dist/trigger.d.ts.map +0 -1
  890. package/dist/trigger.js +0 -100
  891. package/dist/trigger.js.map +0 -1
  892. package/dist/utils/date.d.ts +0 -10
  893. package/dist/utils/date.d.ts.map +0 -1
  894. package/dist/utils/date.js +0 -23
  895. package/dist/utils/date.js.map +0 -1
  896. package/dist/utils/env.d.ts +0 -3
  897. package/dist/utils/env.d.ts.map +0 -1
  898. package/dist/utils/env.js +0 -12
  899. package/dist/utils/env.js.map +0 -1
  900. package/dist/utils/file-guards.d.ts +0 -9
  901. package/dist/utils/file-guards.d.ts.map +0 -1
  902. package/dist/utils/file-guards.js +0 -59
  903. package/dist/utils/file-guards.js.map +0 -1
  904. package/dist/utils/fs-atomic.d.ts +0 -10
  905. package/dist/utils/fs-atomic.d.ts.map +0 -1
  906. package/dist/utils/fs-atomic.js +0 -45
  907. package/dist/utils/fs-atomic.js.map +0 -1
  908. package/dist/utils/html.d.ts +0 -2
  909. package/dist/utils/html.d.ts.map +0 -1
  910. package/dist/utils/html.js +0 -4
  911. package/dist/utils/html.js.map +0 -1
  912. package/dist/utils/http-body.d.ts +0 -10
  913. package/dist/utils/http-body.d.ts.map +0 -1
  914. package/dist/utils/http-body.js +0 -34
  915. package/dist/utils/http-body.js.map +0 -1
  916. package/dist/vault/routing.d.ts +0 -3
  917. package/dist/vault/routing.d.ts.map +0 -1
  918. package/dist/vault/routing.js +0 -13
  919. package/dist/vault/routing.js.map +0 -1
  920. package/dist/web/admin/portal.d.ts +0 -5
  921. package/dist/web/admin/portal.d.ts.map +0 -1
  922. package/dist/web/admin/portal.js +0 -2572
  923. package/dist/web/admin/portal.js.map +0 -1
  924. package/dist/web/admin/provider-models.d.ts +0 -10
  925. package/dist/web/admin/provider-models.d.ts.map +0 -1
  926. package/dist/web/admin/provider-models.js +0 -96
  927. package/dist/web/admin/provider-models.js.map +0 -1
  928. package/dist/web/admin/store.d.ts +0 -13
  929. package/dist/web/admin/store.d.ts.map +0 -1
  930. package/dist/web/admin/store.js +0 -14
  931. package/dist/web/admin/store.js.map +0 -1
  932. package/dist/web/admin/types.d.ts +0 -30
  933. package/dist/web/admin/types.d.ts.map +0 -1
  934. package/dist/web/admin/types.js.map +0 -1
  935. package/dist/web/agent-events/portal.d.ts +0 -3
  936. package/dist/web/agent-events/portal.d.ts.map +0 -1
  937. package/dist/web/agent-events/portal.js +0 -18
  938. package/dist/web/agent-events/portal.js.map +0 -1
  939. package/dist/web/login/oauth.d.ts +0 -6
  940. package/dist/web/login/oauth.d.ts.map +0 -1
  941. package/dist/web/login/oauth.js +0 -258
  942. package/dist/web/login/oauth.js.map +0 -1
  943. package/dist/web/login/portal.d.ts +0 -17
  944. package/dist/web/login/portal.d.ts.map +0 -1
  945. package/dist/web/login/portal.js +0 -1329
  946. package/dist/web/login/portal.js.map +0 -1
  947. package/dist/web/login/store.d.ts +0 -8
  948. package/dist/web/login/store.d.ts.map +0 -1
  949. package/dist/web/login/store.js +0 -15
  950. package/dist/web/login/store.js.map +0 -1
  951. package/dist/web/login/types.d.ts +0 -50
  952. package/dist/web/login/types.d.ts.map +0 -1
  953. package/dist/web/login/types.js.map +0 -1
  954. package/dist/web/request.d.ts +0 -3
  955. package/dist/web/request.d.ts.map +0 -1
  956. package/dist/web/request.js +0 -13
  957. package/dist/web/request.js.map +0 -1
  958. package/dist/web/server.d.ts +0 -28
  959. package/dist/web/server.d.ts.map +0 -1
  960. package/dist/web/server.js +0 -66
  961. package/dist/web/server.js.map +0 -1
  962. package/dist/web/session-view/command.d.ts +0 -4
  963. package/dist/web/session-view/command.d.ts.map +0 -1
  964. package/dist/web/session-view/command.js +0 -7
  965. package/dist/web/session-view/command.js.map +0 -1
  966. package/dist/web/session-view/portal.d.ts +0 -13
  967. package/dist/web/session-view/portal.d.ts.map +0 -1
  968. package/dist/web/session-view/portal.js +0 -1651
  969. package/dist/web/session-view/portal.js.map +0 -1
  970. package/dist/web/session-view/service.d.ts +0 -6
  971. package/dist/web/session-view/service.d.ts.map +0 -1
  972. package/dist/web/session-view/service.js +0 -498
  973. package/dist/web/session-view/service.js.map +0 -1
  974. package/dist/web/session-view/store.d.ts +0 -8
  975. package/dist/web/session-view/store.d.ts.map +0 -1
  976. package/dist/web/session-view/store.js +0 -15
  977. package/dist/web/session-view/store.js.map +0 -1
  978. package/dist/web/session-view/types.d.ts +0 -50
  979. package/dist/web/session-view/types.d.ts.map +0 -1
  980. package/dist/web/session-view/types.js.map +0 -1
  981. package/dist/web/token-store.d.ts +0 -13
  982. package/dist/web/token-store.d.ts.map +0 -1
  983. package/dist/web/token-store.js +0 -44
  984. package/dist/web/token-store.js.map +0 -1
  985. package/dist/web/types.d.ts.map +0 -1
  986. package/dist/web/types.js.map +0 -1
  987. /package/dist/{commands → adapters/commands}/admin.d.ts +0 -0
  988. /package/dist/{commands → adapters/commands}/new.d.ts +0 -0
  989. /package/dist/{commands → adapters/commands}/session-view.d.ts +0 -0
  990. /package/dist/{commands → adapters/commands}/types.js +0 -0
  991. /package/dist/{harness/extensions → adapters/web/admin}/types.js +0 -0
  992. /package/dist/{tools → adapters/web/login}/types.js +0 -0
  993. /package/dist/{web/admin → adapters/web/session-view}/types.js +0 -0
  994. /package/dist/{web → adapters/web}/types.d.ts +0 -0
  995. /package/dist/{web/login → adapters/web}/types.js +0 -0
  996. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
  997. /package/dist/{web/session-view → cli}/types.js +0 -0
  998. /package/dist/{web → harness/tools}/types.js +0 -0
@@ -1,22 +1,60 @@
1
1
  import { SocketModeClient } from "@slack/socket-mode";
2
- import { WebClient } from "@slack/web-api";
3
- import { existsSync, readFileSync } from "fs";
4
- import { readFile } from "fs/promises";
5
- import { basename, join } from "path";
6
- import { resolveConversationSettings } from "../../config.js";
2
+ import { WebAPIRateLimitedError, WebClient } from "@slack/web-api";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { basename } from "node:path";
5
+ import { createConversationEvent, createConversationMessage, createOfficeAddress, listRegisteredOffices, } from "../../office/index.js";
6
+ import { COMMAND_MANIFEST } from "../commands/manifest.js";
7
+ import { slackConversationAutoReplyMode, resolveConversationSettings, } from "../../settings/index.js";
8
+ import { evaluateWithJev, JevNotConfiguredError } from "../../harness/jev.js";
7
9
  import * as log from "../../log.js";
8
- import { PRODUCT_NAME, formatForceStopped, formatNothingRunning } from "../../platform-messages.js";
9
- import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, resolveOnlyScopedStopTarget, resolveStopTarget, withRetry, } from "../shared.js";
10
- import { processMessageIntake } from "../intake.js";
10
+ import { readTextFileNoFollowIfExists } from "../../file-guards.js";
11
+ import { PRODUCT_NAME, formatForceStopped } from "../messages.js";
12
+ import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, MessagingIntakeTracker, AttachmentRejectedError, saveIncomingAttachments, writeResponseToFile, withRetry, } from "../shared.js";
13
+ import { matchMagicWord, processMessageIntake } from "../intake.js";
14
+ import { readPlatformChannelKind, recordPlatformChannelKind } from "../../office/projection.js";
15
+ import { AssistantThreadRegistry, handleAgentContextChanged, handleAgentDmOpened, handleAssistantThreadStarted, titleAssistantThread, } from "./assistant.js";
11
16
  import { createSlackAdapters } from "./context.js";
12
- import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/agent-memory-file-manager.js";
17
+ import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/chat-history-sync.js";
18
+ import { conversationIdOf } from "../../sessions/session-key.js";
13
19
  import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorRun, resolveSlackSessionKey, } from "./session.js";
14
- import { reportUserFacingError } from "../../observability/sentry.js";
15
- import { renderSlackBlocks } from "./blocks.js";
20
+ import { reportUserFacingError } from "../../observability/index.js";
21
+ import { recordSlackUpdate } from "./update-diagnostics.js";
22
+ import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
23
+ import { querySlackTasks, formatTaskStatus, readTaskRoots, isTaskStatusQuestion, } from "./task-status.js";
24
+ import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
25
+ import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
26
+ import { StreamStartLimiter } from "./stream-limits.js";
27
+ import { errorMessage, isRecord } from "../../unknown-values.js";
16
28
  const SLACK_EVENT_ANCHOR_TEXT = "Working on it...";
17
- // Slack WebClient errors carry either `code: "rate_limited"` (retry-after) or
18
- // the legacy `data.error === "rate_limited"` / 429 status shape.
29
+ function hasMessageContent(message, includeBlocks = true) {
30
+ const content = [message.text, message.files];
31
+ if (includeBlocks)
32
+ content.push(message.blocks, message.attachments);
33
+ return content.some((part) => !!part?.length);
34
+ }
35
+ function hasBotIdentity(message) {
36
+ return !!message.bot_id || message.subtype === "bot_message";
37
+ }
38
+ const USER_MESSAGE_SUBTYPES = new Set([undefined, "file_share"]);
39
+ const BOT_MESSAGE_SUBTYPES = new Set([...USER_MESSAGE_SUBTYPES, "bot_message"]);
40
+ const MAX_STREAM_TEXT_CHARS = 12_000;
41
+ export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
42
+ if (text.length <= limit)
43
+ return [text];
44
+ const chunks = [];
45
+ for (let index = 0; index < text.length; index += limit) {
46
+ chunks.push(text.slice(index, index + limit));
47
+ }
48
+ return chunks;
49
+ }
50
+ function slackErrorCode(error) {
51
+ if (!isRecord(error) || !isRecord(error.data))
52
+ return undefined;
53
+ return typeof error.data.error === "string" ? error.data.error : undefined;
54
+ }
19
55
  function slackIsRateLimited(err) {
56
+ if (err instanceof WebAPIRateLimitedError)
57
+ return true;
20
58
  if (err.code === "rate_limited")
21
59
  return true;
22
60
  const data = err.data;
@@ -47,75 +85,200 @@ function collectSlackText(value, parts) {
47
85
  collectSlackText(obj.elements, parts);
48
86
  collectSlackText(obj.blocks, parts);
49
87
  }
88
+ function unescapeSlackText(text) {
89
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
90
+ }
50
91
  function buildSlackAppMessageText(event) {
51
92
  const parts = [];
52
93
  collectSlackText(event.text, parts);
53
94
  collectSlackText(event.blocks, parts);
54
95
  collectSlackText(event.attachments, parts);
55
96
  const deduped = parts.filter((part, index) => parts.indexOf(part) === index);
56
- return deduped.join("\n");
97
+ return unescapeSlackText(deduped.join("\n"));
98
+ }
99
+ export function italicizeLines(text) {
100
+ return text
101
+ .split("\n")
102
+ .map((line) => (line.trim() ? `_${line}_` : line))
103
+ .join("\n");
104
+ }
105
+ const RECENT_MESSAGE_KEYS = 2000;
106
+ function withLoggedAck(event, payload) {
107
+ return {
108
+ ...payload,
109
+ ack: () => payload.ack().catch((error) => {
110
+ log.logWarning(`Slack ack failed for ${event}; Slack may redeliver it`, errorMessage(error));
111
+ }),
112
+ };
57
113
  }
58
- // ---------------------------------------------------------------------------
59
- // Shared mrkdwn truncation helper
60
- // ---------------------------------------------------------------------------
61
114
  const MRKDWN_CONTEXT_TEXT_LIMIT = 3000;
62
- /**
63
- * Build a Slack context block whose text is capped at the mrkdwn limit.
64
- * Used for muted diagnostics and ephemeral command responses.
65
- */
66
115
  export function buildMrkdwnContextBlock(text) {
67
116
  const blockText = text.length > MRKDWN_CONTEXT_TEXT_LIMIT
68
117
  ? text.substring(0, MRKDWN_CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
69
118
  : text;
70
119
  return { type: "context", elements: [{ type: "mrkdwn", text: blockText }] };
71
120
  }
72
- // ============================================================================
73
- // SlackMessagingBot
74
- // ============================================================================
121
+ class AttachmentDownloadHttpError extends Error {
122
+ constructor(message, status) {
123
+ super(message);
124
+ this.status = status;
125
+ }
126
+ }
127
+ function isRetryableAttachmentDownloadError(error) {
128
+ if (error instanceof AttachmentRejectedError)
129
+ return false;
130
+ if (!(error instanceof AttachmentDownloadHttpError))
131
+ return true;
132
+ return error.status === 408 || error.status === 429 || error.status >= 500;
133
+ }
134
+ function assertSlackFileUrl(url) {
135
+ const { protocol, hostname } = new URL(url);
136
+ if (protocol === "https:" && (hostname === "slack.com" || hostname.endsWith(".slack.com")))
137
+ return;
138
+ throw new AttachmentRejectedError(`refusing to send the bot token to ${hostname}`);
139
+ }
75
140
  export class SlackMessagingBot {
141
+ office(channelId) {
142
+ return this.workspace.office(createOfficeAddress("slack", channelId));
143
+ }
144
+ conversationDir(channelId) {
145
+ return this.office(channelId).dir;
146
+ }
147
+ resolveReplyMode(address) {
148
+ return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
149
+ }
76
150
  createContext(event) {
77
- return createSlackAdapters(event, this, {
78
- replyMode: resolveConversationSettings(join(this.workingDir, event.conversationId)).slack?.replyMode ??
79
- "top-level",
151
+ const context = createSlackAdapters(event, this, {
152
+ replyMode: this.resolveReplyMode(event.address),
80
153
  });
154
+ if (event.conversationKind === "direct") {
155
+ context.responder.getTaskStatus = (key) => querySlackTasks(this.office(event.channel), event.channel, this.handler
156
+ .getRunningSessions()
157
+ .filter((s) => s.address.platform === "slack" && s.address.conversationId === event.channel), key);
158
+ }
159
+ if (event.conversationKind === "direct" &&
160
+ event.thread_ts &&
161
+ this.isTaskThread(event.channel, event.thread_ts)) {
162
+ let notified = false;
163
+ context.responder.notifyCompletion = async () => {
164
+ if (notified)
165
+ return;
166
+ const text = `<@${event.user}> 這一輪處理已結束,請查看上方結果。`;
167
+ const ts = await this.postInThread(event.channel, event.thread_ts, text);
168
+ notified = true;
169
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
170
+ };
171
+ }
172
+ if (event.conversationKind === "direct" && !event.thread_ts) {
173
+ context.responder.startTask = async (message, task) => {
174
+ if (this.stopped)
175
+ throw new Error("Slack is shutting down; task was not started.");
176
+ await context.responder.deleteResponse();
177
+ const root = await this.postMessage(event.channel, message);
178
+ this.logToFile(event.channel, {
179
+ date: new Date().toISOString(),
180
+ ts: root,
181
+ user: "bot",
182
+ text: message,
183
+ isMessagingBot: true,
184
+ taskRoot: true,
185
+ });
186
+ const sessionKey = resolveSlackSessionKey(event.channel, root);
187
+ try {
188
+ await registerThreadSession({
189
+ office: this.office(event.channel),
190
+ sessionKey,
191
+ });
192
+ const child = {
193
+ ...event,
194
+ ts: `task:${root}`,
195
+ thread_ts: root,
196
+ sessionKey,
197
+ text: task,
198
+ };
199
+ if (!this.getQueue(sessionKey).enqueue(async () => {
200
+ try {
201
+ await this.handler.handleEvent({
202
+ ...child,
203
+ attachments: child.attachments?.map((a) => ({
204
+ name: a.original,
205
+ localPath: a.localPath,
206
+ })),
207
+ }, this, this.createContext(child));
208
+ }
209
+ catch (error) {
210
+ reportUserFacingError(error, {
211
+ domain: "mikan",
212
+ surface: "task_handoff",
213
+ operation: "start_task_run",
214
+ severity: "error",
215
+ platform: "slack",
216
+ context: { conversationId: event.channel, sessionKey, threadTs: root },
217
+ });
218
+ await this.postInThread(event.channel, root, "Task could not start. Please reply here to try again.");
219
+ throw error;
220
+ }
221
+ }))
222
+ throw new Error("Task queue is closed.");
223
+ }
224
+ catch (error) {
225
+ await this.updateMessage(event.channel, root, `${message}\n\nTask could not start.`);
226
+ throw error;
227
+ }
228
+ return sessionKey;
229
+ };
230
+ }
231
+ return context;
81
232
  }
82
233
  constructor(handler, config) {
234
+ this.statusUpdates = new Map();
83
235
  this.botUserId = null;
84
236
  this.botId = null;
85
237
  this.teamId = null;
86
238
  this.ownMentionRegex = null;
87
- this.startupTs = null; // Messages older than this are just logged, not processed
239
+ this.startupTs = null;
88
240
  this.users = new Map();
89
241
  this.channels = new Map();
242
+ this.assistantThreads = new AssistantThreadRegistry();
243
+ this.streamStarts = new StreamStartLimiter();
244
+ this.stopped = false;
90
245
  this.queues = new Map();
91
- this.eventsWatcher = null;
246
+ this.intake = new MessagingIntakeTracker("Slack");
247
+ this.recentMessageKeys = new Set();
248
+ this.agentSurfaceUnavailable = false;
249
+ this.eventScheduler = null;
92
250
  this.handler = handler;
93
- this.workingDir = config.workingDir;
94
- this.store = config.store;
95
- this.socketClient = new SocketModeClient({
96
- appToken: config.appToken,
97
- // Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
98
- // cause false pong timeouts; 4 in a row makes Slack drop the socket.
99
- clientPingTimeout: 12_000,
251
+ this.workspace = config.workspace;
252
+ this.botToken = config.botToken;
253
+ this.fetchFn = config.fetch ?? ((input, init) => fetch(input, init));
254
+ this.socketClient =
255
+ config.socket ??
256
+ new SocketModeClient({
257
+ appToken: config.appToken,
258
+ clientPingTimeout: 12_000,
259
+ });
260
+ this.webClient = config.webApi ?? new WebClient(config.botToken, { fetch: config.fetch });
261
+ this.statusClient = new WebClient(config.botToken, {
262
+ timeout: 3000,
263
+ retryConfig: { retries: 0 },
264
+ rejectRateLimitedCalls: true,
265
+ fetch: config.fetch,
100
266
  });
101
- this.webClient = new WebClient(config.botToken);
102
267
  }
103
- setEventsWatcher(watcher) {
104
- this.eventsWatcher = watcher;
268
+ setEventScheduler(scheduler) {
269
+ this.eventScheduler = scheduler;
105
270
  }
106
- // ==========================================================================
107
- // Public API
108
- // ==========================================================================
109
271
  async start() {
272
+ this.stopped = false;
110
273
  const auth = await this.webClient.auth.test();
111
274
  this.botUserId = auth.user_id;
112
275
  this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
113
276
  this.teamId = typeof auth.team_id === "string" ? auth.team_id : null;
114
277
  await Promise.all([this.fetchUsers(), this.fetchChannels()]);
278
+ if (this.stopped)
279
+ return;
115
280
  log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
116
- // Record startup time before opening the socket. Slack may replay older events;
117
- // those should be logged but not processed. Backfill runs in the background up
118
- // to this timestamp so startup is not blocked by one history call per channel.
281
+ this.backfillChannelKinds();
119
282
  this.startupTs = (Date.now() / 1000).toFixed(6);
120
283
  this.setupEventHandlers();
121
284
  await this.socketClient.start();
@@ -124,6 +287,16 @@ export class SlackMessagingBot {
124
287
  log.logWarning("Slack backfill failed", String(error));
125
288
  });
126
289
  }
290
+ async stop() {
291
+ this.stopped = true;
292
+ try {
293
+ await this.socketClient.disconnect();
294
+ }
295
+ finally {
296
+ await this.intake.close();
297
+ await Promise.all([...this.queues.values()].map((queue) => queue.close()));
298
+ }
299
+ }
127
300
  getUser(userId) {
128
301
  return this.users.get(userId);
129
302
  }
@@ -137,7 +310,7 @@ export class SlackMessagingBot {
137
310
  return Array.from(this.channels.values());
138
311
  }
139
312
  stripOwnMention(text) {
140
- const source = text ?? "";
313
+ const source = unescapeSlackText(text ?? "");
141
314
  if (!this.botUserId)
142
315
  return source.trim();
143
316
  if (!this.ownMentionRegex || !this.ownMentionRegex.source.includes(this.botUserId)) {
@@ -145,54 +318,64 @@ export class SlackMessagingBot {
145
318
  }
146
319
  return source.replace(this.ownMentionRegex, "").trim();
147
320
  }
148
- async postMessage(channel, text) {
321
+ resolveMentions(text) {
322
+ return resolveSlackMentions(text, this.users.values());
323
+ }
324
+ async postMessage(channel, text, threadTs, identity) {
149
325
  return slackRetry(async () => {
150
- const payload = { channel, ...renderSlackBlocks(text) };
326
+ const payload = {
327
+ channel,
328
+ thread_ts: threadTs,
329
+ username: identity?.username,
330
+ icon_emoji: identity?.iconEmoji,
331
+ ...renderSlackBlocks(this.resolveMentions(text)),
332
+ };
151
333
  const result = await this.webClient.chat.postMessage(payload);
152
334
  return result.ts;
153
335
  });
154
336
  }
155
337
  async addReaction(channel, messageTs, emoji) {
156
- // Slack reaction names are colon-free short names; strip any wrapping colons.
157
338
  const name = emoji.replace(/^:|:$/g, "");
158
339
  await slackRetry(async () => {
159
340
  try {
160
341
  await this.webClient.reactions.add({ channel, timestamp: messageTs, name });
161
342
  }
162
343
  catch (err) {
163
- // Re-reacting with the same emoji is not an error worth surfacing.
164
344
  if (err?.data?.error === "already_reacted")
165
345
  return;
166
346
  throw err;
167
347
  }
168
348
  });
169
349
  }
170
- async postEphemeral(channel, user, text, threadTs) {
350
+ async postEphemeral(channel, user, text, threadTs, blocks) {
171
351
  return slackRetry(async () => {
172
352
  await this.webClient.chat.postEphemeral({
173
353
  channel,
174
354
  user,
175
355
  text,
176
- ...(threadTs ? { thread_ts: threadTs } : {}),
356
+ blocks: blocks,
357
+ thread_ts: threadTs || undefined,
177
358
  });
178
359
  });
179
360
  }
180
361
  async postEphemeralBlocks(channel, user, text, blocks, threadTs) {
362
+ return this.postEphemeral(channel, user, text, threadTs, blocks);
363
+ }
364
+ async postMessageBlocks(channel, text, blocks, threadTs) {
181
365
  return slackRetry(async () => {
182
- await this.webClient.chat.postEphemeral({
366
+ const payload = {
183
367
  channel,
184
- user,
185
368
  text,
186
369
  blocks: blocks,
187
- ...(threadTs ? { thread_ts: threadTs } : {}),
188
- });
370
+ thread_ts: threadTs,
371
+ };
372
+ const result = await this.webClient.chat.postMessage(payload);
373
+ return result.ts;
189
374
  });
190
375
  }
191
- async postMessageBlocks(channel, text, blocks) {
376
+ async updateMessageBlocks(channel, ts, text, blocks) {
192
377
  return slackRetry(async () => {
193
- const payload = { channel, text, blocks: blocks };
194
- const result = await this.webClient.chat.postMessage(payload);
195
- return result.ts;
378
+ await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
196
379
  });
197
380
  }
198
381
  async postPrivate(conversationId, userId, text) {
@@ -200,12 +383,12 @@ export class SlackMessagingBot {
200
383
  }
201
384
  async postPrivateDiagnostic(conversationId, userId, text, options) {
202
385
  if (options?.style !== "muted") {
203
- await this.postEphemeral(conversationId, userId, options?.style === "error" ? `_${text}_` : text);
386
+ await this.postEphemeral(conversationId, userId, options?.style === "error" ? italicizeLines(text) : text);
204
387
  return;
205
388
  }
206
389
  await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
207
390
  }
208
- async openDirectMessage(userId) {
391
+ async openDirectConversation(userId) {
209
392
  return slackRetry(async () => {
210
393
  const result = await this.webClient.conversations.open({ users: userId });
211
394
  const channelId = result.channel?.id;
@@ -215,33 +398,101 @@ export class SlackMessagingBot {
215
398
  return channelId;
216
399
  });
217
400
  }
401
+ async fetchHistory(channel, options) {
402
+ const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
403
+ const threadTs = options?.threadTs;
404
+ return slackRetry(async () => {
405
+ const result = threadTs
406
+ ? await this.webClient.conversations.replies({
407
+ channel,
408
+ ts: threadTs,
409
+ oldest: options?.oldest || undefined,
410
+ inclusive: options?.oldest ? false : undefined,
411
+ limit,
412
+ })
413
+ : await this.webClient.conversations.history({
414
+ channel,
415
+ oldest: options?.oldest || undefined,
416
+ inclusive: options?.oldest ? false : undefined,
417
+ limit,
418
+ });
419
+ const messages = (result.messages ?? []);
420
+ const mapped = messages
421
+ .filter((msg) => !!msg.ts)
422
+ .filter((msg) => msg.ts !== threadTs)
423
+ .map((msg) => {
424
+ const user = msg.user ? this.users.get(msg.user) : undefined;
425
+ const message = {
426
+ ts: msg.ts,
427
+ text: unescapeSlackText(msg.text ?? ""),
428
+ isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
429
+ };
430
+ if (msg.thread_ts && msg.thread_ts !== msg.ts)
431
+ message.threadTs = msg.thread_ts;
432
+ if (msg.user)
433
+ message.userId = msg.user;
434
+ if (user)
435
+ message.userName = user.userName;
436
+ return message;
437
+ });
438
+ return threadTs ? mapped : mapped.toReversed();
439
+ });
440
+ }
441
+ async listUsers() {
442
+ await slackRetry(() => this.fetchUsers());
443
+ return this.getAllUsers().map((user) => ({
444
+ id: user.id,
445
+ userName: user.userName,
446
+ displayName: user.displayName,
447
+ isBot: user.isBot === true,
448
+ }));
449
+ }
218
450
  async updateMessage(channel, ts, text) {
219
451
  return slackRetry(async () => {
220
- const payload = { channel, ts, ...renderSlackBlocks(text) };
221
- await this.webClient.chat.update(payload);
452
+ const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
453
+ try {
454
+ await this.webClient.chat.update(payload);
455
+ }
456
+ catch (error) {
457
+ recordSlackUpdate(this, { channel, ts }, text, payload, { error });
458
+ throw error;
459
+ }
460
+ recordSlackUpdate(this, { channel, ts }, text, payload, { success: true });
222
461
  });
223
462
  }
463
+ tryReserveStreamStart() {
464
+ return this.streamStarts.tryReserve();
465
+ }
224
466
  async startMessageStream(channel, text, threadTs, recipientUserId) {
225
- return slackRetry(async () => {
467
+ const [head, ...tail] = chunkStreamText(text);
468
+ const ts = await slackRetry(async () => {
226
469
  const result = await this.webClient.apiCall("chat.startStream", {
227
470
  channel,
228
- markdown_text: text,
229
- ...(threadTs ? { thread_ts: threadTs } : {}),
230
- ...(this.teamId ? { recipient_team_id: this.teamId } : {}),
231
- ...(recipientUserId ? { recipient_user_id: recipientUserId } : {}),
471
+ markdown_text: this.resolveMentions(head ?? ""),
472
+ thread_ts: threadTs || undefined,
473
+ recipient_team_id: this.teamId || undefined,
474
+ recipient_user_id: recipientUserId || undefined,
232
475
  });
233
- const ts = result.ts;
234
- if (!ts)
476
+ const streamTs = result.ts;
477
+ if (!streamTs)
235
478
  throw new Error("Slack chat.startStream did not return ts");
236
- return ts;
479
+ return streamTs;
237
480
  });
481
+ for (const part of tail)
482
+ await this.appendStreamChunk(channel, ts, part);
483
+ return ts;
238
484
  }
239
485
  async appendMessageStream(channel, ts, text) {
486
+ for (const part of chunkStreamText(text)) {
487
+ await this.appendStreamChunk(channel, ts, part);
488
+ }
489
+ }
490
+ async appendStreamChunk(channel, ts, text) {
240
491
  return slackRetry(async () => {
241
492
  await this.webClient.apiCall("chat.appendStream", {
242
493
  channel,
243
494
  ts,
244
- markdown_text: text,
495
+ markdown_text: this.resolveMentions(text),
245
496
  });
246
497
  });
247
498
  }
@@ -255,38 +506,93 @@ export class SlackMessagingBot {
255
506
  await this.webClient.chat.delete({ channel, ts });
256
507
  });
257
508
  }
258
- // ==========================================================================
259
- // Slack Assistant API (AI assistant experience)
260
- // ==========================================================================
261
- /** Set the status for an assistant thread (shows "thinking" state) */
262
509
  async setAssistantStatus(channel, threadTs, status) {
263
- return slackRetry(async () => {
264
- await this.webClient.assistant.threads.setStatus({
510
+ const key = `${channel}:${threadTs}`;
511
+ const previous = this.statusUpdates.get(key) ?? Promise.resolve();
512
+ const update = previous
513
+ .catch(() => undefined)
514
+ .then(async () => {
515
+ await this.statusClient.assistant.threads.setStatus({
265
516
  channel_id: channel,
266
517
  thread_ts: threadTs,
267
518
  status,
268
519
  });
269
520
  });
521
+ this.statusUpdates.set(key, update);
522
+ try {
523
+ await update;
524
+ }
525
+ finally {
526
+ if (this.statusUpdates.get(key) === update)
527
+ this.statusUpdates.delete(key);
528
+ }
270
529
  }
271
- async postInThread(channel, threadTs, text) {
272
- return slackRetry(async () => {
273
- const payload = { channel, thread_ts: threadTs, ...renderSlackBlocks(text) };
274
- const result = await this.webClient.chat.postMessage(payload);
275
- return result.ts;
276
- });
530
+ async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
531
+ if (this.agentSurfaceUnavailable)
532
+ return;
533
+ try {
534
+ await slackRetry(async () => {
535
+ await this.webClient.assistant.threads.setSuggestedPrompts({
536
+ channel_id: channel,
537
+ thread_ts: threadTs || undefined,
538
+ prompts,
539
+ });
540
+ });
541
+ }
542
+ catch (error) {
543
+ if (slackErrorCode(error) !== "not_agent_app")
544
+ throw error;
545
+ this.agentSurfaceUnavailable = true;
546
+ log.logInfo("Slack app is not an agent app; suggested prompts are disabled");
547
+ }
277
548
  }
278
- async postInThreadBlocks(channel, threadTs, text, blocks) {
549
+ async setAssistantTitle(channel, threadTs, title) {
279
550
  return slackRetry(async () => {
280
- const payload = {
281
- channel,
551
+ await this.webClient.assistant.threads.setTitle({
552
+ channel_id: channel,
282
553
  thread_ts: threadTs,
283
- text, // fallback for notifications
284
- blocks: blocks,
285
- };
286
- const result = await this.webClient.chat.postMessage(payload);
287
- return result.ts;
554
+ title,
555
+ });
288
556
  });
289
557
  }
558
+ assistantOps() {
559
+ return {
560
+ postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
561
+ setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
562
+ setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
563
+ channelName: (channelId) => this.channels.get(channelId)?.name,
564
+ };
565
+ }
566
+ handleAssistantThreadStarted({ event, ack }) {
567
+ ack();
568
+ const payload = event;
569
+ if (!payload.assistant_thread)
570
+ return;
571
+ void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
572
+ }
573
+ handleAgentContextChangedEvent({ event, ack }) {
574
+ ack();
575
+ const payload = event;
576
+ if (payload.assistant_thread) {
577
+ handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
578
+ return;
579
+ }
580
+ const channelId = payload.channel_id ?? payload.channel;
581
+ if (channelId) {
582
+ handleAgentContextChanged(this.assistantThreads, {
583
+ channel_id: channelId,
584
+ context: payload.context,
585
+ });
586
+ }
587
+ }
588
+ async postInThread(channel, threadTs, text, identity) {
589
+ return identity
590
+ ? this.postMessage(channel, text, threadTs, identity)
591
+ : this.postMessage(channel, text, threadTs);
592
+ }
593
+ async postInThreadBlocks(channel, threadTs, text, blocks) {
594
+ return this.postMessageBlocks(channel, text, blocks, threadTs);
595
+ }
290
596
  async uploadFile(channel, filePath, title, threadTs) {
291
597
  return slackRetry(async () => {
292
598
  const fileName = title || basename(filePath);
@@ -301,18 +607,41 @@ export class SlackMessagingBot {
301
607
  });
302
608
  }
303
609
  logToFile(channel, entry) {
304
- appendChannelLog(this.workingDir, channel, entry);
610
+ appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
305
611
  }
306
- logBotResponse(channel, text, ts, threadTs, slackBlocks) {
307
- appendBotResponseLog(this.workingDir, channel, text, ts, threadTs, {
612
+ logBotResponse(channel, text, ts, threadTs, { slackBlocks, answer } = {}) {
613
+ appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
308
614
  platform: "slack",
309
- ...(slackBlocks ? { slackBlocks } : {}),
615
+ slackBlocks,
616
+ ...answer,
310
617
  });
311
618
  }
619
+ ownsBlockKitMessage(channel, ts, threadTs) {
620
+ const content = readTextFileNoFollowIfExists(this.office(channel).logPath);
621
+ if (content === undefined)
622
+ return false;
623
+ for (const line of content.trim().split("\n").toReversed()) {
624
+ try {
625
+ const entry = JSON.parse(line);
626
+ if (!isRecord(entry) || entry.ts !== ts)
627
+ continue;
628
+ return (entry.isMessagingBot === true &&
629
+ entry.platform === "slack" &&
630
+ Array.isArray(entry.slackBlocks) &&
631
+ entry.threadTs === threadTs);
632
+ }
633
+ catch {
634
+ continue;
635
+ }
636
+ }
637
+ return false;
638
+ }
312
639
  getMessagingInfo() {
313
640
  return {
314
641
  name: "slack",
315
- formattingGuide: "## Slack Formatting (mrkdwn, NOT Markdown)\nBold: *text*, Italic: _text_, Code: `code`, Block: ```code```, Links: <url|text>\nDo NOT use **double asterisks** or [markdown](links).",
642
+ workspaceId: this.teamId || undefined,
643
+ trustModel: "membership",
644
+ formattingGuide: "## Slack Formatting\nWrite standard Markdown/GFM: **bold**, _italic_, ~~strike~~, `code`, fenced code blocks, [links](url), lists, and pipe tables (rendered as native Slack tables).\nDo NOT use Slack mrkdwn syntax like *single-asterisk bold* or <url|label> links.",
316
645
  channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
317
646
  users: this.getAllUsers().map((u) => ({
318
647
  id: u.id,
@@ -324,29 +653,18 @@ export class SlackMessagingBot {
324
653
  },
325
654
  };
326
655
  }
327
- // ==========================================================================
328
- // Events Integration
329
- // ==========================================================================
330
- /**
331
- * Enqueue an event for processing. Always queues (no "already working" rejection).
332
- * Returns true if enqueued, false if queue is full (max 5).
333
- */
334
656
  enqueueEvent(event) {
335
- const conversationId = event.conversationId;
336
- const queue = this.getQueue(conversationId);
337
- if (queue.size() >= 5) {
338
- log.logWarning(`Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`);
657
+ if (this.stopped)
339
658
  return false;
340
- }
341
- log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);
342
- queue.enqueue(async () => {
659
+ const conversationId = event.address.conversationId;
660
+ return this.getQueue(conversationId).offerEvent(event, async () => {
343
661
  let anchorTs;
344
662
  if (!event.thread_ts) {
345
663
  try {
346
664
  anchorTs = await this.postMessage(conversationId, SLACK_EVENT_ANCHOR_TEXT);
347
665
  }
348
666
  catch (err) {
349
- log.logWarning(`Failed to post Slack event anchor for ${conversationId}`, err instanceof Error ? err.message : String(err));
667
+ log.logWarning(`Failed to post Slack event anchor for ${conversationId}`, errorMessage(err));
350
668
  reportUserFacingError(err, {
351
669
  domain: "events",
352
670
  surface: "event_delivery",
@@ -366,16 +684,18 @@ export class SlackMessagingBot {
366
684
  const eventPlan = planSlackEventAnchorRun(event, anchorTs);
367
685
  const eventForRun = eventPlan.event;
368
686
  if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
369
- registerThreadSession({
370
- conversationDir: join(this.workingDir, conversationId),
687
+ await registerThreadSession({
688
+ office: this.office(conversationId),
371
689
  sessionKey: eventForRun.sessionKey,
372
690
  });
373
691
  }
374
692
  const runQueueKey = planSlackAdapterSession(eventForRun, {
375
693
  initialMessageTs: eventPlan.initialMessageTs,
376
694
  }).sessionKey;
377
- this.getQueue(runQueueKey).enqueue(async () => {
378
- const slackEvent = {
695
+ const run = async () => {
696
+ const slackEvent = createConversationEvent({
697
+ platform: "slack",
698
+ address: eventForRun.address,
379
699
  type: eventForRun.type,
380
700
  conversationId,
381
701
  conversationKind: eventForRun.conversationKind,
@@ -384,78 +704,140 @@ export class SlackMessagingBot {
384
704
  thread_ts: eventForRun.thread_ts,
385
705
  user: eventForRun.user,
386
706
  text: eventForRun.text,
387
- attachments: eventForRun.attachments?.map((attachment) => ({
388
- original: attachment.name,
389
- localPath: attachment.localPath,
390
- })),
707
+ attachments: eventForRun.attachments,
391
708
  sessionKey: eventForRun.sessionKey,
392
- };
709
+ });
393
710
  const context = createSlackAdapters(slackEvent, this, {
394
711
  initialMessageTs: eventPlan.initialMessageTs,
395
- replyMode: resolveConversationSettings(join(this.workingDir, eventForRun.conversationId)).slack
396
- ?.replyMode ?? "top-level",
712
+ replyMode: this.resolveReplyMode(eventForRun.address),
397
713
  });
398
714
  return this.handler.handleEvent(eventForRun, this, context);
399
- });
715
+ };
716
+ const runQueue = this.getQueue(runQueueKey);
717
+ if (!runQueue.enqueue(run)) {
718
+ await runQueue.close();
719
+ await run();
720
+ }
400
721
  });
401
- return true;
402
722
  }
403
- // ==========================================================================
404
- // Private - Event Handlers
405
- // ==========================================================================
406
723
  getQueue(channelId) {
407
724
  let queue = this.queues.get(channelId);
408
725
  if (!queue) {
409
726
  queue = new MessagingEventQueue("Slack");
727
+ if (this.stopped)
728
+ void queue.close();
410
729
  this.queues.set(channelId, queue);
411
730
  }
412
731
  return queue;
413
732
  }
414
- resolveQueueKey(conversationId, sessionKey) {
733
+ async resolveQueueKey(conversationId, sessionKey) {
415
734
  if (!isSlackThreadSessionKey(sessionKey))
416
735
  return sessionKey;
417
- if (this.handler.isRunning(sessionKey))
736
+ if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
418
737
  return sessionKey;
419
- return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
738
+ }
739
+ return (await this.hasKnownThreadSession(conversationId, sessionKey))
740
+ ? sessionKey
741
+ : conversationId;
742
+ }
743
+ isTaskThread(channel, root) {
744
+ return readTaskRoots(this.office(channel)).has(root);
745
+ }
746
+ hasRunningTaskThread(channel) {
747
+ const roots = readTaskRoots(this.office(channel));
748
+ if (!roots.size)
749
+ return false;
750
+ return this.handler
751
+ .getRunningSessions()
752
+ .some((s) => s.address.platform === "slack" &&
753
+ s.address.conversationId === channel &&
754
+ [...roots.keys()].some((root) => resolveSlackSessionKey(channel, root) === s.sessionKey));
420
755
  }
421
756
  hasKnownThreadSession(conversationId, sessionKey) {
422
757
  return hasMaterializedChatSession({
423
- conversationDir: join(this.workingDir, conversationId),
758
+ office: this.office(conversationId),
424
759
  sessionKey,
425
760
  });
426
761
  }
762
+ channelKindFor(channelId) {
763
+ if (channelId.startsWith("D"))
764
+ return "im";
765
+ const channel = this.channels.get(channelId);
766
+ if (!channel)
767
+ return undefined;
768
+ if (channel.name.startsWith("DM:"))
769
+ return "im";
770
+ if (channel.isExternallyShared)
771
+ return "external";
772
+ if (channel.isPrivate === true)
773
+ return "private_channel";
774
+ if (channel.isPrivate === false)
775
+ return "public_channel";
776
+ return undefined;
777
+ }
778
+ backfillChannelKinds() {
779
+ let recorded = 0;
780
+ for (const record of listRegisteredOffices(this.workspace.stateDir)) {
781
+ if (record.platform !== "slack")
782
+ continue;
783
+ const kind = this.channelKindFor(record.conversationId);
784
+ if (!kind)
785
+ continue;
786
+ try {
787
+ const office = this.workspace.office(record);
788
+ if (readPlatformChannelKind(office) === kind)
789
+ continue;
790
+ recordPlatformChannelKind(office, kind);
791
+ recorded++;
792
+ }
793
+ catch (err) {
794
+ log.logWarning("Failed to backfill Slack channel kind", String(err));
795
+ }
796
+ }
797
+ if (recorded > 0)
798
+ log.logInfo(`Recorded channel kind for ${recorded} Slack offices`);
799
+ }
427
800
  processSlackMessageIntake(options) {
428
- void processMessageIntake({
801
+ const kind = this.channelKindFor(options.event.address.conversationId);
802
+ if (kind) {
803
+ try {
804
+ recordPlatformChannelKind(this.workspace.office(options.event.address), kind);
805
+ }
806
+ catch (err) {
807
+ log.logWarning("Failed to record Slack channel kind", String(err));
808
+ }
809
+ }
810
+ const absorbAttachmentFailure = () => {
811
+ void options.attachmentsPromise.catch((err) => {
812
+ log.logWarning("Failed to log Slack message", String(err));
813
+ });
814
+ };
815
+ return processMessageIntake({
429
816
  eventBase: options.event,
430
- workingDir: this.workingDir,
431
- isAutoReplyCandidate: options.isAutoReplyCandidate,
817
+ addressed: options.addressed,
818
+ magicWord: {
819
+ addressed: options.magicWordAddressed ?? options.addressed,
820
+ scopeFallback: "top-level",
821
+ },
822
+ busyPolicy: "queue",
432
823
  logEntryBase: {},
433
824
  processAttachments: () => options.attachmentsPromise,
434
825
  queueKey: options.queueKey,
435
- enqueue: (queueKey, work) => this.getQueue(queueKey).enqueue(work),
826
+ enqueue: (queueKey, work) => this.getQueue(queueKey).admit(work),
827
+ react: (emoji) => this.addReaction(options.event.channel, options.event.ts, emoji),
436
828
  handler: this.handler,
437
829
  bot: this,
438
830
  createContext: (event) => this.createContext(event),
439
- onNotTriggered: options.onNotTriggered,
440
831
  deferAttachmentsUntilRun: true,
832
+ }).then((outcome) => {
833
+ if (outcome !== "enqueued")
834
+ absorbAttachmentFailure();
835
+ }, (err) => {
836
+ log.logWarning("Slack message intake failed", String(err));
837
+ absorbAttachmentFailure();
441
838
  });
442
839
  }
443
- buildHomeView() {
444
- const blocks = [
445
- {
446
- type: "section",
447
- text: {
448
- type: "mrkdwn",
449
- text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
450
- },
451
- accessory: {
452
- type: "image",
453
- image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
454
- alt_text: PRODUCT_NAME,
455
- },
456
- },
457
- ];
458
- // --- Running tasks ---
840
+ appendRunningTasks(blocks) {
459
841
  const runningSessions = this.handler.getRunningSessions();
460
842
  blocks.push({ type: "divider" }, {
461
843
  type: "header",
@@ -470,61 +852,51 @@ export class SlackMessagingBot {
470
852
  type: "context",
471
853
  elements: [{ type: "mrkdwn", text: "_No tasks running right now._" }],
472
854
  });
855
+ return;
473
856
  }
474
- else {
475
- // Threshold for "stuck" detection (10 minutes)
476
- const STUCK_THRESHOLD_MS = 10 * 60 * 1000;
477
- for (const session of runningSessions) {
478
- const channelId = session.sessionKey.split(":")[0];
479
- const channel = this.channels.get(channelId);
480
- const channelName = channel ? `#${channel.name}` : channelId;
481
- const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
482
- const elapsedStr = elapsed < 1 ? "<1 min" : `${elapsed} min`;
483
- // Check if task might be stuck
484
- const lastActivity = session.lastActivityAt ? Date.now() - session.lastActivityAt : 0;
485
- const isStuck = lastActivity > STUCK_THRESHOLD_MS;
486
- const statusText = isStuck ? "_stuck_" : "_running_";
487
- // Build status line: channel · status · time · step
488
- let statusLine = `${statusText} · ${elapsedStr}`;
489
- if (session.currentTool) {
490
- statusLine += ` · ${session.currentTool}`;
491
- }
492
- if (isStuck && lastActivity > 0) {
493
- const inactiveMin = Math.floor(lastActivity / 60000);
494
- statusLine += ` · idle ${inactiveMin}m`;
495
- }
496
- // Use context block for gray small text (like "No scheduled jobs.")
857
+ const stuckThresholdMs = 10 * 60 * 1000;
858
+ for (const session of runningSessions) {
859
+ const channelId = conversationIdOf(session.sessionKey);
860
+ const channelName = this.channels.get(channelId)?.name;
861
+ const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
862
+ const elapsedStr = elapsed < 1 ? "<1 min" : `${elapsed} min`;
863
+ const lastActivity = session.lastActivityAt ? Date.now() - session.lastActivityAt : 0;
864
+ const isStuck = lastActivity > stuckThresholdMs;
865
+ let statusLine = `${isStuck ? "_stuck_" : "_running_"} · ${elapsedStr}`;
866
+ if (session.currentTool)
867
+ statusLine += ` · ${session.currentTool}`;
868
+ if (isStuck)
869
+ statusLine += ` · idle ${Math.floor(lastActivity / 60000)}m`;
870
+ blocks.push({
871
+ type: "context",
872
+ elements: [
873
+ {
874
+ type: "mrkdwn",
875
+ text: `*${channelName ? `#${channelName}` : channelId}* · ${statusLine}`,
876
+ },
877
+ ],
878
+ });
879
+ if (isStuck) {
497
880
  blocks.push({
498
881
  type: "context",
499
882
  elements: [
883
+ { type: "mrkdwn", text: " " },
500
884
  {
501
- type: "mrkdwn",
502
- text: `*${channelName}* · ${statusLine}`,
885
+ type: "button",
886
+ text: { type: "plain_text", text: "Force Stop", emoji: true },
887
+ action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
888
+ value: session.sessionKey,
889
+ style: "danger",
503
890
  },
504
891
  ],
505
892
  });
506
- // Add Force Stop button as separate element if stuck
507
- if (isStuck) {
508
- blocks.push({
509
- type: "context",
510
- elements: [
511
- {
512
- type: "mrkdwn",
513
- text: " ",
514
- },
515
- {
516
- type: "button",
517
- text: { type: "plain_text", text: "Force Stop", emoji: true },
518
- action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
519
- style: "danger",
520
- },
521
- ],
522
- });
523
- }
524
893
  }
525
894
  }
526
- // --- Cron jobs ---
527
- const periodicEvents = this.eventsWatcher?.getPeriodicEvents() ?? [];
895
+ }
896
+ appendScheduledJobs(blocks, dmChannelId) {
897
+ const periodicEvents = dmChannelId && this.eventScheduler
898
+ ? this.eventScheduler.periodicEvents(createOfficeAddress("slack", dmChannelId))
899
+ : [];
528
900
  blocks.push({ type: "divider" }, {
529
901
  type: "header",
530
902
  text: {
@@ -538,34 +910,46 @@ export class SlackMessagingBot {
538
910
  type: "context",
539
911
  elements: [{ type: "mrkdwn", text: "_No scheduled jobs._" }],
540
912
  });
913
+ return;
541
914
  }
542
- else {
543
- for (const ev of periodicEvents) {
544
- const channelLabel = ev.platform === "slack"
545
- ? (() => {
546
- const channel = this.channels.get(ev.conversationId);
547
- const channelName = channel ? `#${channel.name}` : ev.conversationId;
548
- return `${ev.platform}:${channelName}`;
549
- })()
550
- : `${ev.platform}:${ev.conversationId}`;
551
- const nextStr = ev.nextRun
552
- ? new Date(ev.nextRun).toLocaleString("en-US", {
553
- month: "short",
554
- day: "numeric",
555
- hour: "2-digit",
556
- minute: "2-digit",
557
- })
558
- : "—";
559
- blocks.push({
560
- type: "section",
561
- text: {
562
- type: "mrkdwn",
563
- text: `*${ev.text}*\n└ \`${ev.schedule}\` · ${channelLabel} · Next: ${nextStr}`,
564
- },
565
- });
566
- }
915
+ for (const event of periodicEvents) {
916
+ const channel = event.platform === "slack" ? this.channels.get(event.conversationId) : undefined;
917
+ const channelId = channel ? `#${channel.name}` : event.conversationId;
918
+ const channelLabel = `${event.platform}:${channelId}`;
919
+ const nextRun = event.nextRun
920
+ ? new Date(event.nextRun).toLocaleString("en-US", {
921
+ month: "short",
922
+ day: "numeric",
923
+ hour: "2-digit",
924
+ minute: "2-digit",
925
+ })
926
+ : "—";
927
+ blocks.push({
928
+ type: "section",
929
+ text: {
930
+ type: "mrkdwn",
931
+ text: `*${event.text}*\n└ \`${event.schedule}\` · ${channelLabel} · Next: ${nextRun}`,
932
+ },
933
+ });
567
934
  }
568
- // --- Footer ---
935
+ }
936
+ buildHomeView(dmChannelId) {
937
+ const blocks = [
938
+ {
939
+ type: "section",
940
+ text: {
941
+ type: "mrkdwn",
942
+ text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
943
+ },
944
+ accessory: {
945
+ type: "image",
946
+ image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
947
+ alt_text: PRODUCT_NAME,
948
+ },
949
+ },
950
+ ];
951
+ this.appendRunningTasks(blocks);
952
+ this.appendScheduledJobs(blocks, dmChannelId);
569
953
  blocks.push({ type: "divider" }, {
570
954
  type: "context",
571
955
  elements: [
@@ -574,58 +958,38 @@ export class SlackMessagingBot {
574
958
  });
575
959
  return { type: "home", blocks: blocks };
576
960
  }
577
- resolveStopTarget(channelId, threadTs) {
578
- const directTarget = resolveStopTarget({
579
- handler: this.handler,
580
- conversationId: channelId,
581
- sessionKey: resolveSlackSessionKey(channelId, threadTs),
582
- });
583
- if (directTarget)
584
- return directTarget;
585
- if (threadTs)
586
- return null;
587
- return resolveOnlyScopedStopTarget(this.handler, channelId);
588
- }
589
- isStopText(text) {
590
- const normalized = text.trim().toLowerCase();
591
- return normalized === "stop" || normalized === "/stop";
592
- }
593
- createCommandAdapters(conversationId, userId, userName, text, ts, options = {}) {
594
- const message = {
961
+ createCommandAdapters(input) {
962
+ const { conversationId, userId, userName, text, ts, ...options } = input;
963
+ const message = createConversationMessage({
964
+ platform: "slack",
965
+ conversationId,
595
966
  id: ts,
596
- sessionKey: conversationId,
967
+ sessionKey: options.sessionKey ?? conversationId,
597
968
  conversationKind: options.ephemeralChannelId ? "shared" : "direct",
598
969
  userId,
599
970
  userName,
600
971
  text,
601
972
  attachments: [],
602
- };
603
- const respond = async (responseText) => {
604
- if (options.ephemeralChannelId) {
605
- await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs);
606
- return;
607
- }
608
- const messageTs = await this.postMessage(conversationId, responseText);
609
- this.logBotResponse(conversationId, responseText, messageTs);
610
- };
611
- const respondMuted = async (responseText) => {
612
- const blocks = [buildMrkdwnContextBlock(responseText)];
973
+ });
974
+ const respond = async (responseText, blocks) => {
613
975
  if (options.ephemeralChannelId) {
614
- await this.postEphemeralBlocks(options.ephemeralChannelId, userId, responseText, blocks, options.threadTs);
976
+ await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs, blocks);
615
977
  return;
616
978
  }
617
- const messageTs = await this.postMessageBlocks(conversationId, responseText, blocks);
979
+ const messageTs = blocks
980
+ ? await this.postMessageBlocks(conversationId, responseText, blocks)
981
+ : await this.postMessage(conversationId, responseText);
618
982
  this.logBotResponse(conversationId, responseText, messageTs);
619
983
  };
620
984
  const responder = {
621
985
  respond,
622
- replaceResponse: respond,
986
+ replaceResponse: (responseText) => respond(responseText),
623
987
  respondDiagnostic: async (responseText, responseOptions) => {
624
988
  if (responseOptions?.style === "muted") {
625
- await respondMuted(responseText);
989
+ await respond(responseText, [buildMrkdwnContextBlock(responseText)]);
626
990
  return;
627
991
  }
628
- await respond(responseOptions?.style === "error" ? `_${responseText}_` : responseText);
992
+ await respond(responseOptions?.style === "error" ? italicizeLines(responseText) : responseText);
629
993
  },
630
994
  respondToolResult: async (result) => {
631
995
  const duration = (result.durationMs / 1000).toFixed(1);
@@ -639,6 +1003,7 @@ export class SlackMessagingBot {
639
1003
  deleteResponse: async () => { },
640
1004
  };
641
1005
  return {
1006
+ address: message.address,
642
1007
  message,
643
1008
  responder,
644
1009
  platform: this.getMessagingInfo(),
@@ -664,66 +1029,42 @@ export class SlackMessagingBot {
664
1029
  text: commandText,
665
1030
  attachments: [],
666
1031
  isMessagingBot: false,
667
- ...(threadTs ? { threadTs } : {}),
1032
+ threadTs: threadTs || undefined,
668
1033
  });
669
- const event = {
670
- type: options.type ?? (isDirectMessage ? "dm" : "mention"),
1034
+ const event = createConversationEvent({
1035
+ platform: "slack",
671
1036
  conversationId,
1037
+ type: options.type ?? (isDirectMessage ? "dm" : "mention"),
672
1038
  conversationKind: isDirectMessage ? "direct" : "shared",
673
1039
  ts: eventTs,
674
1040
  user: payload.user_id,
675
1041
  text: commandText,
676
1042
  attachments: [],
677
- ...(threadTs ? { thread_ts: threadTs } : {}),
1043
+ thread_ts: threadTs || undefined,
678
1044
  sessionKey,
679
- };
680
- const context = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? { threadTs } : { ephemeralChannelId: conversationId, threadTs });
681
- return { event, context };
682
- }
683
- async routeSlashLoginCommand(payload) {
684
- const { event, context } = this.buildSlashCommandEvent(payload, {
685
- type: payload.channel_id.startsWith("D") ? "dm" : "private_command",
686
- includeText: true,
687
1045
  });
688
- await this.handler.handleEvent(event, this, context);
689
- }
690
- async routeSlashNewCommand(payload) {
691
- const conversationId = payload.channel_id;
692
- if (!conversationId.startsWith("D")) {
693
- await this.postEphemeral(conversationId, payload.user_id, `為了避免誤清除共享上下文,${payload.command} 目前只能在與 ${PRODUCT_NAME} 的私訊中使用。`);
694
- return;
695
- }
696
- const createdAt = new Date();
697
- const eventTs = (createdAt.getTime() / 1000).toFixed(6);
698
- const userName = payload.user_name ?? this.getUser(payload.user_id)?.userName;
699
- this.logToFile(conversationId, {
700
- date: createdAt.toISOString(),
701
- ts: eventTs,
702
- user: payload.user_id,
1046
+ const context = this.createCommandAdapters({
1047
+ conversationId,
1048
+ userId: payload.user_id,
703
1049
  userName,
704
- text: payload.command,
705
- attachments: [],
706
- isMessagingBot: false,
1050
+ text: commandText,
1051
+ ts: eventTs,
1052
+ ephemeralChannelId: isDirectMessage ? undefined : conversationId,
1053
+ threadTs,
1054
+ sessionKey,
707
1055
  });
708
- const commandMessagingBot = {
709
- start: async () => { },
710
- postMessage: async (_channel, text) => this.postMessage(conversationId, text),
711
- updateMessage: async (channel, ts, text) => this.updateMessage(channel, ts, text),
712
- enqueueEvent: (event) => this.enqueueEvent(event),
713
- getMessagingInfo: () => this.getMessagingInfo(),
714
- };
715
- await this.handler.handleNewCommand(conversationId, conversationId, commandMessagingBot);
716
- }
717
- async routeSlashModelCommand(payload) {
718
- const { event, context } = this.buildSlashCommandEvent(payload, { includeText: true });
719
- await this.handler.handleEvent(event, this, context);
720
- }
721
- async routeSlashSessionCommand(payload) {
722
- const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
723
- await this.handler.handleEvent(event, this, context);
1056
+ return { event, context };
724
1057
  }
725
- async routeSlashAdminCommand(payload) {
726
- const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
1058
+ async routeSlashCommand(route, payload) {
1059
+ const { event, context } = this.buildSlashCommandEvent(payload, {
1060
+ includeText: route.includeText,
1061
+ thread: route.thread,
1062
+ type: route.privateCommand
1063
+ ? payload.channel_id.startsWith("D")
1064
+ ? "dm"
1065
+ : "private_command"
1066
+ : undefined,
1067
+ });
727
1068
  await this.handler.handleEvent(event, this, context);
728
1069
  }
729
1070
  setupEventHandlers() {
@@ -736,38 +1077,57 @@ export class SlackMessagingBot {
736
1077
  this.socketClient.on("unable_to_socket_mode_start", (err) => {
737
1078
  log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
738
1079
  });
739
- this.socketClient.on("app_mention", (payload) => this.handleAppMention(payload));
740
- this.socketClient.on("message", (payload) => this.handleMessageEvent(payload));
741
- this.socketClient.on("slash_commands", (payload) => void this.handleSlashCommand(payload));
742
- this.socketClient.on("app_home_opened", (payload) => this.handleAppHomeOpened(payload));
743
- this.socketClient.on("block_actions", (payload) => void this.handleBlockAction(payload));
744
- this.socketClient.on("interactive", (payload) => void this.handleBlockAction(payload));
745
- }
746
- handleAppMention({ event, ack }) {
1080
+ const on = (event, handle) => this.socketClient.on(event, (payload) => this.intake.run(() => handle(withLoggedAck(event, payload))));
1081
+ on("app_mention", (payload) => this.handleAppMention(payload));
1082
+ on("message", (payload) => this.handleMessageEvent(payload));
1083
+ on("slash_commands", (payload) => this.handleSlashCommand(payload));
1084
+ on("app_home_opened", (payload) => this.handleAppHomeOpened(payload));
1085
+ on("assistant_thread_started", (payload) => this.handleAssistantThreadStarted(payload));
1086
+ on("assistant_thread_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
1087
+ on("app_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
1088
+ const blockAction = ({ body, ack }) => this.handleBlockAction({ body: body, ack });
1089
+ on("block_actions", blockAction);
1090
+ on("interactive", blockAction);
1091
+ }
1092
+ isRedelivery(kind, channel, ts) {
1093
+ const key = `${kind}:${channel}:${ts}`;
1094
+ if (this.recentMessageKeys.has(key))
1095
+ return true;
1096
+ this.recentMessageKeys.add(key);
1097
+ if (this.recentMessageKeys.size > RECENT_MESSAGE_KEYS) {
1098
+ const oldest = this.recentMessageKeys.values().next().value;
1099
+ if (oldest !== undefined)
1100
+ this.recentMessageKeys.delete(oldest);
1101
+ }
1102
+ return false;
1103
+ }
1104
+ async handleAppMention({ event, ack }) {
747
1105
  const e = event;
748
- // Skip DMs (handled by message event)
749
1106
  if (e.channel.startsWith("D")) {
750
1107
  ack();
751
1108
  return;
752
1109
  }
753
- // Top-level mentions use a persistent channel session.
754
- // Thread replies get their own isolated session (channelId:thread_ts).
1110
+ if (this.isRedelivery("app_mention", e.channel, e.ts)) {
1111
+ log.logInfo(`[${e.channel}] Ignoring redelivered mention ${e.ts}`);
1112
+ await ack();
1113
+ return;
1114
+ }
755
1115
  const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
756
1116
  const mentionText = this.stripOwnMention(e.text);
757
- const slackEvent = {
758
- type: "mention",
1117
+ const slackEvent = createConversationEvent({
1118
+ platform: "slack",
759
1119
  conversationId: e.channel,
1120
+ type: "mention",
760
1121
  conversationKind: "shared",
761
- channel: e.channel,
762
1122
  ts: e.ts,
763
1123
  thread_ts: e.thread_ts,
764
1124
  user: e.user,
765
1125
  text: mentionText || "Please respond to the recent conversation context.",
766
- files: e.files,
767
1126
  sessionKey,
768
- };
1127
+ channel: e.channel,
1128
+ files: e.files,
1129
+ });
769
1130
  const attachmentsPromise = this.logUserMessage(slackEvent);
770
- // Only trigger processing for messages AFTER startup (not replayed old messages)
771
1131
  if (this.startupTs && e.ts < this.startupTs) {
772
1132
  log.logInfo(`[${e.channel}] Logged old message (pre-startup), not triggering: ${slackEvent.text.substring(0, 30)}`);
773
1133
  void attachmentsPromise.catch((err) => {
@@ -776,90 +1136,161 @@ export class SlackMessagingBot {
776
1136
  ack();
777
1137
  return;
778
1138
  }
779
- // Check for stop command - execute immediately, don't queue!
780
- if (this.isStopText(slackEvent.text)) {
781
- const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
782
- if (stopTarget) {
783
- this.handler.handleStop(stopTarget, e.channel, this);
784
- }
785
- else {
786
- this.postMessage(e.channel, formatNothingRunning("slack"));
787
- }
788
- void attachmentsPromise.catch((err) => {
789
- log.logWarning("Failed to log Slack message", String(err));
790
- });
791
- ack();
792
- return;
793
- }
794
- this.processSlackMessageIntake({
1139
+ const intake = this.processSlackMessageIntake({
795
1140
  event: slackEvent,
796
1141
  attachmentsPromise,
797
- queueKey: this.resolveQueueKey(e.channel, sessionKey),
798
- isAutoReplyCandidate: false,
1142
+ queueKey: await this.resolveQueueKey(e.channel, sessionKey),
1143
+ addressed: true,
799
1144
  });
800
1145
  ack();
1146
+ await intake;
801
1147
  }
802
- handleMessageEvent({ event, ack }) {
803
- const e = event;
804
- const hasFiles = !!e.files && e.files.length > 0;
805
- const hasSlackContent = !!e.text || hasFiles || !!e.blocks?.length || !!e.attachments?.length;
806
- const isOwnMessagingBotMessage = (!!e.user && e.user === this.botUserId) || (!!this.botId && e.bot_id === this.botId);
807
- if (isOwnMessagingBotMessage) {
1148
+ admitHumanMessage(event, ack) {
1149
+ const hasSlackContent = hasMessageContent(event);
1150
+ const isOwnMessage = event.user === this.botUserId || (!!this.botId && event.bot_id === this.botId);
1151
+ if (isOwnMessage) {
808
1152
  ack();
809
- return;
1153
+ return false;
810
1154
  }
811
- const isExternalMessagingBotMessage = !!e.bot_id || e.subtype === "bot_message";
812
- if (isExternalMessagingBotMessage) {
813
- if (e.subtype !== undefined && e.subtype !== "bot_message" && e.subtype !== "file_share") {
814
- ack();
815
- return;
1155
+ const authorIsKnownHuman = !!event.user && this.users.get(event.user)?.isBot === false;
1156
+ const isExternalMessage = event.subtype === "bot_message" || (!!event.bot_id && !authorIsKnownHuman);
1157
+ if (isExternalMessage) {
1158
+ const supportedSubtype = BOT_MESSAGE_SUBTYPES.has(event.subtype);
1159
+ if (supportedSubtype && hasSlackContent) {
1160
+ void this.logExternalMessagingBotMessage(event).catch((err) => {
1161
+ log.logWarning("Failed to log Slack bot message", String(err));
1162
+ });
816
1163
  }
817
- if (!hasSlackContent) {
818
- ack();
819
- return;
1164
+ ack();
1165
+ return false;
1166
+ }
1167
+ const isSupportedUserMessage = !!event.user && USER_MESSAGE_SUBTYPES.has(event.subtype) && hasSlackContent;
1168
+ if (isSupportedUserMessage)
1169
+ return true;
1170
+ ack();
1171
+ return false;
1172
+ }
1173
+ async deliverTaskUpdate(event, attachmentsPromise) {
1174
+ if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
1175
+ return false;
1176
+ event.attachments = await attachmentsPromise;
1177
+ const context = this.createContext(event);
1178
+ const post = (text) => event.thread_ts
1179
+ ? this.postInThread(event.channel, event.thread_ts, text)
1180
+ : this.postMessage(event.channel, text);
1181
+ try {
1182
+ const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1183
+ if (!tasks.length)
1184
+ return false;
1185
+ const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
1186
+ const user = this.users.get(event.user);
1187
+ const intent = event.attachments?.length
1188
+ ? event.thread_ts
1189
+ ? "steer"
1190
+ : "request"
1191
+ : await classifyTaskIntent(buildTaskIntentState(this.conversationDir(event.channel), event, {
1192
+ speaker: user?.displayName ?? user?.userName,
1193
+ tasks,
1194
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1195
+ botUserId: this.botUserId,
1196
+ }), event.text, { conversationId: event.channel, inTaskThread: !!event.thread_ts });
1197
+ if (intent === "status") {
1198
+ const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
1199
+ const text = selection.length > 1
1200
+ ? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
1201
+ : formatTaskStatus(selection);
1202
+ const ts = await post(text);
1203
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1204
+ return true;
1205
+ }
1206
+ if (intent === "steer") {
1207
+ const target = event.thread_ts
1208
+ ? context.message
1209
+ : active.length === 1
1210
+ ? {
1211
+ ...context.message,
1212
+ sessionKey: active[0].sessionKey,
1213
+ threadTs: active[0].threadTs,
1214
+ }
1215
+ : undefined;
1216
+ if (target && (await this.handler.steer?.(target))) {
1217
+ const text = "收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。";
1218
+ const ts = await post(text);
1219
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1220
+ return true;
1221
+ }
820
1222
  }
821
- void this.logExternalMessagingBotMessage(e).catch((err) => {
822
- log.logWarning("Failed to log Slack bot message", String(err));
1223
+ }
1224
+ catch (error) {
1225
+ await post(error instanceof Error ? error.message : "Could not deliver task update.");
1226
+ return true;
1227
+ }
1228
+ return false;
1229
+ }
1230
+ async evaluateJevAddressed(event) {
1231
+ if (!event.text.trim() || matchMagicWord(event.text) === "stop")
1232
+ return false;
1233
+ try {
1234
+ const user = this.users.get(event.user);
1235
+ const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
1236
+ speaker: user?.displayName ?? user?.userName,
1237
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1238
+ botUserId: this.botUserId,
823
1239
  });
824
- ack();
825
- return;
1240
+ const result = await evaluateWithJev(state, { addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS } }, { caller: "slack_auto_reply" });
1241
+ const probability = result.answers.addressed.probability;
1242
+ log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
1243
+ return probability > 0.5;
826
1244
  }
827
- if (!e.user) {
828
- ack();
829
- return;
1245
+ catch (err) {
1246
+ if (err instanceof JevNotConfiguredError) {
1247
+ log.logWarning("Slack auto-reply jev mode requires OPENROUTER_API_KEY; treating message as unaddressed", String(err));
1248
+ return false;
1249
+ }
1250
+ reportUserFacingError(err, {
1251
+ domain: "chat_platform",
1252
+ surface: "slack_auto_reply_jev",
1253
+ operation: "evaluate",
1254
+ severity: "warning",
1255
+ context: { conversationId: event.address.conversationId },
1256
+ });
1257
+ return false;
830
1258
  }
831
- if (e.subtype !== undefined && e.subtype !== "file_share") {
832
- ack();
1259
+ }
1260
+ async handleMessageEvent({ event, ack }) {
1261
+ const e = event;
1262
+ if (!this.admitHumanMessage(e, ack))
833
1263
  return;
834
- }
835
- if (!hasSlackContent) {
836
- ack();
1264
+ if (this.isRedelivery("message", e.channel, e.ts)) {
1265
+ log.logInfo(`[${e.channel}] Ignoring redelivered message ${e.ts}`);
1266
+ await ack();
837
1267
  return;
838
1268
  }
839
- const isDM = e.channel_type === "im";
1269
+ const isDM = e.channel_type === "im" || e.channel.startsWith("D");
840
1270
  const conversationKind = isDM ? "direct" : "shared";
841
1271
  const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
842
- // Skip channel @mentions - already handled by app_mention event
843
1272
  if (!isDM && isMessagingBotMention) {
844
1273
  ack();
845
1274
  return;
846
1275
  }
847
- const isThreadReply = !!e.thread_ts;
848
1276
  const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
849
- const slackEvent = {
850
- type: isDM ? "dm" : "mention",
1277
+ if (isDM && e.thread_ts && e.text) {
1278
+ void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
1279
+ }
1280
+ const slackEvent = createConversationEvent({
1281
+ platform: "slack",
851
1282
  conversationId: e.channel,
1283
+ type: (isDM ? "dm" : "mention"),
852
1284
  conversationKind,
853
- channel: e.channel,
854
1285
  ts: e.ts,
855
1286
  thread_ts: e.thread_ts,
856
1287
  user: e.user,
857
1288
  text: this.stripOwnMention(e.text),
858
- files: e.files,
859
1289
  sessionKey,
860
- };
1290
+ channel: e.channel,
1291
+ files: e.files,
1292
+ });
861
1293
  const attachmentsPromise = this.logUserMessage(slackEvent);
862
- // Only trigger processing for messages AFTER startup (not replayed old messages)
863
1294
  if (this.startupTs && e.ts < this.startupTs) {
864
1295
  log.logInfo(`[${e.channel}] Skipping old message (pre-startup): ${slackEvent.text.substring(0, 30)}`);
865
1296
  void attachmentsPromise.catch((err) => {
@@ -868,136 +1299,77 @@ export class SlackMessagingBot {
868
1299
  ack();
869
1300
  return;
870
1301
  }
871
- // Stop command for DM only (app_mention handles shared-channel "@mikan stop").
872
- if (isDM && this.isStopText(slackEvent.text)) {
873
- const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
874
- if (stopTarget) {
875
- this.handler.handleStop(stopTarget, e.channel, this);
876
- }
877
- else {
878
- this.postMessage(e.channel, formatNothingRunning("slack"));
879
- }
880
- void attachmentsPromise.catch((err) => {
881
- log.logWarning("Failed to log Slack message", String(err));
882
- });
883
- ack();
884
- return;
885
- }
886
- const enqueueTriggered = () => {
887
- const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
888
- // Auto-reply top-level channel messages start with no sessionKey because
889
- // they are only candidates until the policy allows them. Once triggered,
890
- // persist the resolved key on the event; otherwise the runtime fallback
891
- // treats the message ts as a thread session (`channel:ts`) instead of the
892
- // persistent top-level channel session.
893
- slackEvent.sessionKey = activeSessionKey;
894
- this.processSlackMessageIntake({
895
- event: slackEvent,
896
- attachmentsPromise,
897
- queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
898
- isAutoReplyCandidate: false,
899
- });
900
- };
901
- const logOnly = () => {
902
- void attachmentsPromise.catch((err) => {
903
- log.logWarning("Failed to log Slack message", String(err));
904
- });
905
- };
906
- if (isDM) {
907
- enqueueTriggered();
908
- ack();
909
- return;
910
- }
911
- if (isThreadReply) {
912
- logOnly();
1302
+ const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1303
+ slackEvent.sessionKey = activeSessionKey;
1304
+ const taskControl = isDM &&
1305
+ (e.thread_ts
1306
+ ? this.isTaskThread(e.channel, e.thread_ts)
1307
+ : this.hasRunningTaskThread(e.channel) || isTaskStatusQuestion(slackEvent.text));
1308
+ if (taskControl) {
913
1309
  ack();
914
- return;
1310
+ if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
1311
+ return;
915
1312
  }
916
- const activeSessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
917
- slackEvent.sessionKey = activeSessionKey;
918
- this.processSlackMessageIntake({
1313
+ const autoReplyMode = isDM
1314
+ ? "off"
1315
+ : slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));
1316
+ const autoReply = autoReplyMode === "on"
1317
+ ? true
1318
+ : autoReplyMode === "jev"
1319
+ ? await this.evaluateJevAddressed(slackEvent)
1320
+ : false;
1321
+ const intake = this.processSlackMessageIntake({
919
1322
  event: slackEvent,
920
1323
  attachmentsPromise,
921
- queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
922
- isAutoReplyCandidate: true,
923
- onNotTriggered: logOnly,
1324
+ queueKey: await this.resolveQueueKey(e.channel, activeSessionKey),
1325
+ addressed: isDM || autoReply,
1326
+ magicWordAddressed: isDM,
924
1327
  });
925
- ack();
1328
+ if (!taskControl)
1329
+ ack();
1330
+ await intake;
926
1331
  }
927
- async handleSlashCommand({ body, ack, }) {
1332
+ async handleSlashCommand({ body, ack }) {
928
1333
  const payload = body;
929
1334
  await ack();
930
1335
  if (!payload.command || !payload.channel_id || !payload.user_id) {
931
1336
  return;
932
1337
  }
933
1338
  const { command, text, channel_id, user_id, user_name, thread_ts } = payload;
934
- let handlerPromise = null;
935
- if (command === "/pi-login") {
936
- handlerPromise = this.routeSlashLoginCommand({
937
- command,
938
- text,
939
- channel_id,
940
- user_id,
941
- user_name,
942
- });
943
- }
944
- else if (command === "/pi-new") {
945
- handlerPromise = this.routeSlashNewCommand({ command, channel_id, user_id, user_name });
946
- }
947
- else if (command === "/pi-session") {
948
- handlerPromise = this.routeSlashSessionCommand({
949
- command,
950
- channel_id,
951
- user_id,
952
- user_name,
953
- thread_ts,
954
- });
955
- }
956
- else if (command === "/pi-model") {
957
- handlerPromise = this.routeSlashModelCommand({
958
- command,
959
- text,
960
- channel_id,
961
- user_id,
962
- user_name,
963
- });
964
- }
965
- else if (command === "/pi-sandbox" ||
966
- command === "/pi-auto-reply" ||
967
- command === "/pi-extensions") {
968
- handlerPromise = this.routeSlashModelCommand({
1339
+ const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
1340
+ if (!entry)
1341
+ return;
1342
+ if (!entry.slackRoute)
1343
+ return;
1344
+ try {
1345
+ await this.routeSlashCommand(entry.slackRoute, {
969
1346
  command,
970
1347
  text,
971
1348
  channel_id,
972
1349
  user_id,
973
1350
  user_name,
974
- });
975
- }
976
- else if (command === "/pi-admin") {
977
- handlerPromise = this.routeSlashAdminCommand({
978
- command,
979
- channel_id,
980
- user_id,
981
- user_name,
982
1351
  thread_ts,
983
1352
  });
984
1353
  }
985
- if (!handlerPromise) {
986
- return;
1354
+ catch (err) {
1355
+ log.logWarning("Slack slash command error", errorMessage(err));
987
1356
  }
988
- handlerPromise.catch((err) => {
989
- log.logWarning("Slack slash command error", err instanceof Error ? err.message : String(err));
990
- });
991
1357
  }
992
1358
  handleAppHomeOpened({ event, ack }) {
993
1359
  const e = event;
994
1360
  ack();
1361
+ if (e.tab === "messages") {
1362
+ if (e.channel) {
1363
+ void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
1364
+ }
1365
+ return;
1366
+ }
995
1367
  if (e.tab !== "home")
996
1368
  return;
997
1369
  this.webClient.views
998
1370
  .publish({
999
1371
  user_id: e.user,
1000
- view: this.buildHomeView(),
1372
+ view: this.buildHomeView(e.channel),
1001
1373
  })
1002
1374
  .catch((err) => {
1003
1375
  log.logWarning(`Failed to publish App Home view`, String(err));
@@ -1011,31 +1383,28 @@ export class SlackMessagingBot {
1011
1383
  }
1012
1384
  if (!action.action_id?.startsWith("force_stop_")) {
1013
1385
  ack();
1014
- this.handleSlackInteraction(body, action);
1386
+ await this.handleSlackInteraction(body, action);
1015
1387
  return;
1016
1388
  }
1017
1389
  ack();
1018
- const sessionKey = action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1390
+ const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1019
1391
  const userId = body.user?.id;
1020
- const channelId = body.container?.channel_id || sessionKey.split(":")[0];
1392
+ const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
1021
1393
  log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
1022
- // Use handler's forceStop method
1023
- this.handler.forceStop(sessionKey);
1024
- // Notify in channel
1394
+ this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
1025
1395
  await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
1026
- // Refresh home tab
1027
1396
  if (userId) {
1028
- this.webClient.views
1029
- .publish({
1397
+ this.openDirectConversation(userId)
1398
+ .then((dmChannelId) => this.webClient.views.publish({
1030
1399
  user_id: userId,
1031
- view: this.buildHomeView(),
1032
- })
1400
+ view: this.buildHomeView(dmChannelId),
1401
+ }))
1033
1402
  .catch((err) => {
1034
1403
  log.logWarning(`Failed to refresh App Home view`, String(err));
1035
1404
  });
1036
1405
  }
1037
1406
  }
1038
- handleSlackInteraction(body, action) {
1407
+ async handleSlackInteraction(body, action) {
1039
1408
  const container = body.container ?? {};
1040
1409
  const channelId = container.channel_id;
1041
1410
  const userId = body.user?.id;
@@ -1045,6 +1414,8 @@ export class SlackMessagingBot {
1045
1414
  const selectedOptions = Array.isArray(action.selected_options)
1046
1415
  ? action.selected_options
1047
1416
  : undefined;
1417
+ const threadTs = container.thread_ts;
1418
+ const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1048
1419
  const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
1049
1420
  const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
1050
1421
  const valueText = selectedTexts?.length
@@ -1052,12 +1423,10 @@ export class SlackMessagingBot {
1052
1423
  : (selectedText ?? action.value ?? action.action_id);
1053
1424
  const text = `[Slack action] ${action.action_id}: ${valueText}`;
1054
1425
  const ts = `action:${Date.now()}`;
1055
- const threadTs = container.thread_ts;
1056
- const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1057
1426
  this.logToFile(channelId, {
1058
1427
  date: new Date().toISOString(),
1059
1428
  ts,
1060
- ...(threadTs ? { threadTs } : {}),
1429
+ threadTs: threadTs || undefined,
1061
1430
  user: userId,
1062
1431
  userName: body.user?.username ?? body.user?.name,
1063
1432
  text,
@@ -1080,50 +1449,87 @@ export class SlackMessagingBot {
1080
1449
  messageTs: container.message_ts,
1081
1450
  },
1082
1451
  });
1083
- const event = {
1084
- type: "slack_action",
1452
+ const event = createConversationEvent({
1453
+ platform: "slack",
1085
1454
  conversationId: channelId,
1455
+ type: "slack_action",
1086
1456
  conversationKind: channelId.startsWith("D") ? "direct" : "shared",
1087
1457
  ts,
1088
1458
  user: userId,
1089
1459
  text,
1090
1460
  attachments: [],
1091
- ...(threadTs ? { thread_ts: threadTs } : {}),
1461
+ thread_ts: threadTs || undefined,
1092
1462
  sessionKey,
1093
- };
1094
- this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
1463
+ });
1464
+ this.getQueue(await this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
1095
1465
  const slackEvent = {
1096
- type: event.conversationKind === "direct" ? "dm" : "mention",
1097
- conversationId: channelId,
1098
- conversationKind: event.conversationKind,
1466
+ ...createConversationEvent({
1467
+ platform: "slack",
1468
+ conversationId: channelId,
1469
+ type: (event.conversationKind === "direct" ? "dm" : "mention"),
1470
+ conversationKind: event.conversationKind,
1471
+ ts,
1472
+ thread_ts: threadTs,
1473
+ user: userId,
1474
+ text,
1475
+ attachments: [],
1476
+ sessionKey,
1477
+ }),
1099
1478
  channel: channelId,
1100
- ts,
1101
- thread_ts: threadTs,
1102
- user: userId,
1103
- text,
1104
1479
  attachments: [],
1105
- sessionKey,
1106
1480
  };
1107
1481
  return this.handler.handleEvent(event, this, this.createContext(slackEvent));
1108
1482
  });
1109
1483
  }
1110
- /**
1111
- * Log a user message to log.jsonl after attachments are ready.
1112
- */
1484
+ async processAttachments(channelId, files, timestamp) {
1485
+ const items = [];
1486
+ for (const file of files) {
1487
+ const url = file.url_private_download || file.url_private;
1488
+ if (!url)
1489
+ continue;
1490
+ if (!file.name) {
1491
+ throw new Error(`Attachment missing name for URL: ${url}`);
1492
+ }
1493
+ items.push({
1494
+ name: file.name,
1495
+ timestampMs: Math.floor(parseFloat(timestamp) * 1000),
1496
+ download: (destPath) => this.downloadSlackFile(url, destPath),
1497
+ });
1498
+ }
1499
+ const office = this.workspace.office(createOfficeAddress("slack", channelId));
1500
+ const { saved, failed } = await saveIncomingAttachments(office, items);
1501
+ const firstFailure = failed[0];
1502
+ if (firstFailure) {
1503
+ const { name, error } = firstFailure;
1504
+ const errorMsg = errorMessage(error);
1505
+ throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
1506
+ }
1507
+ return saved;
1508
+ }
1509
+ async downloadSlackFile(url, destPath) {
1510
+ assertSlackFileUrl(url);
1511
+ await withRetry(async () => {
1512
+ const response = await this.fetchFn(url, {
1513
+ headers: { Authorization: `Bearer ${this.botToken}` },
1514
+ });
1515
+ if (!response.ok) {
1516
+ throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
1517
+ }
1518
+ await writeResponseToFile(response, destPath);
1519
+ }, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
1520
+ }
1113
1521
  async logUserMessage(event) {
1114
1522
  const user = this.users.get(event.user);
1115
1523
  let attachments = [];
1116
1524
  let attachmentError;
1117
1525
  if (event.files) {
1118
1526
  try {
1119
- attachments = await this.store.processAttachments(event.channel, event.files, event.ts);
1527
+ attachments = await this.processAttachments(event.channel, event.files, event.ts);
1120
1528
  }
1121
1529
  catch (err) {
1122
1530
  attachmentError = err;
1123
1531
  }
1124
1532
  }
1125
- // Always write the text log, even if attachment processing failed — we want
1126
- // a record of the user message regardless of file-handling errors.
1127
1533
  this.logToFile(event.channel, {
1128
1534
  date: new Date(parseFloat(event.ts) * 1000).toISOString(),
1129
1535
  ts: event.ts,
@@ -1141,7 +1547,7 @@ export class SlackMessagingBot {
1141
1547
  }
1142
1548
  async logExternalMessagingBotMessage(event) {
1143
1549
  const attachments = event.files
1144
- ? await this.store.processAttachments(event.channel, event.files, event.ts)
1550
+ ? await this.processAttachments(event.channel, event.files, event.ts)
1145
1551
  : [];
1146
1552
  const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
1147
1553
  this.logToFile(event.channel, {
@@ -1160,31 +1566,39 @@ export class SlackMessagingBot {
1160
1566
  });
1161
1567
  return attachments;
1162
1568
  }
1163
- // ==========================================================================
1164
- // Private - Backfill
1165
- // ==========================================================================
1166
1569
  async getExistingTimestamps(channelId) {
1167
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1570
+ const { logPath } = this.office(channelId);
1168
1571
  const timestamps = new Set();
1169
- if (!existsSync(logPath))
1572
+ const content = readTextFileNoFollowIfExists(logPath);
1573
+ if (content === undefined)
1170
1574
  return timestamps;
1171
- const content = await readFile(logPath, "utf-8");
1172
1575
  const lines = content.trim().split("\n").filter(Boolean);
1173
1576
  for (let i = 0; i < lines.length; i++) {
1174
1577
  try {
1175
- const entry = JSON.parse(lines[i]);
1578
+ const entry = JSON.parse(lines[i] ?? "");
1176
1579
  if (entry.ts)
1177
1580
  timestamps.add(entry.ts);
1178
1581
  }
1179
1582
  catch (err) {
1180
- log.logWarning(`Skipping malformed log entry at ${logPath}:${i + 1}`, err instanceof Error ? err.message : String(err));
1583
+ log.logWarning(`Skipping malformed log entry at ${logPath}:${i + 1}`, errorMessage(err));
1181
1584
  }
1182
1585
  }
1183
1586
  return timestamps;
1184
1587
  }
1588
+ isBackfillableMessage(message) {
1589
+ if (message.user === this.botUserId)
1590
+ return true;
1591
+ if (hasBotIdentity(message)) {
1592
+ if (this.botId && message.bot_id === this.botId)
1593
+ return false;
1594
+ return BOT_MESSAGE_SUBTYPES.has(message.subtype) && hasMessageContent(message);
1595
+ }
1596
+ return (!!message.user &&
1597
+ USER_MESSAGE_SUBTYPES.has(message.subtype) &&
1598
+ hasMessageContent(message, false));
1599
+ }
1185
1600
  async backfillChannel(channelId, upperBoundTs) {
1186
1601
  const existingTs = await this.getExistingTimestamps(channelId);
1187
- // Find the biggest ts in log.jsonl
1188
1602
  let lastLoggedTs;
1189
1603
  for (const ts of existingTs) {
1190
1604
  if (!lastLoggedTs || parseFloat(ts) > parseFloat(lastLoggedTs))
@@ -1197,8 +1611,8 @@ export class SlackMessagingBot {
1197
1611
  do {
1198
1612
  const result = await this.webClient.conversations.history({
1199
1613
  channel: channelId,
1200
- oldest: lastLoggedTs, // Only fetch messages newer than what we have
1201
- latest: upperBoundTs, // Do not race live socket events after startup
1614
+ oldest: lastLoggedTs,
1615
+ latest: upperBoundTs,
1202
1616
  inclusive: false,
1203
1617
  limit: 1000,
1204
1618
  cursor,
@@ -1209,40 +1623,15 @@ export class SlackMessagingBot {
1209
1623
  cursor = result.response_metadata?.next_cursor;
1210
1624
  pageCount++;
1211
1625
  } while (cursor && pageCount < maxPages);
1212
- // Filter: include mikan's messages, external app/bot messages, and user messages.
1213
1626
  const relevantMessages = allMessages.filter((msg) => {
1214
1627
  if (!msg.ts || existingTs.has(msg.ts))
1215
- return false; // Skip duplicates
1216
- if (msg.user === this.botUserId)
1217
- return true;
1218
- const isExternalMessagingBotMessage = !!msg.bot_id || msg.subtype === "bot_message";
1219
- if (isExternalMessagingBotMessage) {
1220
- if (this.botId && msg.bot_id === this.botId)
1221
- return false;
1222
- if (msg.subtype !== undefined &&
1223
- msg.subtype !== "bot_message" &&
1224
- msg.subtype !== "file_share") {
1225
- return false;
1226
- }
1227
- return (!!msg.text ||
1228
- !!(msg.files && msg.files.length > 0) ||
1229
- !!msg.blocks?.length ||
1230
- !!msg.attachments?.length);
1231
- }
1232
- if (msg.subtype !== undefined && msg.subtype !== "file_share")
1233
- return false;
1234
- if (!msg.user)
1235
- return false;
1236
- if (!msg.text && (!msg.files || msg.files.length === 0))
1237
1628
  return false;
1238
- return true;
1629
+ return this.isBackfillableMessage(msg);
1239
1630
  });
1240
- // Reverse to chronological order
1241
1631
  relevantMessages.reverse();
1242
- // Log each message to log.jsonl
1243
1632
  for (const msg of relevantMessages) {
1244
1633
  const isMikanMessage = msg.user === this.botUserId;
1245
- const isExternalMessagingBotMessage = !isMikanMessage && (!!msg.bot_id || msg.subtype === "bot_message");
1634
+ const isExternalMessagingBotMessage = !isMikanMessage && hasBotIdentity(msg);
1246
1635
  if (isExternalMessagingBotMessage) {
1247
1636
  await this.logExternalMessagingBotMessage({ ...msg, channel: channelId, ts: msg.ts });
1248
1637
  continue;
@@ -1250,7 +1639,7 @@ export class SlackMessagingBot {
1250
1639
  const user = this.users.get(msg.user);
1251
1640
  const text = this.stripOwnMention(msg.text);
1252
1641
  const attachments = msg.files
1253
- ? await this.store.processAttachments(channelId, msg.files, msg.ts)
1642
+ ? await this.processAttachments(channelId, msg.files, msg.ts)
1254
1643
  : [];
1255
1644
  this.logToFile(channelId, {
1256
1645
  date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
@@ -1268,11 +1657,9 @@ export class SlackMessagingBot {
1268
1657
  }
1269
1658
  async backfillAllChannels(upperBoundTs) {
1270
1659
  const startTime = Date.now();
1271
- // Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
1272
1660
  const channelsToBackfill = [];
1273
1661
  for (const [channelId, channel] of this.channels) {
1274
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1275
- if (existsSync(logPath)) {
1662
+ if (existsSync(this.office(channelId).logPath)) {
1276
1663
  channelsToBackfill.push([channelId, channel]);
1277
1664
  }
1278
1665
  }
@@ -1288,38 +1675,32 @@ export class SlackMessagingBot {
1288
1675
  catch (error) {
1289
1676
  log.logWarning(`Failed to backfill #${channel.name}`, String(error));
1290
1677
  }
1291
- // Add delay between channels to avoid hitting Slack rate limits
1292
- if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
1678
+ if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
1293
1679
  await new Promise((resolve) => setTimeout(resolve, 500));
1294
1680
  }
1295
1681
  }
1296
1682
  const durationMs = Date.now() - startTime;
1297
1683
  log.logBackfillComplete(totalMessages, durationMs);
1298
1684
  }
1299
- // ==========================================================================
1300
- // Private - Fetch Users/Channels
1301
- // ==========================================================================
1302
1685
  async fetchUsers() {
1303
1686
  let cursor;
1304
1687
  do {
1305
1688
  const result = await this.webClient.users.list({ limit: 200, cursor });
1306
1689
  const members = result.members;
1307
- if (members) {
1308
- for (const u of members) {
1309
- if (u.id && u.name && !u.deleted) {
1310
- this.users.set(u.id, {
1311
- id: u.id,
1312
- userName: u.name,
1313
- displayName: u.real_name || u.name,
1314
- });
1315
- }
1316
- }
1690
+ for (const u of members ?? []) {
1691
+ if (!u.id || !u.name || u.deleted)
1692
+ continue;
1693
+ this.users.set(u.id, {
1694
+ id: u.id,
1695
+ userName: u.name,
1696
+ displayName: u.real_name || u.name,
1697
+ isBot: !!u.is_bot,
1698
+ });
1317
1699
  }
1318
1700
  cursor = result.response_metadata?.next_cursor;
1319
1701
  } while (cursor);
1320
1702
  }
1321
1703
  async fetchChannels() {
1322
- // Fetch public/private channels
1323
1704
  let cursor;
1324
1705
  do {
1325
1706
  const result = await this.webClient.conversations.list({
@@ -1329,16 +1710,18 @@ export class SlackMessagingBot {
1329
1710
  cursor,
1330
1711
  });
1331
1712
  const channels = result.channels;
1332
- if (channels) {
1333
- for (const c of channels) {
1334
- if (c.id && c.name && c.is_member) {
1335
- this.channels.set(c.id, { id: c.id, name: c.name });
1336
- }
1337
- }
1713
+ for (const c of channels ?? []) {
1714
+ if (!c.id || !c.name || !c.is_member)
1715
+ continue;
1716
+ this.channels.set(c.id, {
1717
+ id: c.id,
1718
+ name: c.name,
1719
+ isPrivate: typeof c.is_private === "boolean" ? c.is_private : undefined,
1720
+ isExternallyShared: c.is_shared || c.is_ext_shared ? true : undefined,
1721
+ });
1338
1722
  }
1339
1723
  cursor = result.response_metadata?.next_cursor;
1340
1724
  } while (cursor);
1341
- // Also fetch DM channels (IMs)
1342
1725
  cursor = undefined;
1343
1726
  do {
1344
1727
  const result = await this.webClient.conversations.list({
@@ -1347,15 +1730,12 @@ export class SlackMessagingBot {
1347
1730
  cursor,
1348
1731
  });
1349
1732
  const ims = result.channels;
1350
- if (ims) {
1351
- for (const im of ims) {
1352
- if (im.id) {
1353
- // Use user's name as channel name for DMs
1354
- const user = im.user ? this.users.get(im.user) : undefined;
1355
- const name = user ? `DM:${user.userName}` : `DM:${im.id}`;
1356
- this.channels.set(im.id, { id: im.id, name });
1357
- }
1358
- }
1733
+ for (const im of ims ?? []) {
1734
+ if (!im.id)
1735
+ continue;
1736
+ const user = im.user ? this.users.get(im.user) : undefined;
1737
+ const name = user ? `DM:${user.userName}` : `DM:${im.id}`;
1738
+ this.channels.set(im.id, { id: im.id, name });
1359
1739
  }
1360
1740
  cursor = result.response_metadata?.next_cursor;
1361
1741
  } while (cursor);