@geminixiang/mikan 1.0.0-beta.7 → 1.0.0-beta.71

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 (856) hide show
  1. package/CHANGELOG.md +1052 -0
  2. package/README.md +98 -38
  3. package/dist/adapters/commands/admin.d.ts.map +1 -0
  4. package/dist/{commands → adapters/commands}/admin.js +11 -8
  5. package/dist/adapters/commands/admin.js.map +1 -0
  6. package/dist/{commands → adapters/commands}/auto-reply.d.ts +0 -3
  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 +166 -0
  13. package/dist/adapters/commands/login.js.map +1 -0
  14. package/dist/adapters/commands/manifest.d.ts +37 -0
  15. package/dist/adapters/commands/manifest.d.ts.map +1 -0
  16. package/dist/adapters/commands/manifest.js +155 -0
  17. package/dist/adapters/commands/manifest.js.map +1 -0
  18. package/dist/adapters/commands/model.d.ts +15 -0
  19. package/dist/adapters/commands/model.d.ts.map +1 -0
  20. package/dist/adapters/commands/model.js +153 -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 +41 -0
  24. package/dist/adapters/commands/new.js.map +1 -0
  25. package/dist/adapters/commands/registry.d.ts +5 -0
  26. package/dist/adapters/commands/registry.d.ts.map +1 -0
  27. package/dist/adapters/commands/registry.js +43 -0
  28. package/dist/adapters/commands/registry.js.map +1 -0
  29. package/dist/adapters/commands/sandbox.d.ts.map +1 -0
  30. package/dist/adapters/commands/sandbox.js +125 -0
  31. package/dist/adapters/commands/sandbox.js.map +1 -0
  32. package/dist/adapters/commands/session-view.d.ts.map +1 -0
  33. package/dist/{commands → adapters/commands}/session-view.js +23 -16
  34. package/dist/adapters/commands/session-view.js.map +1 -0
  35. package/dist/adapters/commands/types.d.ts +145 -0
  36. package/dist/adapters/commands/types.d.ts.map +1 -0
  37. package/dist/adapters/commands/types.js.map +1 -0
  38. package/dist/adapters/commands/utils.d.ts +14 -0
  39. package/dist/adapters/commands/utils.d.ts.map +1 -0
  40. package/dist/{commands → adapters/commands}/utils.js +18 -1
  41. package/dist/adapters/commands/utils.js.map +1 -0
  42. package/dist/adapters/discord/bot.d.ts +11 -8
  43. package/dist/adapters/discord/bot.d.ts.map +1 -1
  44. package/dist/adapters/discord/bot.js +223 -256
  45. package/dist/adapters/discord/bot.js.map +1 -1
  46. package/dist/adapters/discord/components.d.ts +28 -0
  47. package/dist/adapters/discord/components.d.ts.map +1 -0
  48. package/dist/adapters/discord/components.js +32 -0
  49. package/dist/adapters/discord/components.js.map +1 -0
  50. package/dist/adapters/discord/context.d.ts +2 -6
  51. package/dist/adapters/discord/context.d.ts.map +1 -1
  52. package/dist/adapters/discord/context.js +64 -218
  53. package/dist/adapters/discord/context.js.map +1 -1
  54. package/dist/adapters/discord/format.d.ts +3 -0
  55. package/dist/adapters/discord/format.d.ts.map +1 -0
  56. package/dist/adapters/discord/format.js +84 -0
  57. package/dist/adapters/discord/format.js.map +1 -0
  58. package/dist/adapters/discord/types.d.ts +8 -1
  59. package/dist/adapters/discord/types.d.ts.map +1 -1
  60. package/dist/adapters/discord/types.js.map +1 -1
  61. package/dist/adapters/github/bot.d.ts +102 -0
  62. package/dist/adapters/github/bot.d.ts.map +1 -0
  63. package/dist/adapters/github/bot.js +720 -0
  64. package/dist/adapters/github/bot.js.map +1 -0
  65. package/dist/adapters/github/client.d.ts +122 -0
  66. package/dist/adapters/github/client.d.ts.map +1 -0
  67. package/dist/adapters/github/client.js +301 -0
  68. package/dist/adapters/github/client.js.map +1 -0
  69. package/dist/adapters/github/context.d.ts +5 -0
  70. package/dist/adapters/github/context.d.ts.map +1 -0
  71. package/dist/adapters/github/context.js +101 -0
  72. package/dist/adapters/github/context.js.map +1 -0
  73. package/dist/adapters/github/github-ops.d.ts +78 -0
  74. package/dist/adapters/github/github-ops.d.ts.map +1 -0
  75. package/dist/adapters/github/github-ops.js +337 -0
  76. package/dist/adapters/github/github-ops.js.map +1 -0
  77. package/dist/adapters/github/ids.d.ts +34 -0
  78. package/dist/adapters/github/ids.d.ts.map +1 -0
  79. package/dist/adapters/github/ids.js +57 -0
  80. package/dist/adapters/github/ids.js.map +1 -0
  81. package/dist/adapters/github/repo.d.ts +33 -0
  82. package/dist/adapters/github/repo.d.ts.map +1 -0
  83. package/dist/adapters/github/repo.js +141 -0
  84. package/dist/adapters/github/repo.js.map +1 -0
  85. package/dist/adapters/github/tool-pack.d.ts +14 -0
  86. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  87. package/dist/adapters/github/tool-pack.js +55 -0
  88. package/dist/adapters/github/tool-pack.js.map +1 -0
  89. package/dist/adapters/github/tools/checks.d.ts +18 -0
  90. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  91. package/dist/adapters/github/tools/checks.js +86 -0
  92. package/dist/adapters/github/tools/checks.js.map +1 -0
  93. package/dist/adapters/github/tools/issue.d.ts +21 -0
  94. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  95. package/dist/adapters/github/tools/issue.js +48 -0
  96. package/dist/adapters/github/tools/issue.js.map +1 -0
  97. package/dist/adapters/github/tools/pr.d.ts +22 -0
  98. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  99. package/dist/adapters/github/tools/pr.js +48 -0
  100. package/dist/adapters/github/tools/pr.js.map +1 -0
  101. package/dist/adapters/github/tools/read.d.ts +21 -0
  102. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  103. package/dist/adapters/github/tools/read.js +141 -0
  104. package/dist/adapters/github/tools/read.js.map +1 -0
  105. package/dist/adapters/github/tools/review-reply.d.ts +17 -0
  106. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  107. package/dist/adapters/github/tools/review-reply.js +38 -0
  108. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  109. package/dist/adapters/github/tools/sync.d.ts +17 -0
  110. package/dist/adapters/github/tools/sync.d.ts.map +1 -0
  111. package/dist/adapters/github/tools/sync.js +34 -0
  112. package/dist/adapters/github/tools/sync.js.map +1 -0
  113. package/dist/adapters/github/types.d.ts +345 -0
  114. package/dist/adapters/github/types.d.ts.map +1 -0
  115. package/dist/adapters/github/types.js.map +1 -0
  116. package/dist/adapters/github/webhook.d.ts +20 -0
  117. package/dist/adapters/github/webhook.d.ts.map +1 -0
  118. package/dist/adapters/github/webhook.js +78 -0
  119. package/dist/adapters/github/webhook.js.map +1 -0
  120. package/dist/adapters/index.d.ts +12 -0
  121. package/dist/adapters/index.d.ts.map +1 -0
  122. package/dist/adapters/index.js +19 -0
  123. package/dist/adapters/index.js.map +1 -0
  124. package/dist/adapters/intake.d.ts +17 -6
  125. package/dist/adapters/intake.d.ts.map +1 -1
  126. package/dist/adapters/intake.js +96 -27
  127. package/dist/adapters/intake.js.map +1 -1
  128. package/dist/adapters/markdown-tables.d.ts +19 -0
  129. package/dist/adapters/markdown-tables.d.ts.map +1 -0
  130. package/dist/adapters/markdown-tables.js +152 -0
  131. package/dist/adapters/markdown-tables.js.map +1 -0
  132. package/dist/{platform-messages.d.ts → adapters/messages.d.ts} +3 -2
  133. package/dist/adapters/messages.d.ts.map +1 -0
  134. package/dist/{platform-messages.js → adapters/messages.js} +4 -1
  135. package/dist/adapters/messages.js.map +1 -0
  136. package/dist/adapters/progressive-renderer.d.ts +7 -0
  137. package/dist/adapters/progressive-renderer.d.ts.map +1 -0
  138. package/dist/adapters/progressive-renderer.js +443 -0
  139. package/dist/adapters/progressive-renderer.js.map +1 -0
  140. package/dist/adapters/shared.d.ts +37 -11
  141. package/dist/adapters/shared.d.ts.map +1 -1
  142. package/dist/adapters/shared.js +130 -24
  143. package/dist/adapters/shared.js.map +1 -1
  144. package/dist/adapters/slack/assistant.d.ts +73 -0
  145. package/dist/adapters/slack/assistant.d.ts.map +1 -0
  146. package/dist/adapters/slack/assistant.js +205 -0
  147. package/dist/adapters/slack/assistant.js.map +1 -0
  148. package/dist/adapters/slack/auto-reply-context.d.ts +21 -0
  149. package/dist/adapters/slack/auto-reply-context.d.ts.map +1 -0
  150. package/dist/adapters/slack/auto-reply-context.js +28 -0
  151. package/dist/adapters/slack/auto-reply-context.js.map +1 -0
  152. package/dist/adapters/slack/blocks.d.ts +14 -0
  153. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  154. package/dist/adapters/slack/blocks.js +104 -148
  155. package/dist/adapters/slack/blocks.js.map +1 -1
  156. package/dist/adapters/slack/bot.d.ts +154 -21
  157. package/dist/adapters/slack/bot.d.ts.map +1 -1
  158. package/dist/adapters/slack/bot.js +1037 -484
  159. package/dist/adapters/slack/bot.js.map +1 -1
  160. package/dist/adapters/slack/context.d.ts +2 -6
  161. package/dist/adapters/slack/context.d.ts.map +1 -1
  162. package/dist/adapters/slack/context.js +11 -17
  163. package/dist/adapters/slack/context.js.map +1 -1
  164. package/dist/adapters/slack/jev-context.d.ts +30 -0
  165. package/dist/adapters/slack/jev-context.d.ts.map +1 -0
  166. package/dist/adapters/slack/jev-context.js +67 -0
  167. package/dist/adapters/slack/jev-context.js.map +1 -0
  168. package/dist/adapters/slack/markdown.d.ts +3 -0
  169. package/dist/adapters/slack/markdown.d.ts.map +1 -0
  170. package/dist/adapters/slack/markdown.js +48 -0
  171. package/dist/adapters/slack/markdown.js.map +1 -0
  172. package/dist/adapters/slack/persona.d.ts +15 -0
  173. package/dist/adapters/slack/persona.d.ts.map +1 -0
  174. package/dist/adapters/slack/persona.js +22 -0
  175. package/dist/adapters/slack/persona.js.map +1 -0
  176. package/dist/adapters/slack/response-lifecycle.d.ts +1 -1
  177. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  178. package/dist/adapters/slack/response-lifecycle.js +261 -345
  179. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  180. package/dist/adapters/slack/session.d.ts.map +1 -1
  181. package/dist/adapters/slack/session.js +7 -11
  182. package/dist/adapters/slack/session.js.map +1 -1
  183. package/dist/adapters/slack/stream-limits.d.ts +35 -0
  184. package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
  185. package/dist/adapters/slack/stream-limits.js +56 -0
  186. package/dist/adapters/slack/stream-limits.js.map +1 -0
  187. package/dist/adapters/slack/task-intent.d.ts +32 -0
  188. package/dist/adapters/slack/task-intent.d.ts.map +1 -0
  189. package/dist/adapters/slack/task-intent.js +72 -0
  190. package/dist/adapters/slack/task-intent.js.map +1 -0
  191. package/dist/adapters/slack/task-status.d.ts +7 -0
  192. package/dist/adapters/slack/task-status.d.ts.map +1 -0
  193. package/dist/adapters/slack/task-status.js +107 -0
  194. package/dist/adapters/slack/task-status.js.map +1 -0
  195. package/dist/adapters/slack/tool-pack.d.ts +11 -0
  196. package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
  197. package/dist/adapters/slack/tool-pack.js +45 -0
  198. package/dist/adapters/slack/tool-pack.js.map +1 -0
  199. package/dist/adapters/slack/tools/blockkit.d.ts +14 -0
  200. package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
  201. package/dist/adapters/slack/tools/blockkit.js +106 -0
  202. package/dist/adapters/slack/tools/blockkit.js.map +1 -0
  203. package/dist/adapters/slack/types.d.ts +73 -2
  204. package/dist/adapters/slack/types.d.ts.map +1 -1
  205. package/dist/adapters/slack/types.js.map +1 -1
  206. package/dist/adapters/slack/update-diagnostics.d.ts +14 -0
  207. package/dist/adapters/slack/update-diagnostics.d.ts.map +1 -0
  208. package/dist/adapters/slack/update-diagnostics.js +134 -0
  209. package/dist/adapters/slack/update-diagnostics.js.map +1 -0
  210. package/dist/adapters/telegram/bot.d.ts +10 -9
  211. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  212. package/dist/adapters/telegram/bot.js +133 -202
  213. package/dist/adapters/telegram/bot.js.map +1 -1
  214. package/dist/adapters/telegram/context.d.ts +2 -6
  215. package/dist/adapters/telegram/context.d.ts.map +1 -1
  216. package/dist/adapters/telegram/context.js +53 -190
  217. package/dist/adapters/telegram/context.js.map +1 -1
  218. package/dist/adapters/telegram/types.d.ts +1 -1
  219. package/dist/adapters/telegram/types.d.ts.map +1 -1
  220. package/dist/adapters/telegram/types.js.map +1 -1
  221. package/dist/adapters/types.d.ts +144 -13
  222. package/dist/adapters/types.d.ts.map +1 -1
  223. package/dist/adapters/types.js.map +1 -1
  224. package/dist/adapters/web/admin/portal.d.ts +44 -0
  225. package/dist/adapters/web/admin/portal.d.ts.map +1 -0
  226. package/dist/adapters/web/admin/portal.js +3207 -0
  227. package/dist/adapters/web/admin/portal.js.map +1 -0
  228. package/dist/{web → adapters/web}/admin/provider-models.d.ts +1 -1
  229. package/dist/adapters/web/admin/provider-models.d.ts.map +1 -0
  230. package/dist/{web → adapters/web}/admin/provider-models.js +1 -1
  231. package/dist/adapters/web/admin/provider-models.js.map +1 -0
  232. package/dist/adapters/web/admin/types.d.ts +53 -0
  233. package/dist/adapters/web/admin/types.d.ts.map +1 -0
  234. package/dist/adapters/web/admin/types.js.map +1 -0
  235. package/dist/adapters/web/login/oauth.d.ts +5 -0
  236. package/dist/adapters/web/login/oauth.d.ts.map +1 -0
  237. package/dist/{web → adapters/web}/login/oauth.js +50 -85
  238. package/dist/adapters/web/login/oauth.js.map +1 -0
  239. package/dist/{web → adapters/web}/login/portal.d.ts +9 -3
  240. package/dist/adapters/web/login/portal.d.ts.map +1 -0
  241. package/dist/{web → adapters/web}/login/portal.js +161 -119
  242. package/dist/adapters/web/login/portal.js.map +1 -0
  243. package/dist/{web → adapters/web}/login/types.d.ts +1 -12
  244. package/dist/adapters/web/login/types.d.ts.map +1 -0
  245. package/dist/adapters/web/login/types.js.map +1 -0
  246. package/dist/adapters/web/portal-shell.d.ts +22 -0
  247. package/dist/adapters/web/portal-shell.d.ts.map +1 -0
  248. package/dist/{portal-shell.js → adapters/web/portal-shell.js} +78 -2
  249. package/dist/adapters/web/portal-shell.js.map +1 -0
  250. package/dist/{web → adapters/web}/server.d.ts +14 -8
  251. package/dist/adapters/web/server.d.ts.map +1 -0
  252. package/dist/{web → adapters/web}/server.js +14 -7
  253. package/dist/adapters/web/server.js.map +1 -0
  254. package/dist/adapters/web/session-view/portal.d.ts +20 -0
  255. package/dist/adapters/web/session-view/portal.d.ts.map +1 -0
  256. package/dist/{web → adapters/web}/session-view/portal.js +633 -162
  257. package/dist/adapters/web/session-view/portal.js.map +1 -0
  258. package/dist/{web → adapters/web}/session-view/types.d.ts +11 -6
  259. package/dist/adapters/web/session-view/types.d.ts.map +1 -0
  260. package/dist/adapters/web/session-view/types.js.map +1 -0
  261. package/dist/adapters/web/token-store.d.ts.map +1 -0
  262. package/dist/{web → adapters/web}/token-store.js +1 -1
  263. package/dist/adapters/web/token-store.js.map +1 -0
  264. package/dist/adapters/web/types.d.ts.map +1 -0
  265. package/dist/adapters/web/types.js.map +1 -0
  266. package/dist/cli/arg-grammar.d.ts +11 -0
  267. package/dist/cli/arg-grammar.d.ts.map +1 -0
  268. package/dist/cli/arg-grammar.js +50 -0
  269. package/dist/cli/arg-grammar.js.map +1 -0
  270. package/dist/cli/boot.d.ts +5 -0
  271. package/dist/cli/boot.d.ts.map +1 -0
  272. package/dist/cli/boot.js +90 -0
  273. package/dist/cli/boot.js.map +1 -0
  274. package/dist/cli/download.d.ts.map +1 -0
  275. package/dist/cli/download.js +88 -0
  276. package/dist/cli/download.js.map +1 -0
  277. package/dist/cli/office.d.ts +2 -0
  278. package/dist/cli/office.d.ts.map +1 -0
  279. package/dist/cli/office.js +181 -0
  280. package/dist/cli/office.js.map +1 -0
  281. package/dist/cli/onboard.d.ts +10 -0
  282. package/dist/cli/onboard.d.ts.map +1 -0
  283. package/dist/cli/onboard.js +225 -0
  284. package/dist/cli/onboard.js.map +1 -0
  285. package/dist/cli/process-lifecycle.d.ts +19 -0
  286. package/dist/cli/process-lifecycle.d.ts.map +1 -0
  287. package/dist/cli/process-lifecycle.js +67 -0
  288. package/dist/cli/process-lifecycle.js.map +1 -0
  289. package/dist/cli/sessions.d.ts +2 -0
  290. package/dist/cli/sessions.d.ts.map +1 -0
  291. package/dist/cli/sessions.js +88 -0
  292. package/dist/cli/sessions.js.map +1 -0
  293. package/dist/cli/types.d.ts +25 -0
  294. package/dist/cli/types.d.ts.map +1 -0
  295. package/dist/cli/types.js.map +1 -0
  296. package/dist/dream/index.d.ts +20 -0
  297. package/dist/dream/index.d.ts.map +1 -0
  298. package/dist/dream/index.js +264 -0
  299. package/dist/dream/index.js.map +1 -0
  300. package/dist/dream/types.d.ts +30 -0
  301. package/dist/dream/types.d.ts.map +1 -0
  302. package/dist/dream/types.js +2 -0
  303. package/dist/dream/types.js.map +1 -0
  304. package/dist/env-manifest.d.ts +38 -0
  305. package/dist/env-manifest.d.ts.map +1 -0
  306. package/dist/env-manifest.js +260 -0
  307. package/dist/env-manifest.js.map +1 -0
  308. package/dist/events/index.d.ts +172 -0
  309. package/dist/events/index.d.ts.map +1 -0
  310. package/dist/events/index.js +340 -0
  311. package/dist/events/index.js.map +1 -0
  312. package/dist/events/scheduler.d.ts +34 -0
  313. package/dist/events/scheduler.d.ts.map +1 -0
  314. package/dist/events/scheduler.js +231 -0
  315. package/dist/events/scheduler.js.map +1 -0
  316. package/dist/file-guards.d.ts +26 -0
  317. package/dist/file-guards.d.ts.map +1 -0
  318. package/dist/file-guards.js +133 -0
  319. package/dist/file-guards.js.map +1 -0
  320. package/dist/harness/execution-env.d.ts +19 -0
  321. package/dist/harness/execution-env.d.ts.map +1 -0
  322. package/dist/harness/execution-env.js +262 -0
  323. package/dist/harness/execution-env.js.map +1 -0
  324. package/dist/harness/execution-resolver.d.ts +25 -0
  325. package/dist/harness/execution-resolver.d.ts.map +1 -0
  326. package/dist/harness/execution-resolver.js +156 -0
  327. package/dist/harness/execution-resolver.js.map +1 -0
  328. package/dist/harness/index.d.ts +18 -13
  329. package/dist/harness/index.d.ts.map +1 -1
  330. package/dist/harness/index.js +11 -11
  331. package/dist/harness/index.js.map +1 -1
  332. package/dist/harness/jev.d.ts +86 -0
  333. package/dist/harness/jev.d.ts.map +1 -0
  334. package/dist/harness/jev.js +167 -0
  335. package/dist/harness/jev.js.map +1 -0
  336. package/dist/harness/mcp.d.ts +38 -0
  337. package/dist/harness/mcp.d.ts.map +1 -0
  338. package/dist/harness/mcp.js +304 -0
  339. package/dist/harness/mcp.js.map +1 -0
  340. package/dist/harness/models.d.ts +7 -4
  341. package/dist/harness/models.d.ts.map +1 -1
  342. package/dist/harness/models.js +54 -55
  343. package/dist/harness/models.js.map +1 -1
  344. package/dist/harness/open-connector.d.ts +25 -0
  345. package/dist/harness/open-connector.d.ts.map +1 -0
  346. package/dist/harness/open-connector.js +178 -0
  347. package/dist/harness/open-connector.js.map +1 -0
  348. package/dist/harness/presenter.d.ts +31 -0
  349. package/dist/harness/presenter.d.ts.map +1 -0
  350. package/dist/harness/presenter.js +859 -0
  351. package/dist/harness/presenter.js.map +1 -0
  352. package/dist/harness/prompt.d.ts +30 -0
  353. package/dist/harness/prompt.d.ts.map +1 -0
  354. package/dist/harness/prompt.js +420 -0
  355. package/dist/harness/prompt.js.map +1 -0
  356. package/dist/harness/runner.d.ts +2 -123
  357. package/dist/harness/runner.d.ts.map +1 -1
  358. package/dist/harness/runner.js +551 -416
  359. package/dist/harness/runner.js.map +1 -1
  360. package/dist/harness/session.d.ts +122 -0
  361. package/dist/harness/session.d.ts.map +1 -0
  362. package/dist/harness/session.js +581 -0
  363. package/dist/harness/session.js.map +1 -0
  364. package/dist/harness/skills.d.ts +17 -28
  365. package/dist/harness/skills.d.ts.map +1 -1
  366. package/dist/harness/skills.js +104 -39
  367. package/dist/harness/skills.js.map +1 -1
  368. package/dist/harness/subagent-profiles.d.ts +8 -0
  369. package/dist/harness/subagent-profiles.d.ts.map +1 -0
  370. package/dist/harness/subagent-profiles.js +293 -0
  371. package/dist/harness/subagent-profiles.js.map +1 -0
  372. package/dist/harness/subagent.d.ts +64 -0
  373. package/dist/harness/subagent.d.ts.map +1 -0
  374. package/dist/harness/subagent.js +687 -0
  375. package/dist/harness/subagent.js.map +1 -0
  376. package/dist/harness/tools/attach.d.ts.map +1 -0
  377. package/dist/{adapters/slack → harness}/tools/attach.js +1 -1
  378. package/dist/harness/tools/attach.js.map +1 -0
  379. package/dist/{tools → harness/tools}/event.d.ts +4 -33
  380. package/dist/harness/tools/event.d.ts.map +1 -0
  381. package/dist/harness/tools/event.js +175 -0
  382. package/dist/harness/tools/event.js.map +1 -0
  383. package/dist/harness/tools/generate-image.d.ts +23 -0
  384. package/dist/harness/tools/generate-image.d.ts.map +1 -0
  385. package/dist/harness/tools/generate-image.js +65 -0
  386. package/dist/harness/tools/generate-image.js.map +1 -0
  387. package/dist/harness/tools/host-fn-tool.d.ts +21 -0
  388. package/dist/harness/tools/host-fn-tool.d.ts.map +1 -0
  389. package/dist/harness/tools/host-fn-tool.js +32 -0
  390. package/dist/harness/tools/host-fn-tool.js.map +1 -0
  391. package/dist/harness/tools/index.d.ts +41 -0
  392. package/dist/harness/tools/index.d.ts.map +1 -0
  393. package/dist/harness/tools/index.js +51 -0
  394. package/dist/harness/tools/index.js.map +1 -0
  395. package/dist/harness/tools/jev.d.ts +21 -0
  396. package/dist/harness/tools/jev.d.ts.map +1 -0
  397. package/dist/harness/tools/jev.js +109 -0
  398. package/dist/harness/tools/jev.js.map +1 -0
  399. package/dist/harness/tools/pi-tools.d.ts +18 -0
  400. package/dist/harness/tools/pi-tools.d.ts.map +1 -0
  401. package/dist/harness/tools/pi-tools.js +84 -0
  402. package/dist/harness/tools/pi-tools.js.map +1 -0
  403. package/dist/harness/tools/react.d.ts.map +1 -0
  404. package/dist/{tools → harness/tools}/react.js +6 -17
  405. package/dist/harness/tools/react.js.map +1 -0
  406. package/dist/{tools → harness/tools}/sandbox.d.ts +4 -4
  407. package/dist/harness/tools/sandbox.d.ts.map +1 -0
  408. package/dist/{tools → harness/tools}/sandbox.js +12 -8
  409. package/dist/harness/tools/sandbox.js.map +1 -0
  410. package/dist/harness/tools/subagent.d.ts +85 -0
  411. package/dist/harness/tools/subagent.d.ts.map +1 -0
  412. package/dist/harness/tools/subagent.js +677 -0
  413. package/dist/harness/tools/subagent.js.map +1 -0
  414. package/dist/harness/tools/task.d.ts +20 -0
  415. package/dist/harness/tools/task.d.ts.map +1 -0
  416. package/dist/harness/tools/task.js +79 -0
  417. package/dist/harness/tools/task.js.map +1 -0
  418. package/dist/harness/tools/types.d.ts +29 -0
  419. package/dist/harness/tools/types.d.ts.map +1 -0
  420. package/dist/harness/tools/types.js +2 -0
  421. package/dist/harness/tools/types.js.map +1 -0
  422. package/dist/harness/types.d.ts +384 -25
  423. package/dist/harness/types.d.ts.map +1 -1
  424. package/dist/harness/types.js +1 -1
  425. package/dist/harness/types.js.map +1 -1
  426. package/dist/index.d.ts +33 -9
  427. package/dist/index.d.ts.map +1 -1
  428. package/dist/index.js +19 -6
  429. package/dist/index.js.map +1 -1
  430. package/dist/log.d.ts.map +1 -1
  431. package/dist/log.js +4 -1
  432. package/dist/log.js.map +1 -1
  433. package/dist/main.js +357 -198
  434. package/dist/main.js.map +1 -1
  435. package/dist/observability/index.d.ts +33 -0
  436. package/dist/observability/index.d.ts.map +1 -0
  437. package/dist/observability/index.js +257 -0
  438. package/dist/observability/index.js.map +1 -0
  439. package/dist/observability/instrument.js +14 -6
  440. package/dist/observability/instrument.js.map +1 -1
  441. package/dist/observability/otel.d.ts +10 -0
  442. package/dist/observability/otel.d.ts.map +1 -0
  443. package/dist/observability/otel.js +183 -0
  444. package/dist/observability/otel.js.map +1 -0
  445. package/dist/observability/sentry.d.ts +40 -7
  446. package/dist/observability/sentry.d.ts.map +1 -1
  447. package/dist/observability/sentry.js +176 -32
  448. package/dist/observability/sentry.js.map +1 -1
  449. package/dist/observability/types.d.ts +24 -3
  450. package/dist/observability/types.d.ts.map +1 -1
  451. package/dist/observability/types.js.map +1 -1
  452. package/dist/office/index.d.ts +139 -0
  453. package/dist/office/index.d.ts.map +1 -0
  454. package/dist/office/index.js +1084 -0
  455. package/dist/office/index.js.map +1 -0
  456. package/dist/office/projection.d.ts +42 -0
  457. package/dist/office/projection.d.ts.map +1 -0
  458. package/dist/office/projection.js +182 -0
  459. package/dist/office/projection.js.map +1 -0
  460. package/dist/office/types.d.ts +116 -0
  461. package/dist/office/types.d.ts.map +1 -0
  462. package/dist/office/types.js +2 -0
  463. package/dist/office/types.js.map +1 -0
  464. package/dist/runtime/conversation-runtime.d.ts +1 -1
  465. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  466. package/dist/runtime/conversation-runtime.js +423 -152
  467. package/dist/runtime/conversation-runtime.js.map +1 -1
  468. package/dist/runtime/session-lifecycle.d.ts +76 -0
  469. package/dist/runtime/session-lifecycle.d.ts.map +1 -0
  470. package/dist/runtime/session-lifecycle.js +404 -0
  471. package/dist/runtime/session-lifecycle.js.map +1 -0
  472. package/dist/runtime/types.d.ts +63 -15
  473. package/dist/runtime/types.d.ts.map +1 -1
  474. package/dist/runtime/types.js.map +1 -1
  475. package/dist/sandbox/cloudflare.d.ts +4 -0
  476. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  477. package/dist/sandbox/cloudflare.js +54 -52
  478. package/dist/sandbox/cloudflare.js.map +1 -1
  479. package/dist/sandbox/container.d.ts +3 -0
  480. package/dist/sandbox/container.d.ts.map +1 -1
  481. package/dist/sandbox/container.js +19 -7
  482. package/dist/sandbox/container.js.map +1 -1
  483. package/dist/sandbox/host.d.ts +3 -0
  484. package/dist/sandbox/host.d.ts.map +1 -1
  485. package/dist/sandbox/host.js +44 -35
  486. package/dist/sandbox/host.js.map +1 -1
  487. package/dist/sandbox/identity.d.ts +42 -0
  488. package/dist/sandbox/identity.d.ts.map +1 -0
  489. package/dist/sandbox/identity.js +77 -0
  490. package/dist/sandbox/identity.js.map +1 -0
  491. package/dist/sandbox/index.d.ts +24 -6
  492. package/dist/sandbox/index.d.ts.map +1 -1
  493. package/dist/sandbox/index.js +77 -17
  494. package/dist/sandbox/index.js.map +1 -1
  495. package/dist/sandbox/provisioner.d.ts +139 -0
  496. package/dist/sandbox/provisioner.d.ts.map +1 -0
  497. package/dist/{provisioner.js → sandbox/provisioner.js} +378 -24
  498. package/dist/sandbox/provisioner.js.map +1 -0
  499. package/dist/sandbox/types.d.ts +43 -9
  500. package/dist/sandbox/types.d.ts.map +1 -1
  501. package/dist/sandbox/types.js.map +1 -1
  502. package/dist/sandbox/utils.d.ts +24 -0
  503. package/dist/sandbox/utils.d.ts.map +1 -1
  504. package/dist/sandbox/utils.js +92 -1
  505. package/dist/sandbox/utils.js.map +1 -1
  506. package/dist/sessions/chat-history-sync.d.ts +23 -0
  507. package/dist/sessions/chat-history-sync.d.ts.map +1 -0
  508. package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +239 -175
  509. package/dist/sessions/chat-history-sync.js.map +1 -0
  510. package/dist/sessions/history-line.d.ts +29 -0
  511. package/dist/sessions/history-line.d.ts.map +1 -0
  512. package/dist/sessions/history-line.js +45 -0
  513. package/dist/sessions/history-line.js.map +1 -0
  514. package/dist/sessions/migrate-common.d.ts +43 -0
  515. package/dist/sessions/migrate-common.d.ts.map +1 -0
  516. package/dist/sessions/migrate-common.js +99 -0
  517. package/dist/sessions/migrate-common.js.map +1 -0
  518. package/dist/sessions/migrate-pi-084.d.ts +8 -0
  519. package/dist/sessions/migrate-pi-084.d.ts.map +1 -0
  520. package/dist/sessions/migrate-pi-084.js +302 -0
  521. package/dist/sessions/migrate-pi-084.js.map +1 -0
  522. package/dist/sessions/migrate-v3.d.ts +15 -0
  523. package/dist/sessions/migrate-v3.d.ts.map +1 -0
  524. package/dist/sessions/migrate-v3.js +419 -0
  525. package/dist/sessions/migrate-v3.js.map +1 -0
  526. package/dist/sessions/session-key.d.ts +33 -0
  527. package/dist/sessions/session-key.d.ts.map +1 -0
  528. package/dist/sessions/session-key.js +115 -0
  529. package/dist/sessions/session-key.js.map +1 -0
  530. package/dist/sessions/session-store.d.ts +61 -0
  531. package/dist/sessions/session-store.d.ts.map +1 -0
  532. package/dist/sessions/session-store.js +684 -0
  533. package/dist/sessions/session-store.js.map +1 -0
  534. package/dist/sessions/store.d.ts +17 -10
  535. package/dist/sessions/store.d.ts.map +1 -1
  536. package/dist/sessions/store.js +182 -47
  537. package/dist/sessions/store.js.map +1 -1
  538. package/dist/sessions/types.d.ts +74 -8
  539. package/dist/sessions/types.d.ts.map +1 -1
  540. package/dist/sessions/types.js +1 -1
  541. package/dist/sessions/types.js.map +1 -1
  542. package/dist/settings/apply.d.ts +16 -0
  543. package/dist/settings/apply.d.ts.map +1 -0
  544. package/dist/settings/apply.js +60 -0
  545. package/dist/settings/apply.js.map +1 -0
  546. package/dist/settings/index.d.ts +126 -0
  547. package/dist/settings/index.d.ts.map +1 -0
  548. package/dist/settings/index.js +422 -0
  549. package/dist/settings/index.js.map +1 -0
  550. package/dist/settings/migrate.d.ts +21 -0
  551. package/dist/settings/migrate.d.ts.map +1 -0
  552. package/dist/settings/migrate.js +86 -0
  553. package/dist/settings/migrate.js.map +1 -0
  554. package/dist/types.d.ts +330 -141
  555. package/dist/types.d.ts.map +1 -1
  556. package/dist/types.js +2 -2
  557. package/dist/types.js.map +1 -1
  558. package/dist/vault/index.d.ts +52 -2
  559. package/dist/vault/index.d.ts.map +1 -1
  560. package/dist/vault/index.js +218 -86
  561. package/dist/vault/index.js.map +1 -1
  562. package/dist/vault/types.d.ts +6 -2
  563. package/dist/vault/types.d.ts.map +1 -1
  564. package/dist/vault/types.js.map +1 -1
  565. package/package.json +79 -40
  566. package/dist/adapter.d.ts +0 -2
  567. package/dist/adapter.d.ts.map +0 -1
  568. package/dist/adapter.js +0 -2
  569. package/dist/adapter.js.map +0 -1
  570. package/dist/adapters/slack/tools/attach.d.ts.map +0 -1
  571. package/dist/adapters/slack/tools/attach.js.map +0 -1
  572. package/dist/adapters/streaming.d.ts +0 -18
  573. package/dist/adapters/streaming.d.ts.map +0 -1
  574. package/dist/adapters/streaming.js +0 -44
  575. package/dist/adapters/streaming.js.map +0 -1
  576. package/dist/adapters/telegram/html.d.ts +0 -3
  577. package/dist/adapters/telegram/html.d.ts.map +0 -1
  578. package/dist/adapters/telegram/html.js +0 -98
  579. package/dist/adapters/telegram/html.js.map +0 -1
  580. package/dist/agent-events.d.ts +0 -5
  581. package/dist/agent-events.d.ts.map +0 -1
  582. package/dist/agent-events.js +0 -26
  583. package/dist/agent-events.js.map +0 -1
  584. package/dist/agent.d.ts +0 -39
  585. package/dist/agent.d.ts.map +0 -1
  586. package/dist/agent.js +0 -1291
  587. package/dist/agent.js.map +0 -1
  588. package/dist/cli/ext-git.d.ts +0 -22
  589. package/dist/cli/ext-git.d.ts.map +0 -1
  590. package/dist/cli/ext-git.js +0 -76
  591. package/dist/cli/ext-git.js.map +0 -1
  592. package/dist/cli/ext.d.ts +0 -2
  593. package/dist/cli/ext.d.ts.map +0 -1
  594. package/dist/cli/ext.js +0 -217
  595. package/dist/cli/ext.js.map +0 -1
  596. package/dist/commands/admin.d.ts.map +0 -1
  597. package/dist/commands/admin.js.map +0 -1
  598. package/dist/commands/auto-reply.d.ts.map +0 -1
  599. package/dist/commands/auto-reply.js +0 -76
  600. package/dist/commands/auto-reply.js.map +0 -1
  601. package/dist/commands/extensions.d.ts +0 -14
  602. package/dist/commands/extensions.d.ts.map +0 -1
  603. package/dist/commands/extensions.js +0 -55
  604. package/dist/commands/extensions.js.map +0 -1
  605. package/dist/commands/login.d.ts +0 -5
  606. package/dist/commands/login.d.ts.map +0 -1
  607. package/dist/commands/login.js +0 -115
  608. package/dist/commands/login.js.map +0 -1
  609. package/dist/commands/model.d.ts +0 -10
  610. package/dist/commands/model.d.ts.map +0 -1
  611. package/dist/commands/model.js +0 -102
  612. package/dist/commands/model.js.map +0 -1
  613. package/dist/commands/new.d.ts.map +0 -1
  614. package/dist/commands/new.js +0 -25
  615. package/dist/commands/new.js.map +0 -1
  616. package/dist/commands/parse.d.ts +0 -7
  617. package/dist/commands/parse.d.ts.map +0 -1
  618. package/dist/commands/parse.js +0 -14
  619. package/dist/commands/parse.js.map +0 -1
  620. package/dist/commands/registry.d.ts +0 -5
  621. package/dist/commands/registry.d.ts.map +0 -1
  622. package/dist/commands/registry.js +0 -31
  623. package/dist/commands/registry.js.map +0 -1
  624. package/dist/commands/sandbox.d.ts.map +0 -1
  625. package/dist/commands/sandbox.js +0 -78
  626. package/dist/commands/sandbox.js.map +0 -1
  627. package/dist/commands/session-view.d.ts.map +0 -1
  628. package/dist/commands/session-view.js.map +0 -1
  629. package/dist/commands/types.d.ts +0 -66
  630. package/dist/commands/types.d.ts.map +0 -1
  631. package/dist/commands/types.js.map +0 -1
  632. package/dist/commands/utils.d.ts +0 -11
  633. package/dist/commands/utils.d.ts.map +0 -1
  634. package/dist/commands/utils.js.map +0 -1
  635. package/dist/config.d.ts +0 -78
  636. package/dist/config.d.ts.map +0 -1
  637. package/dist/config.js +0 -450
  638. package/dist/config.js.map +0 -1
  639. package/dist/download.d.ts.map +0 -1
  640. package/dist/download.js +0 -89
  641. package/dist/download.js.map +0 -1
  642. package/dist/events.d.ts +0 -43
  643. package/dist/events.d.ts.map +0 -1
  644. package/dist/events.js +0 -456
  645. package/dist/events.js.map +0 -1
  646. package/dist/execution-resolver.d.ts +0 -21
  647. package/dist/execution-resolver.d.ts.map +0 -1
  648. package/dist/execution-resolver.js +0 -170
  649. package/dist/execution-resolver.js.map +0 -1
  650. package/dist/harness/auth.d.ts +0 -16
  651. package/dist/harness/auth.d.ts.map +0 -1
  652. package/dist/harness/auth.js +0 -86
  653. package/dist/harness/auth.js.map +0 -1
  654. package/dist/harness/extensions/loader.d.ts +0 -86
  655. package/dist/harness/extensions/loader.d.ts.map +0 -1
  656. package/dist/harness/extensions/loader.js +0 -525
  657. package/dist/harness/extensions/loader.js.map +0 -1
  658. package/dist/harness/extensions/registry.d.ts +0 -23
  659. package/dist/harness/extensions/registry.d.ts.map +0 -1
  660. package/dist/harness/extensions/registry.js +0 -45
  661. package/dist/harness/extensions/registry.js.map +0 -1
  662. package/dist/harness/extensions/types.d.ts +0 -248
  663. package/dist/harness/extensions/types.d.ts.map +0 -1
  664. package/dist/harness/extensions/types.js.map +0 -1
  665. package/dist/harness/session-store.d.ts +0 -77
  666. package/dist/harness/session-store.d.ts.map +0 -1
  667. package/dist/harness/session-store.js +0 -248
  668. package/dist/harness/session-store.js.map +0 -1
  669. package/dist/harness/settings.d.ts +0 -47
  670. package/dist/harness/settings.d.ts.map +0 -1
  671. package/dist/harness/settings.js +0 -29
  672. package/dist/harness/settings.js.map +0 -1
  673. package/dist/model-registry.d.ts +0 -4
  674. package/dist/model-registry.d.ts.map +0 -1
  675. package/dist/model-registry.js +0 -7
  676. package/dist/model-registry.js.map +0 -1
  677. package/dist/platform-messages.d.ts.map +0 -1
  678. package/dist/platform-messages.js.map +0 -1
  679. package/dist/portal-shell.d.ts +0 -4
  680. package/dist/portal-shell.d.ts.map +0 -1
  681. package/dist/portal-shell.js.map +0 -1
  682. package/dist/provisioner.d.ts +0 -61
  683. package/dist/provisioner.d.ts.map +0 -1
  684. package/dist/provisioner.js.map +0 -1
  685. package/dist/runtime/agent-run-controller.d.ts +0 -31
  686. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  687. package/dist/runtime/agent-run-controller.js +0 -195
  688. package/dist/runtime/agent-run-controller.js.map +0 -1
  689. package/dist/sandbox/errors.d.ts +0 -6
  690. package/dist/sandbox/errors.d.ts.map +0 -1
  691. package/dist/sandbox/errors.js +0 -11
  692. package/dist/sandbox/errors.js.map +0 -1
  693. package/dist/sandbox/firecracker.d.ts +0 -15
  694. package/dist/sandbox/firecracker.d.ts.map +0 -1
  695. package/dist/sandbox/firecracker.js +0 -212
  696. package/dist/sandbox/firecracker.js.map +0 -1
  697. package/dist/sandbox/image.d.ts +0 -3
  698. package/dist/sandbox/image.d.ts.map +0 -1
  699. package/dist/sandbox/image.js +0 -27
  700. package/dist/sandbox/image.js.map +0 -1
  701. package/dist/sandbox/path-context.d.ts +0 -3
  702. package/dist/sandbox/path-context.d.ts.map +0 -1
  703. package/dist/sandbox/path-context.js +0 -20
  704. package/dist/sandbox/path-context.js.map +0 -1
  705. package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
  706. package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
  707. package/dist/sessions/agent-memory-file-manager.js.map +0 -1
  708. package/dist/sessions/metadata.d.ts +0 -3
  709. package/dist/sessions/metadata.d.ts.map +0 -1
  710. package/dist/sessions/metadata.js +0 -11
  711. package/dist/sessions/metadata.js.map +0 -1
  712. package/dist/sessions/policy.d.ts +0 -6
  713. package/dist/sessions/policy.d.ts.map +0 -1
  714. package/dist/sessions/policy.js +0 -23
  715. package/dist/sessions/policy.js.map +0 -1
  716. package/dist/store.d.ts +0 -46
  717. package/dist/store.d.ts.map +0 -1
  718. package/dist/store.js +0 -177
  719. package/dist/store.js.map +0 -1
  720. package/dist/tools/bash.d.ts +0 -10
  721. package/dist/tools/bash.d.ts.map +0 -1
  722. package/dist/tools/bash.js +0 -80
  723. package/dist/tools/bash.js.map +0 -1
  724. package/dist/tools/edit.d.ts +0 -11
  725. package/dist/tools/edit.d.ts.map +0 -1
  726. package/dist/tools/edit.js +0 -131
  727. package/dist/tools/edit.js.map +0 -1
  728. package/dist/tools/event.d.ts.map +0 -1
  729. package/dist/tools/event.js +0 -252
  730. package/dist/tools/event.js.map +0 -1
  731. package/dist/tools/index.d.ts +0 -24
  732. package/dist/tools/index.d.ts.map +0 -1
  733. package/dist/tools/index.js +0 -31
  734. package/dist/tools/index.js.map +0 -1
  735. package/dist/tools/react.d.ts.map +0 -1
  736. package/dist/tools/react.js.map +0 -1
  737. package/dist/tools/read.d.ts +0 -11
  738. package/dist/tools/read.d.ts.map +0 -1
  739. package/dist/tools/read.js +0 -134
  740. package/dist/tools/read.js.map +0 -1
  741. package/dist/tools/sandbox.d.ts.map +0 -1
  742. package/dist/tools/sandbox.js.map +0 -1
  743. package/dist/tools/truncate.d.ts +0 -33
  744. package/dist/tools/truncate.d.ts.map +0 -1
  745. package/dist/tools/truncate.js +0 -184
  746. package/dist/tools/truncate.js.map +0 -1
  747. package/dist/tools/types.d.ts +0 -54
  748. package/dist/tools/types.d.ts.map +0 -1
  749. package/dist/tools/types.js.map +0 -1
  750. package/dist/tools/write.d.ts +0 -10
  751. package/dist/tools/write.d.ts.map +0 -1
  752. package/dist/tools/write.js +0 -29
  753. package/dist/tools/write.js.map +0 -1
  754. package/dist/trigger.d.ts +0 -22
  755. package/dist/trigger.d.ts.map +0 -1
  756. package/dist/trigger.js +0 -100
  757. package/dist/trigger.js.map +0 -1
  758. package/dist/utils/date.d.ts +0 -10
  759. package/dist/utils/date.d.ts.map +0 -1
  760. package/dist/utils/date.js +0 -23
  761. package/dist/utils/date.js.map +0 -1
  762. package/dist/utils/env.d.ts +0 -3
  763. package/dist/utils/env.d.ts.map +0 -1
  764. package/dist/utils/env.js +0 -12
  765. package/dist/utils/env.js.map +0 -1
  766. package/dist/utils/file-guards.d.ts +0 -9
  767. package/dist/utils/file-guards.d.ts.map +0 -1
  768. package/dist/utils/file-guards.js +0 -59
  769. package/dist/utils/file-guards.js.map +0 -1
  770. package/dist/utils/fs-atomic.d.ts +0 -10
  771. package/dist/utils/fs-atomic.d.ts.map +0 -1
  772. package/dist/utils/fs-atomic.js +0 -45
  773. package/dist/utils/fs-atomic.js.map +0 -1
  774. package/dist/utils/html.d.ts +0 -2
  775. package/dist/utils/html.d.ts.map +0 -1
  776. package/dist/utils/html.js +0 -4
  777. package/dist/utils/html.js.map +0 -1
  778. package/dist/utils/http-body.d.ts +0 -10
  779. package/dist/utils/http-body.d.ts.map +0 -1
  780. package/dist/utils/http-body.js +0 -34
  781. package/dist/utils/http-body.js.map +0 -1
  782. package/dist/vault/routing.d.ts +0 -3
  783. package/dist/vault/routing.d.ts.map +0 -1
  784. package/dist/vault/routing.js +0 -13
  785. package/dist/vault/routing.js.map +0 -1
  786. package/dist/web/admin/portal.d.ts +0 -5
  787. package/dist/web/admin/portal.d.ts.map +0 -1
  788. package/dist/web/admin/portal.js +0 -2572
  789. package/dist/web/admin/portal.js.map +0 -1
  790. package/dist/web/admin/provider-models.d.ts.map +0 -1
  791. package/dist/web/admin/provider-models.js.map +0 -1
  792. package/dist/web/admin/store.d.ts +0 -13
  793. package/dist/web/admin/store.d.ts.map +0 -1
  794. package/dist/web/admin/store.js +0 -14
  795. package/dist/web/admin/store.js.map +0 -1
  796. package/dist/web/admin/types.d.ts +0 -30
  797. package/dist/web/admin/types.d.ts.map +0 -1
  798. package/dist/web/admin/types.js.map +0 -1
  799. package/dist/web/agent-events/portal.d.ts +0 -3
  800. package/dist/web/agent-events/portal.d.ts.map +0 -1
  801. package/dist/web/agent-events/portal.js +0 -18
  802. package/dist/web/agent-events/portal.js.map +0 -1
  803. package/dist/web/login/oauth.d.ts +0 -6
  804. package/dist/web/login/oauth.d.ts.map +0 -1
  805. package/dist/web/login/oauth.js.map +0 -1
  806. package/dist/web/login/portal.d.ts.map +0 -1
  807. package/dist/web/login/portal.js.map +0 -1
  808. package/dist/web/login/store.d.ts +0 -8
  809. package/dist/web/login/store.d.ts.map +0 -1
  810. package/dist/web/login/store.js +0 -15
  811. package/dist/web/login/store.js.map +0 -1
  812. package/dist/web/login/types.d.ts.map +0 -1
  813. package/dist/web/login/types.js.map +0 -1
  814. package/dist/web/request.d.ts +0 -3
  815. package/dist/web/request.d.ts.map +0 -1
  816. package/dist/web/request.js +0 -13
  817. package/dist/web/request.js.map +0 -1
  818. package/dist/web/server.d.ts.map +0 -1
  819. package/dist/web/server.js.map +0 -1
  820. package/dist/web/session-view/command.d.ts +0 -4
  821. package/dist/web/session-view/command.d.ts.map +0 -1
  822. package/dist/web/session-view/command.js +0 -7
  823. package/dist/web/session-view/command.js.map +0 -1
  824. package/dist/web/session-view/portal.d.ts +0 -13
  825. package/dist/web/session-view/portal.d.ts.map +0 -1
  826. package/dist/web/session-view/portal.js.map +0 -1
  827. package/dist/web/session-view/service.d.ts +0 -6
  828. package/dist/web/session-view/service.d.ts.map +0 -1
  829. package/dist/web/session-view/service.js +0 -498
  830. package/dist/web/session-view/service.js.map +0 -1
  831. package/dist/web/session-view/store.d.ts +0 -8
  832. package/dist/web/session-view/store.d.ts.map +0 -1
  833. package/dist/web/session-view/store.js +0 -15
  834. package/dist/web/session-view/store.js.map +0 -1
  835. package/dist/web/session-view/types.d.ts.map +0 -1
  836. package/dist/web/session-view/types.js.map +0 -1
  837. package/dist/web/token-store.d.ts.map +0 -1
  838. package/dist/web/token-store.js.map +0 -1
  839. package/dist/web/types.d.ts.map +0 -1
  840. package/dist/web/types.js.map +0 -1
  841. /package/dist/{commands → adapters/commands}/admin.d.ts +0 -0
  842. /package/dist/{commands → adapters/commands}/new.d.ts +0 -0
  843. /package/dist/{commands → adapters/commands}/sandbox.d.ts +0 -0
  844. /package/dist/{commands → adapters/commands}/session-view.d.ts +0 -0
  845. /package/dist/{commands → adapters/commands}/types.js +0 -0
  846. /package/dist/{harness/extensions → adapters/github}/types.js +0 -0
  847. /package/dist/{tools → adapters/web/admin}/types.js +0 -0
  848. /package/dist/{web/admin → adapters/web/login}/types.js +0 -0
  849. /package/dist/{web/login → adapters/web/session-view}/types.js +0 -0
  850. /package/dist/{web → adapters/web}/token-store.d.ts +0 -0
  851. /package/dist/{web → adapters/web}/types.d.ts +0 -0
  852. /package/dist/{web/session-view → adapters/web}/types.js +0 -0
  853. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
  854. /package/dist/{web → cli}/types.js +0 -0
  855. /package/dist/{adapters/slack → harness}/tools/attach.d.ts +0 -0
  856. /package/dist/{tools → harness/tools}/react.d.ts +0 -0
@@ -1,22 +1,64 @@
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 { readFile, writeFile } from "node:fs/promises";
5
+ import { basename, join } from "node:path";
6
+ import { createConversationEvent, createConversationMessage, } from "../index.js";
7
+ import { createOfficeAddress, listRegisteredOffices } from "../../office/index.js";
8
+ import { COMMAND_MANIFEST } from "../commands/manifest.js";
9
+ import { slackConversationAutoReplyMode, resolveConversationSettings, } from "../../settings/index.js";
10
+ import { evaluateWithJev, JevNotConfiguredError } from "../../harness/index.js";
7
11
  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";
12
+ import { isRecord, readTextFileIfExists } from "../../file-guards.js";
13
+ import { PRODUCT_NAME, formatForceStopped } from "../messages.js";
14
+ import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, MessagingIntakeTracker, saveIncomingAttachments, withRetry, } from "../shared.js";
15
+ import { matchMagicWord, processMessageIntake } from "../intake.js";
16
+ import { readPlatformChannelKind, recordPlatformChannelKind, } from "../../office/projection.js";
17
+ import { AssistantThreadRegistry, handleAgentContextChanged, handleAgentDmOpened, handleAssistantThreadStarted, titleAssistantThread, } from "./assistant.js";
11
18
  import { createSlackAdapters } from "./context.js";
12
- import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/agent-memory-file-manager.js";
19
+ import { hasMaterializedChatSession, registerThreadSession, } from "../../sessions/chat-history-sync.js";
20
+ import { conversationIdOf } from "../../sessions/session-key.js";
13
21
  import { isSlackThreadSessionKey, planSlackAdapterSession, planSlackEventAnchorRun, resolveSlackSessionKey, } from "./session.js";
14
- import { reportUserFacingError } from "../../observability/sentry.js";
15
- import { renderSlackBlocks } from "./blocks.js";
22
+ import { reportUserFacingError } from "../../observability/index.js";
23
+ import { recordSlackUpdate } from "./update-diagnostics.js";
24
+ import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
25
+ import { querySlackTasks, formatTaskStatus, readTaskRoots, isTaskStatusQuestion, } from "./task-status.js";
26
+ import { buildAutoReplyState, JEV_ADDRESSED_INSTRUCTIONS } from "./auto-reply-context.js";
27
+ import { buildTaskIntentState, classifyTaskIntent } from "./task-intent.js";
28
+ import { StreamStartLimiter } from "./stream-limits.js";
16
29
  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.
30
+ function hasMessageContent(message, includeBlocks = true) {
31
+ const content = [message.text, message.files];
32
+ if (includeBlocks)
33
+ content.push(message.blocks, message.attachments);
34
+ return content.some((part) => !!part?.length);
35
+ }
36
+ function hasBotIdentity(message) {
37
+ return !!message.bot_id || message.subtype === "bot_message";
38
+ }
39
+ const USER_MESSAGE_SUBTYPES = new Set([undefined, "file_share"]);
40
+ const BOT_MESSAGE_SUBTYPES = new Set([...USER_MESSAGE_SUBTYPES, "bot_message"]);
41
+ /**
42
+ * Slack's per-call limit on `markdown_text`: "Limit this field to 12,000
43
+ * characters." mikan allows far more in a message, so streamed text is split
44
+ * to fit rather than truncated.
45
+ */
46
+ const MAX_STREAM_TEXT_CHARS = 12_000;
47
+ /** Slices `text` into pieces that each fit one streaming call. */
48
+ export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
49
+ if (text.length <= limit)
50
+ return [text];
51
+ const chunks = [];
52
+ for (let index = 0; index < text.length; index += limit) {
53
+ chunks.push(text.slice(index, index + limit));
54
+ }
55
+ return chunks;
56
+ }
57
+ // web-api v8 throws WebAPIRateLimitedError; the duck-typed shapes cover
58
+ // platform `data.error === "rate_limited"` / 429 responses and older callers.
19
59
  function slackIsRateLimited(err) {
60
+ if (err instanceof WebAPIRateLimitedError)
61
+ return true;
20
62
  if (err.code === "rate_limited")
21
63
  return true;
22
64
  const data = err.data;
@@ -72,14 +114,110 @@ export function buildMrkdwnContextBlock(text) {
72
114
  // ============================================================================
73
115
  // SlackMessagingBot
74
116
  // ============================================================================
117
+ class AttachmentDownloadHttpError extends Error {
118
+ constructor(message, status) {
119
+ super(message);
120
+ this.status = status;
121
+ }
122
+ }
123
+ function isRetryableAttachmentDownloadError(error) {
124
+ if (!(error instanceof AttachmentDownloadHttpError))
125
+ return true;
126
+ return error.status === 408 || error.status === 429 || error.status >= 500;
127
+ }
75
128
  export class SlackMessagingBot {
129
+ /** Host office dir for a Slack conversation. */
130
+ conversationDir(channelId) {
131
+ return this.workspace.office(createOfficeAddress("slack", channelId)).dir;
132
+ }
133
+ resolveReplyMode(address) {
134
+ return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
135
+ }
76
136
  createContext(event) {
77
- return createSlackAdapters(event, this, {
78
- replyMode: resolveConversationSettings(join(this.workingDir, event.conversationId)).slack?.replyMode ??
79
- "top-level",
137
+ const context = createSlackAdapters(event, this, {
138
+ replyMode: this.resolveReplyMode(event.address),
80
139
  });
140
+ if (event.conversationKind === "direct") {
141
+ context.responder.getTaskStatus = (key) => querySlackTasks(this.conversationDir(event.channel), event.channel, this.handler
142
+ .getRunningSessions()
143
+ .filter((s) => s.address.platform === "slack" && s.address.conversationId === event.channel), key);
144
+ }
145
+ if (event.conversationKind === "direct" &&
146
+ event.thread_ts &&
147
+ this.isTaskThread(event.channel, event.thread_ts)) {
148
+ let notified = false;
149
+ context.responder.notifyCompletion = async () => {
150
+ if (notified)
151
+ return;
152
+ const text = `<@${event.user}> 這一輪處理已結束,請查看上方結果。`;
153
+ const ts = await this.postInThread(event.channel, event.thread_ts, text);
154
+ notified = true;
155
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
156
+ };
157
+ }
158
+ if (event.conversationKind === "direct" && !event.thread_ts) {
159
+ context.responder.startTask = async (message, task) => {
160
+ if (this.stopped)
161
+ throw new Error("Slack is shutting down; task was not started.");
162
+ await context.responder.deleteResponse();
163
+ const root = await this.postMessage(event.channel, message);
164
+ this.logToFile(event.channel, {
165
+ date: new Date().toISOString(),
166
+ ts: root,
167
+ user: "bot",
168
+ text: message,
169
+ isMessagingBot: true,
170
+ taskRoot: true,
171
+ });
172
+ const sessionKey = resolveSlackSessionKey(event.channel, root);
173
+ try {
174
+ registerThreadSession({
175
+ conversationDir: this.conversationDir(event.channel),
176
+ sessionKey,
177
+ });
178
+ const child = {
179
+ ...event,
180
+ ts: `task:${root}`,
181
+ thread_ts: root,
182
+ sessionKey,
183
+ text: task,
184
+ };
185
+ if (!this.getQueue(sessionKey).enqueue(async () => {
186
+ try {
187
+ await this.handler.handleEvent({
188
+ ...child,
189
+ attachments: child.attachments?.map((a) => ({
190
+ name: a.original,
191
+ localPath: a.localPath,
192
+ })),
193
+ }, this, this.createContext(child));
194
+ }
195
+ catch (error) {
196
+ reportUserFacingError(error, {
197
+ domain: "mikan",
198
+ surface: "task_handoff",
199
+ operation: "start_task_run",
200
+ severity: "error",
201
+ platform: "slack",
202
+ context: { conversationId: event.channel, sessionKey, threadTs: root },
203
+ });
204
+ await this.postInThread(event.channel, root, "Task could not start. Please reply here to try again.");
205
+ throw error;
206
+ }
207
+ }))
208
+ throw new Error("Task queue is closed.");
209
+ }
210
+ catch (error) {
211
+ await this.updateMessage(event.channel, root, `${message}\n\nTask could not start.`);
212
+ throw error;
213
+ }
214
+ return sessionKey;
215
+ };
216
+ }
217
+ return context;
81
218
  }
82
219
  constructor(handler, config) {
220
+ this.statusUpdates = new Map();
83
221
  this.botUserId = null;
84
222
  this.botId = null;
85
223
  this.teamId = null;
@@ -87,11 +225,22 @@ export class SlackMessagingBot {
87
225
  this.startupTs = null; // Messages older than this are just logged, not processed
88
226
  this.users = new Map();
89
227
  this.channels = new Map();
228
+ /** Which threads belong to the assistant pane, and their last known context. */
229
+ this.assistantThreads = new AssistantThreadRegistry();
230
+ /**
231
+ * Native response streams opened recently. Per bot rather than per module:
232
+ * the rate tier applies per workspace, and this bot is the connection to
233
+ * one — which also means a test constructing a bot gets a fresh budget
234
+ * instead of inheriting whatever ran before it.
235
+ */
236
+ this.streamStarts = new StreamStartLimiter();
237
+ this.stopped = false;
90
238
  this.queues = new Map();
91
- this.eventsWatcher = null;
239
+ this.intake = new MessagingIntakeTracker("Slack");
240
+ this.eventScheduler = null;
92
241
  this.handler = handler;
93
- this.workingDir = config.workingDir;
94
- this.store = config.store;
242
+ this.workspace = config.workspace;
243
+ this.botToken = config.botToken;
95
244
  this.socketClient = new SocketModeClient({
96
245
  appToken: config.appToken,
97
246
  // Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
@@ -99,20 +248,29 @@ export class SlackMessagingBot {
99
248
  clientPingTimeout: 12_000,
100
249
  });
101
250
  this.webClient = new WebClient(config.botToken);
251
+ this.statusClient = new WebClient(config.botToken, {
252
+ timeout: 3000,
253
+ retryConfig: { retries: 0 },
254
+ rejectRateLimitedCalls: true,
255
+ });
102
256
  }
103
- setEventsWatcher(watcher) {
104
- this.eventsWatcher = watcher;
257
+ setEventScheduler(scheduler) {
258
+ this.eventScheduler = scheduler;
105
259
  }
106
260
  // ==========================================================================
107
261
  // Public API
108
262
  // ==========================================================================
109
263
  async start() {
264
+ this.stopped = false;
110
265
  const auth = await this.webClient.auth.test();
111
266
  this.botUserId = auth.user_id;
112
267
  this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
113
268
  this.teamId = typeof auth.team_id === "string" ? auth.team_id : null;
114
269
  await Promise.all([this.fetchUsers(), this.fetchChannels()]);
270
+ if (this.stopped)
271
+ return;
115
272
  log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
273
+ this.backfillChannelKinds();
116
274
  // Record startup time before opening the socket. Slack may replay older events;
117
275
  // those should be logged but not processed. Backfill runs in the background up
118
276
  // to this timestamp so startup is not blocked by one history call per channel.
@@ -124,6 +282,16 @@ export class SlackMessagingBot {
124
282
  log.logWarning("Slack backfill failed", String(error));
125
283
  });
126
284
  }
285
+ async stop() {
286
+ this.stopped = true;
287
+ try {
288
+ await this.socketClient.disconnect();
289
+ }
290
+ finally {
291
+ await this.intake.close();
292
+ await Promise.all([...this.queues.values()].map((queue) => queue.close()));
293
+ }
294
+ }
127
295
  getUser(userId) {
128
296
  return this.users.get(userId);
129
297
  }
@@ -145,9 +313,22 @@ export class SlackMessagingBot {
145
313
  }
146
314
  return source.replace(this.ownMentionRegex, "").trim();
147
315
  }
148
- async postMessage(channel, text) {
316
+ /**
317
+ * Response-source `<@userName>` mentions become native `<@U…>` here — the
318
+ * one conversion every outgoing response path funnels through. Slack only
319
+ * links and notifies on raw user ids.
320
+ */
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
+ ...(threadTs !== undefined ? { thread_ts: threadTs } : {}),
329
+ ...(identity ? { username: identity.username, icon_emoji: identity.iconEmoji } : {}),
330
+ ...renderSlackBlocks(this.resolveMentions(text)),
331
+ };
151
332
  const result = await this.webClient.chat.postMessage(payload);
152
333
  return result.ts;
153
334
  });
@@ -167,32 +348,35 @@ export class SlackMessagingBot {
167
348
  }
168
349
  });
169
350
  }
170
- async postEphemeral(channel, user, text, threadTs) {
351
+ async postEphemeral(channel, user, text, threadTs, blocks) {
171
352
  return slackRetry(async () => {
172
353
  await this.webClient.chat.postEphemeral({
173
354
  channel,
174
355
  user,
175
356
  text,
357
+ ...(blocks !== undefined ? { blocks: blocks } : {}),
176
358
  ...(threadTs ? { thread_ts: threadTs } : {}),
177
359
  });
178
360
  });
179
361
  }
180
362
  async postEphemeralBlocks(channel, user, text, blocks, threadTs) {
363
+ return this.postEphemeral(channel, user, text, threadTs, blocks);
364
+ }
365
+ async postMessageBlocks(channel, text, blocks, threadTs) {
181
366
  return slackRetry(async () => {
182
- await this.webClient.chat.postEphemeral({
367
+ const payload = {
183
368
  channel,
184
- user,
185
369
  text,
186
370
  blocks: blocks,
187
- ...(threadTs ? { thread_ts: threadTs } : {}),
188
- });
371
+ ...(threadTs !== undefined ? { thread_ts: threadTs } : {}),
372
+ };
373
+ const result = await this.webClient.chat.postMessage(payload);
374
+ return result.ts;
189
375
  });
190
376
  }
191
- async postMessageBlocks(channel, text, blocks) {
377
+ async updateMessageBlocks(channel, ts, text, blocks) {
192
378
  return slackRetry(async () => {
193
- const payload = { channel, text, blocks: blocks };
194
- const result = await this.webClient.chat.postMessage(payload);
195
- return result.ts;
379
+ await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
196
380
  });
197
381
  }
198
382
  async postPrivate(conversationId, userId, text) {
@@ -205,7 +389,7 @@ export class SlackMessagingBot {
205
389
  }
206
390
  await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
207
391
  }
208
- async openDirectMessage(userId) {
392
+ async openDirectConversation(userId) {
209
393
  return slackRetry(async () => {
210
394
  const result = await this.webClient.conversations.open({ users: userId });
211
395
  const channelId = result.channel?.id;
@@ -215,33 +399,134 @@ export class SlackMessagingBot {
215
399
  return channelId;
216
400
  });
217
401
  }
402
+ /**
403
+ * Fetch recent messages from a channel, oldest first. Without `threadTs`
404
+ * this is one `conversations.history` page of top-level messages (thread
405
+ * replies are not expanded); with it, one `conversations.replies` page of
406
+ * that thread's replies, excluding the parent. Callers page forward by
407
+ * passing the last returned `ts` as `oldest`.
408
+ */
409
+ async fetchHistory(channel, options) {
410
+ const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
411
+ const threadTs = options?.threadTs;
412
+ return slackRetry(async () => {
413
+ const result = threadTs
414
+ ? await this.webClient.conversations.replies({
415
+ channel,
416
+ ts: threadTs,
417
+ ...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
418
+ limit,
419
+ })
420
+ : await this.webClient.conversations.history({
421
+ channel,
422
+ ...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
423
+ limit,
424
+ });
425
+ const messages = (result.messages ?? []);
426
+ const mapped = messages
427
+ .filter((msg) => !!msg.ts)
428
+ // conversations.replies leads with the thread parent; the caller asked
429
+ // for the replies to it, and including it would re-ingest the bot's
430
+ // own message on every poll.
431
+ .filter((msg) => msg.ts !== threadTs)
432
+ .map((msg) => {
433
+ const user = msg.user ? this.users.get(msg.user) : undefined;
434
+ const message = {
435
+ ts: msg.ts,
436
+ text: msg.text ?? "",
437
+ isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
438
+ };
439
+ if (msg.thread_ts && msg.thread_ts !== msg.ts)
440
+ message.threadTs = msg.thread_ts;
441
+ if (msg.user)
442
+ message.userId = msg.user;
443
+ if (user)
444
+ message.userName = user.userName;
445
+ return message;
446
+ });
447
+ // history returns newest-first, replies oldest-first; the contract is
448
+ // oldest-first either way.
449
+ return threadTs ? mapped : mapped.toReversed();
450
+ });
451
+ }
452
+ /** Refresh and list the workspace's active users (`users.list`). */
453
+ async listUsers() {
454
+ await slackRetry(() => this.fetchUsers());
455
+ return this.getAllUsers().map((user) => ({
456
+ id: user.id,
457
+ userName: user.userName,
458
+ displayName: user.displayName,
459
+ isBot: user.isBot === true,
460
+ }));
461
+ }
218
462
  async updateMessage(channel, ts, text) {
219
463
  return slackRetry(async () => {
220
- const payload = { channel, ts, ...renderSlackBlocks(text) };
221
- await this.webClient.chat.update(payload);
464
+ const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
465
+ try {
466
+ await this.webClient.chat.update(payload);
467
+ }
468
+ catch (error) {
469
+ recordSlackUpdate(this, { channel, ts }, text, payload, { error });
470
+ throw error;
471
+ }
472
+ recordSlackUpdate(this, { channel, ts }, text, payload, { success: true });
222
473
  });
223
474
  }
475
+ /**
476
+ * Claim one native stream, or decline. Declining is not an error: the caller
477
+ * falls back to edit-based updates, which is a working answer rather than a
478
+ * degraded one, and costs no request against the tighter start tier.
479
+ */
480
+ tryReserveStreamStart() {
481
+ return this.streamStarts.tryReserve();
482
+ }
483
+ /**
484
+ * Open a stream, carrying text of any length.
485
+ *
486
+ * Slack caps `markdown_text` at 12,000 characters per call, and a long first
487
+ * render exceeds that easily — mikan allows 35,000 in a message. Rather than
488
+ * truncating (losing the answer) or failing (falling back to edit mode for
489
+ * the whole response), the opening call takes the first slice and the rest
490
+ * follows as appends before this resolves. The caller sees one operation
491
+ * that sent everything, so the renderer's idea of what has been streamed
492
+ * stays true.
493
+ *
494
+ * Splitting mid-word or mid-fence is harmless here: a stream concatenates,
495
+ * so Slack renders the reassembled text, not the pieces.
496
+ */
224
497
  async startMessageStream(channel, text, threadTs, recipientUserId) {
225
- return slackRetry(async () => {
498
+ const [head, ...tail] = chunkStreamText(text);
499
+ const ts = await slackRetry(async () => {
226
500
  const result = await this.webClient.apiCall("chat.startStream", {
227
501
  channel,
228
- markdown_text: text,
502
+ markdown_text: this.resolveMentions(head ?? ""),
229
503
  ...(threadTs ? { thread_ts: threadTs } : {}),
230
504
  ...(this.teamId ? { recipient_team_id: this.teamId } : {}),
231
505
  ...(recipientUserId ? { recipient_user_id: recipientUserId } : {}),
232
506
  });
233
- const ts = result.ts;
234
- if (!ts)
507
+ const streamTs = result.ts;
508
+ if (!streamTs)
235
509
  throw new Error("Slack chat.startStream did not return ts");
236
- return ts;
510
+ return streamTs;
237
511
  });
512
+ for (const part of tail)
513
+ await this.appendStreamChunk(channel, ts, part);
514
+ return ts;
238
515
  }
516
+ /** Append a delta of any length, split to the per-call cap. */
239
517
  async appendMessageStream(channel, ts, text) {
518
+ for (const part of chunkStreamText(text)) {
519
+ await this.appendStreamChunk(channel, ts, part);
520
+ }
521
+ }
522
+ async appendStreamChunk(channel, ts, text) {
240
523
  return slackRetry(async () => {
241
524
  await this.webClient.apiCall("chat.appendStream", {
242
525
  channel,
243
526
  ts,
244
- markdown_text: text,
527
+ // A mention split across stream deltas stays unresolved here; the
528
+ // canonical final render (updateMessage) resolves the full text.
529
+ markdown_text: this.resolveMentions(text),
245
530
  });
246
531
  });
247
532
  }
@@ -260,33 +545,97 @@ export class SlackMessagingBot {
260
545
  // ==========================================================================
261
546
  /** Set the status for an assistant thread (shows "thinking" state) */
262
547
  async setAssistantStatus(channel, threadTs, status) {
263
- return slackRetry(async () => {
264
- await this.webClient.assistant.threads.setStatus({
548
+ const key = `${channel}:${threadTs}`;
549
+ const previous = this.statusUpdates.get(key) ?? Promise.resolve();
550
+ const update = previous
551
+ .catch(() => undefined)
552
+ .then(async () => {
553
+ await this.statusClient.assistant.threads.setStatus({
265
554
  channel_id: channel,
266
555
  thread_ts: threadTs,
267
556
  status,
268
557
  });
269
558
  });
559
+ this.statusUpdates.set(key, update);
560
+ try {
561
+ await update;
562
+ }
563
+ finally {
564
+ if (this.statusUpdates.get(key) === update)
565
+ this.statusUpdates.delete(key);
566
+ }
270
567
  }
271
- async postInThread(channel, threadTs, text) {
568
+ /** Offer starting points in a new assistant thread. */
569
+ async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
272
570
  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;
571
+ // Omitting thread_ts pins the prompts to the DM itself, which is what
572
+ // the agent surface wants: there is no thread yet when it opens.
573
+ await this.webClient.assistant.threads.setSuggestedPrompts({
574
+ channel_id: channel,
575
+ ...(threadTs ? { thread_ts: threadTs } : {}),
576
+ prompts,
577
+ });
276
578
  });
277
579
  }
278
- async postInThreadBlocks(channel, threadTs, text, blocks) {
580
+ /** Name an assistant thread, which is what the sidebar conversation list shows. */
581
+ async setAssistantTitle(channel, threadTs, title) {
279
582
  return slackRetry(async () => {
280
- const payload = {
281
- channel,
583
+ await this.webClient.assistant.threads.setTitle({
584
+ channel_id: channel,
282
585
  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;
586
+ title,
587
+ });
288
588
  });
289
589
  }
590
+ /** The assistant surface's view of this bot, handed to the lifecycle handlers. */
591
+ assistantOps() {
592
+ return {
593
+ postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
594
+ setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
595
+ setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
596
+ channelName: (channelId) => this.channels.get(channelId)?.name,
597
+ };
598
+ }
599
+ /**
600
+ * A new conversation opened in the assistant pane. Slack waits for the app
601
+ * to speak first here; staying silent is what makes the pane look broken.
602
+ */
603
+ handleAssistantThreadStarted({ event, ack }) {
604
+ ack();
605
+ const payload = event;
606
+ if (!payload.assistant_thread)
607
+ return;
608
+ void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
609
+ }
610
+ /**
611
+ * The person navigated to another channel while the pane is open. Slack
612
+ * sends this as `assistant_thread_context_changed` under `assistant_view`
613
+ * and `app_context_changed` under `agent_view`; the payload differs enough
614
+ * to normalize here rather than in the handler.
615
+ */
616
+ handleAgentContextChangedEvent({ event, ack, }) {
617
+ ack();
618
+ const payload = event;
619
+ if (payload.assistant_thread) {
620
+ handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
621
+ return;
622
+ }
623
+ const channelId = payload.channel_id ?? payload.channel;
624
+ if (channelId) {
625
+ handleAgentContextChanged(this.assistantThreads, {
626
+ channel_id: channelId,
627
+ context: payload.context,
628
+ });
629
+ }
630
+ }
631
+ async postInThread(channel, threadTs, text, identity) {
632
+ return identity
633
+ ? this.postMessage(channel, text, threadTs, identity)
634
+ : this.postMessage(channel, text, threadTs);
635
+ }
636
+ async postInThreadBlocks(channel, threadTs, text, blocks) {
637
+ return this.postMessageBlocks(channel, text, blocks, threadTs);
638
+ }
290
639
  async uploadFile(channel, filePath, title, threadTs) {
291
640
  return slackRetry(async () => {
292
641
  const fileName = title || basename(filePath);
@@ -301,18 +650,40 @@ export class SlackMessagingBot {
301
650
  });
302
651
  }
303
652
  logToFile(channel, entry) {
304
- appendChannelLog(this.workingDir, channel, entry);
653
+ appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
305
654
  }
306
655
  logBotResponse(channel, text, ts, threadTs, slackBlocks) {
307
- appendBotResponseLog(this.workingDir, channel, text, ts, threadTs, {
656
+ appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
308
657
  platform: "slack",
309
658
  ...(slackBlocks ? { slackBlocks } : {}),
310
659
  });
311
660
  }
661
+ ownsBlockKitMessage(channel, ts, threadTs) {
662
+ const content = readTextFileIfExists(join(this.conversationDir(channel), "log.jsonl"));
663
+ if (content === undefined)
664
+ return false;
665
+ for (const line of content.trim().split("\n").toReversed()) {
666
+ try {
667
+ const entry = JSON.parse(line);
668
+ if (!isRecord(entry) || entry.ts !== ts)
669
+ continue;
670
+ return (entry.isMessagingBot === true &&
671
+ entry.platform === "slack" &&
672
+ Array.isArray(entry.slackBlocks) &&
673
+ entry.threadTs === threadTs);
674
+ }
675
+ catch {
676
+ continue;
677
+ }
678
+ }
679
+ return false;
680
+ }
312
681
  getMessagingInfo() {
313
682
  return {
314
683
  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).",
684
+ ...(this.teamId ? { workspaceId: this.teamId } : {}),
685
+ trustModel: "membership",
686
+ 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
687
  channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
317
688
  users: this.getAllUsers().map((u) => ({
318
689
  id: u.id,
@@ -332,6 +703,8 @@ export class SlackMessagingBot {
332
703
  * Returns true if enqueued, false if queue is full (max 5).
333
704
  */
334
705
  enqueueEvent(event) {
706
+ if (this.stopped)
707
+ return false;
335
708
  const conversationId = event.conversationId;
336
709
  const queue = this.getQueue(conversationId);
337
710
  if (queue.size() >= 5) {
@@ -339,7 +712,7 @@ export class SlackMessagingBot {
339
712
  return false;
340
713
  }
341
714
  log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);
342
- queue.enqueue(async () => {
715
+ return queue.enqueue(async () => {
343
716
  let anchorTs;
344
717
  if (!event.thread_ts) {
345
718
  try {
@@ -367,15 +740,17 @@ export class SlackMessagingBot {
367
740
  const eventForRun = eventPlan.event;
368
741
  if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
369
742
  registerThreadSession({
370
- conversationDir: join(this.workingDir, conversationId),
743
+ conversationDir: this.conversationDir(conversationId),
371
744
  sessionKey: eventForRun.sessionKey,
372
745
  });
373
746
  }
374
747
  const runQueueKey = planSlackAdapterSession(eventForRun, {
375
748
  initialMessageTs: eventPlan.initialMessageTs,
376
749
  }).sessionKey;
377
- this.getQueue(runQueueKey).enqueue(async () => {
378
- const slackEvent = {
750
+ const run = async () => {
751
+ const slackEvent = createConversationEvent({
752
+ platform: "slack",
753
+ address: eventForRun.address,
379
754
  type: eventForRun.type,
380
755
  conversationId,
381
756
  conversationKind: eventForRun.conversationKind,
@@ -384,21 +759,21 @@ export class SlackMessagingBot {
384
759
  thread_ts: eventForRun.thread_ts,
385
760
  user: eventForRun.user,
386
761
  text: eventForRun.text,
387
- attachments: eventForRun.attachments?.map((attachment) => ({
388
- original: attachment.name,
389
- localPath: attachment.localPath,
390
- })),
762
+ attachments: eventForRun.attachments,
391
763
  sessionKey: eventForRun.sessionKey,
392
- };
764
+ });
393
765
  const context = createSlackAdapters(slackEvent, this, {
394
766
  initialMessageTs: eventPlan.initialMessageTs,
395
- replyMode: resolveConversationSettings(join(this.workingDir, eventForRun.conversationId)).slack
396
- ?.replyMode ?? "top-level",
767
+ replyMode: this.resolveReplyMode(eventForRun.address),
397
768
  });
398
769
  return this.handler.handleEvent(eventForRun, this, context);
399
- });
770
+ };
771
+ const runQueue = this.getQueue(runQueueKey);
772
+ if (!runQueue.enqueue(run)) {
773
+ await runQueue.close();
774
+ await run();
775
+ }
400
776
  });
401
- return true;
402
777
  }
403
778
  // ==========================================================================
404
779
  // Private - Event Handlers
@@ -407,6 +782,8 @@ export class SlackMessagingBot {
407
782
  let queue = this.queues.get(channelId);
408
783
  if (!queue) {
409
784
  queue = new MessagingEventQueue("Slack");
785
+ if (this.stopped)
786
+ void queue.close();
410
787
  this.queues.set(channelId, queue);
411
788
  }
412
789
  return queue;
@@ -414,21 +791,108 @@ export class SlackMessagingBot {
414
791
  resolveQueueKey(conversationId, sessionKey) {
415
792
  if (!isSlackThreadSessionKey(sessionKey))
416
793
  return sessionKey;
417
- if (this.handler.isRunning(sessionKey))
794
+ if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
418
795
  return sessionKey;
796
+ }
419
797
  return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
420
798
  }
799
+ isTaskThread(channel, root) {
800
+ return readTaskRoots(this.conversationDir(channel)).has(root);
801
+ }
802
+ hasRunningTaskThread(channel) {
803
+ const roots = readTaskRoots(this.conversationDir(channel));
804
+ if (!roots.size)
805
+ return false;
806
+ return this.handler
807
+ .getRunningSessions()
808
+ .some((s) => s.address.platform === "slack" &&
809
+ s.address.conversationId === channel &&
810
+ [...roots.keys()].some((root) => resolveSlackSessionKey(channel, root) === s.sessionKey));
811
+ }
421
812
  hasKnownThreadSession(conversationId, sessionKey) {
422
813
  return hasMaterializedChatSession({
423
- conversationDir: join(this.workingDir, conversationId),
814
+ conversationDir: this.conversationDir(conversationId),
424
815
  sessionKey,
425
816
  });
426
817
  }
818
+ /**
819
+ * Slack's own conversation vocabulary for this channel, from the metadata
820
+ * fetchChannels() already loads. Externally shared channels report
821
+ * "external" regardless of privacy — platform-public within one workspace
822
+ * is the sharing condition, and a channel visible to another organization
823
+ * does not satisfy it. Unknown channels return undefined (never recorded,
824
+ * so the projection keeps its isolated default).
825
+ */
826
+ channelKindFor(channelId) {
827
+ if (channelId.startsWith("D"))
828
+ return "im";
829
+ const channel = this.channels.get(channelId);
830
+ if (!channel)
831
+ return undefined;
832
+ if (channel.name.startsWith("DM:"))
833
+ return "im";
834
+ if (channel.isExternallyShared)
835
+ return "external";
836
+ if (channel.isPrivate === true)
837
+ return "private_channel";
838
+ if (channel.isPrivate === false)
839
+ return "public_channel";
840
+ return undefined;
841
+ }
842
+ /**
843
+ * Office visibility follows the recorded channel kind, and an unrecorded
844
+ * kind fails closed to private (ADR 0008). Offices created before kinds
845
+ * were recorded would therefore lose public status until their next
846
+ * message; the channel list loaded at startup already knows every kind, so
847
+ * record it for all registered Slack offices now. Metadata only.
848
+ */
849
+ backfillChannelKinds() {
850
+ let recorded = 0;
851
+ for (const record of listRegisteredOffices(this.workspace.stateDir)) {
852
+ if (record.platform !== "slack")
853
+ continue;
854
+ const kind = this.channelKindFor(record.conversationId);
855
+ if (!kind)
856
+ continue;
857
+ try {
858
+ const office = this.workspace.office(record);
859
+ if (readPlatformChannelKind(office) === kind)
860
+ continue;
861
+ recordPlatformChannelKind(office, kind);
862
+ recorded++;
863
+ }
864
+ catch (err) {
865
+ log.logWarning("Failed to backfill Slack channel kind", String(err));
866
+ }
867
+ }
868
+ if (recorded > 0)
869
+ log.logInfo(`Recorded channel kind for ${recorded} Slack offices`);
870
+ }
427
871
  processSlackMessageIntake(options) {
428
- void processMessageIntake({
872
+ const kind = this.channelKindFor(options.event.conversationId);
873
+ if (kind) {
874
+ try {
875
+ recordPlatformChannelKind(this.workspace.office(options.event.address), kind);
876
+ }
877
+ catch (err) {
878
+ // A failed snapshot must not block the message; the projection just
879
+ // keeps its previous (or isolated default) posture.
880
+ log.logWarning("Failed to record Slack channel kind", String(err));
881
+ }
882
+ }
883
+ const absorbAttachmentFailure = () => {
884
+ void options.attachmentsPromise.catch((err) => {
885
+ log.logWarning("Failed to log Slack message", String(err));
886
+ });
887
+ };
888
+ return processMessageIntake({
429
889
  eventBase: options.event,
430
- workingDir: this.workingDir,
431
- isAutoReplyCandidate: options.isAutoReplyCandidate,
890
+ addressed: options.addressed,
891
+ magicWord: {
892
+ addressed: options.magicWordAddressed ?? options.addressed,
893
+ scopeFallback: "top-level",
894
+ },
895
+ busyPolicy: "queue",
432
896
  logEntryBase: {},
433
897
  processAttachments: () => options.attachmentsPromise,
434
898
  queueKey: options.queueKey,
@@ -436,26 +900,18 @@ export class SlackMessagingBot {
436
900
  handler: this.handler,
437
901
  bot: this,
438
902
  createContext: (event) => this.createContext(event),
439
- onNotTriggered: options.onNotTriggered,
440
903
  deferAttachmentsUntilRun: true,
904
+ }).then((outcome) => {
905
+ // Slack logs eagerly via logUserMessage; when intake does not enqueue,
906
+ // nothing awaits the attachment download, so absorb its failure here.
907
+ if (outcome !== "enqueued")
908
+ absorbAttachmentFailure();
909
+ }, (err) => {
910
+ log.logWarning("Slack message intake failed", String(err));
911
+ absorbAttachmentFailure();
441
912
  });
442
913
  }
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 ---
914
+ appendRunningTasks(blocks) {
459
915
  const runningSessions = this.handler.getRunningSessions();
460
916
  blocks.push({ type: "divider" }, {
461
917
  type: "header",
@@ -470,61 +926,56 @@ export class SlackMessagingBot {
470
926
  type: "context",
471
927
  elements: [{ type: "mrkdwn", text: "_No tasks running right now._" }],
472
928
  });
929
+ return;
473
930
  }
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.")
931
+ const stuckThresholdMs = 10 * 60 * 1000;
932
+ for (const session of runningSessions) {
933
+ const channelId = conversationIdOf(session.sessionKey);
934
+ const channelName = this.channels.get(channelId)?.name;
935
+ const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
936
+ const elapsedStr = elapsed < 1 ? "<1 min" : `${elapsed} min`;
937
+ const lastActivity = session.lastActivityAt ? Date.now() - session.lastActivityAt : 0;
938
+ const isStuck = lastActivity > stuckThresholdMs;
939
+ let statusLine = `${isStuck ? "_stuck_" : "_running_"} · ${elapsedStr}`;
940
+ if (session.currentTool)
941
+ statusLine += ` · ${session.currentTool}`;
942
+ if (isStuck)
943
+ statusLine += ` · idle ${Math.floor(lastActivity / 60000)}m`;
944
+ blocks.push({
945
+ type: "context",
946
+ elements: [
947
+ {
948
+ type: "mrkdwn",
949
+ text: `*${channelName ? `#${channelName}` : channelId}* · ${statusLine}`,
950
+ },
951
+ ],
952
+ });
953
+ if (isStuck) {
497
954
  blocks.push({
498
955
  type: "context",
499
956
  elements: [
957
+ { type: "mrkdwn", text: " " },
500
958
  {
501
- type: "mrkdwn",
502
- text: `*${channelName}* · ${statusLine}`,
959
+ type: "button",
960
+ text: { type: "plain_text", text: "Force Stop", emoji: true },
961
+ // `value` preserves the raw key; action_id is only a unique route.
962
+ action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
963
+ value: session.sessionKey,
964
+ style: "danger",
503
965
  },
504
966
  ],
505
967
  });
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
968
  }
525
969
  }
526
- // --- Cron jobs ---
527
- const periodicEvents = this.eventsWatcher?.getPeriodicEvents() ?? [];
970
+ }
971
+ /**
972
+ * Scheduled jobs of the opener's own DM office only: the App Home is a
973
+ * personal surface, so other offices' schedules are not listed here.
974
+ */
975
+ appendScheduledJobs(blocks, dmChannelId) {
976
+ const periodicEvents = dmChannelId && this.eventScheduler
977
+ ? this.eventScheduler.periodicEvents(createOfficeAddress("slack", dmChannelId))
978
+ : [];
528
979
  blocks.push({ type: "divider" }, {
529
980
  type: "header",
530
981
  text: {
@@ -538,34 +989,46 @@ export class SlackMessagingBot {
538
989
  type: "context",
539
990
  elements: [{ type: "mrkdwn", text: "_No scheduled jobs._" }],
540
991
  });
992
+ return;
541
993
  }
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
- }
994
+ for (const event of periodicEvents) {
995
+ const channel = event.platform === "slack" ? this.channels.get(event.conversationId) : undefined;
996
+ const channelId = channel ? `#${channel.name}` : event.conversationId;
997
+ const channelLabel = `${event.platform}:${channelId}`;
998
+ const nextRun = event.nextRun
999
+ ? new Date(event.nextRun).toLocaleString("en-US", {
1000
+ month: "short",
1001
+ day: "numeric",
1002
+ hour: "2-digit",
1003
+ minute: "2-digit",
1004
+ })
1005
+ : "—";
1006
+ blocks.push({
1007
+ type: "section",
1008
+ text: {
1009
+ type: "mrkdwn",
1010
+ text: `*${event.text}*\n└ \`${event.schedule}\` · ${channelLabel} · Next: ${nextRun}`,
1011
+ },
1012
+ });
567
1013
  }
568
- // --- Footer ---
1014
+ }
1015
+ buildHomeView(dmChannelId) {
1016
+ const blocks = [
1017
+ {
1018
+ type: "section",
1019
+ text: {
1020
+ type: "mrkdwn",
1021
+ text: `*${PRODUCT_NAME}*\nStart a new task or check on running work.`,
1022
+ },
1023
+ accessory: {
1024
+ type: "image",
1025
+ image_url: "https://media1.tenor.com/m/lfDATg4Bhc0AAAAC/happy-cat.gif",
1026
+ alt_text: PRODUCT_NAME,
1027
+ },
1028
+ },
1029
+ ];
1030
+ this.appendRunningTasks(blocks);
1031
+ this.appendScheduledJobs(blocks, dmChannelId);
569
1032
  blocks.push({ type: "divider" }, {
570
1033
  type: "context",
571
1034
  elements: [
@@ -574,55 +1037,38 @@ export class SlackMessagingBot {
574
1037
  });
575
1038
  return { type: "home", blocks: blocks };
576
1039
  }
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 = {
1040
+ createCommandAdapters(input) {
1041
+ const { conversationId, userId, userName, text, ts, ...options } = input;
1042
+ const message = createConversationMessage({
1043
+ platform: "slack",
1044
+ conversationId,
595
1045
  id: ts,
596
- sessionKey: conversationId,
1046
+ // One session identity per dispatch: the caller's thread-resolved key.
1047
+ // Defaulting to the top-level conversation here while the event carried
1048
+ // a thread key made the same run answer to two different sessions.
1049
+ sessionKey: options.sessionKey ?? conversationId,
597
1050
  conversationKind: options.ephemeralChannelId ? "shared" : "direct",
598
1051
  userId,
599
1052
  userName,
600
1053
  text,
601
1054
  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)];
1055
+ });
1056
+ const respond = async (responseText, blocks) => {
613
1057
  if (options.ephemeralChannelId) {
614
- await this.postEphemeralBlocks(options.ephemeralChannelId, userId, responseText, blocks, options.threadTs);
1058
+ await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs, blocks);
615
1059
  return;
616
1060
  }
617
- const messageTs = await this.postMessageBlocks(conversationId, responseText, blocks);
1061
+ const messageTs = blocks
1062
+ ? await this.postMessageBlocks(conversationId, responseText, blocks)
1063
+ : await this.postMessage(conversationId, responseText);
618
1064
  this.logBotResponse(conversationId, responseText, messageTs);
619
1065
  };
620
1066
  const responder = {
621
1067
  respond,
622
- replaceResponse: respond,
1068
+ replaceResponse: (responseText) => respond(responseText),
623
1069
  respondDiagnostic: async (responseText, responseOptions) => {
624
1070
  if (responseOptions?.style === "muted") {
625
- await respondMuted(responseText);
1071
+ await respond(responseText, [buildMrkdwnContextBlock(responseText)]);
626
1072
  return;
627
1073
  }
628
1074
  await respond(responseOptions?.style === "error" ? `_${responseText}_` : responseText);
@@ -639,6 +1085,7 @@ export class SlackMessagingBot {
639
1085
  deleteResponse: async () => { },
640
1086
  };
641
1087
  return {
1088
+ address: message.address,
642
1089
  message,
643
1090
  responder,
644
1091
  platform: this.getMessagingInfo(),
@@ -666,9 +1113,10 @@ export class SlackMessagingBot {
666
1113
  isMessagingBot: false,
667
1114
  ...(threadTs ? { threadTs } : {}),
668
1115
  });
669
- const event = {
670
- type: options.type ?? (isDirectMessage ? "dm" : "mention"),
1116
+ const event = createConversationEvent({
1117
+ platform: "slack",
671
1118
  conversationId,
1119
+ type: options.type ?? (isDirectMessage ? "dm" : "mention"),
672
1120
  conversationKind: isDirectMessage ? "direct" : "shared",
673
1121
  ts: eventTs,
674
1122
  user: payload.user_id,
@@ -676,54 +1124,37 @@ export class SlackMessagingBot {
676
1124
  attachments: [],
677
1125
  ...(threadTs ? { thread_ts: threadTs } : {}),
678
1126
  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
1127
  });
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,
1128
+ const context = this.createCommandAdapters({
1129
+ conversationId,
1130
+ userId: payload.user_id,
703
1131
  userName,
704
- text: payload.command,
705
- attachments: [],
706
- isMessagingBot: false,
1132
+ text: commandText,
1133
+ ts: eventTs,
1134
+ ...(isDirectMessage
1135
+ ? { threadTs, sessionKey }
1136
+ : { ephemeralChannelId: conversationId, threadTs, sessionKey }),
707
1137
  });
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);
1138
+ return { event, context };
724
1139
  }
725
- async routeSlashAdminCommand(payload) {
726
- const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
1140
+ /**
1141
+ * Generic slash-command route: synthesize the command text per the
1142
+ * manifest's SlackSlashRoute data and dispatch to the handler. Every
1143
+ * routed command — including `/pi-new` — reaches its CommandHandler
1144
+ * through runtime dispatch; the adapter holds no per-command behavior.
1145
+ */
1146
+ async routeSlashCommand(route, payload) {
1147
+ const { event, context } = this.buildSlashCommandEvent(payload, {
1148
+ includeText: route.includeText,
1149
+ thread: route.thread,
1150
+ ...(route.privateCommand
1151
+ ? {
1152
+ type: payload.channel_id.startsWith("D")
1153
+ ? "dm"
1154
+ : "private_command",
1155
+ }
1156
+ : {}),
1157
+ });
727
1158
  await this.handler.handleEvent(event, this, context);
728
1159
  }
729
1160
  setupEventHandlers() {
@@ -736,14 +1167,39 @@ export class SlackMessagingBot {
736
1167
  this.socketClient.on("unable_to_socket_mode_start", (err) => {
737
1168
  log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
738
1169
  });
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 }) {
1170
+ this.socketClient.on("app_mention", (payload) => {
1171
+ void this.intake.run(() => this.handleAppMention(payload));
1172
+ });
1173
+ this.socketClient.on("message", (payload) => {
1174
+ void this.intake.run(() => this.handleMessageEvent(payload));
1175
+ });
1176
+ this.socketClient.on("slash_commands", (payload) => {
1177
+ void this.intake.run(() => this.handleSlashCommand(payload));
1178
+ });
1179
+ this.socketClient.on("app_home_opened", (payload) => {
1180
+ void this.intake.run(() => this.handleAppHomeOpened(payload));
1181
+ });
1182
+ // The assistant surface's lifecycle. Both events were already subscribed
1183
+ // in the app manifest and then dropped on the floor, which is why the pane
1184
+ // opened empty and the sidebar filled with untitled conversations.
1185
+ this.socketClient.on("assistant_thread_started", (payload) => {
1186
+ void this.intake.run(() => this.handleAssistantThreadStarted(payload));
1187
+ });
1188
+ this.socketClient.on("assistant_thread_context_changed", (payload) => {
1189
+ void this.intake.run(() => this.handleAgentContextChangedEvent(payload));
1190
+ });
1191
+ // agent_view's spelling of the same signal.
1192
+ this.socketClient.on("app_context_changed", (payload) => {
1193
+ void this.intake.run(() => this.handleAgentContextChangedEvent(payload));
1194
+ });
1195
+ this.socketClient.on("block_actions", (payload) => {
1196
+ void this.intake.run(() => this.handleBlockAction(payload));
1197
+ });
1198
+ this.socketClient.on("interactive", (payload) => {
1199
+ void this.intake.run(() => this.handleBlockAction(payload));
1200
+ });
1201
+ }
1202
+ async handleAppMention({ event, ack, }) {
747
1203
  const e = event;
748
1204
  // Skip DMs (handled by message event)
749
1205
  if (e.channel.startsWith("D")) {
@@ -754,18 +1210,19 @@ export class SlackMessagingBot {
754
1210
  // Thread replies get their own isolated session (channelId:thread_ts).
755
1211
  const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
756
1212
  const mentionText = this.stripOwnMention(e.text);
757
- const slackEvent = {
758
- type: "mention",
1213
+ const slackEvent = createConversationEvent({
1214
+ platform: "slack",
759
1215
  conversationId: e.channel,
1216
+ type: "mention",
760
1217
  conversationKind: "shared",
761
- channel: e.channel,
762
1218
  ts: e.ts,
763
1219
  thread_ts: e.thread_ts,
764
1220
  user: e.user,
765
1221
  text: mentionText || "Please respond to the recent conversation context.",
766
- files: e.files,
767
1222
  sessionKey,
768
- };
1223
+ channel: e.channel,
1224
+ files: e.files,
1225
+ });
769
1226
  const attachmentsPromise = this.logUserMessage(slackEvent);
770
1227
  // Only trigger processing for messages AFTER startup (not replayed old messages)
771
1228
  if (this.startupTs && e.ts < this.startupTs) {
@@ -776,67 +1233,162 @@ export class SlackMessagingBot {
776
1233
  ack();
777
1234
  return;
778
1235
  }
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({
1236
+ const intake = this.processSlackMessageIntake({
795
1237
  event: slackEvent,
796
1238
  attachmentsPromise,
797
1239
  queueKey: this.resolveQueueKey(e.channel, sessionKey),
798
- isAutoReplyCandidate: false,
1240
+ addressed: true,
799
1241
  });
800
1242
  ack();
1243
+ await intake;
801
1244
  }
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) {
1245
+ admitHumanMessage(event, ack) {
1246
+ const hasSlackContent = hasMessageContent(event);
1247
+ const isOwnMessage = event.user === this.botUserId || (!!this.botId && event.bot_id === this.botId);
1248
+ if (isOwnMessage) {
808
1249
  ack();
809
- return;
1250
+ return false;
810
1251
  }
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;
816
- }
817
- if (!hasSlackContent) {
818
- ack();
819
- return;
1252
+ // User-token posts carry bot_id/app_id beside the human user. Only unknown
1253
+ // authors and known bot users take the external-bot loop-protection path.
1254
+ const authorIsKnownHuman = !!event.user && this.users.get(event.user)?.isBot === false;
1255
+ const isExternalMessage = event.subtype === "bot_message" || (!!event.bot_id && !authorIsKnownHuman);
1256
+ if (isExternalMessage) {
1257
+ const supportedSubtype = BOT_MESSAGE_SUBTYPES.has(event.subtype);
1258
+ if (supportedSubtype && hasSlackContent) {
1259
+ void this.logExternalMessagingBotMessage(event).catch((err) => {
1260
+ log.logWarning("Failed to log Slack bot message", String(err));
1261
+ });
820
1262
  }
821
- void this.logExternalMessagingBotMessage(e).catch((err) => {
822
- log.logWarning("Failed to log Slack bot message", String(err));
823
- });
824
1263
  ack();
825
- return;
1264
+ return false;
826
1265
  }
827
- if (!e.user) {
828
- ack();
829
- return;
1266
+ const isSupportedUserMessage = !!event.user && USER_MESSAGE_SUBTYPES.has(event.subtype) && hasSlackContent;
1267
+ if (isSupportedUserMessage)
1268
+ return true;
1269
+ ack();
1270
+ return false;
1271
+ }
1272
+ /**
1273
+ * Handle a DM message that may be about a background task without spending
1274
+ * a model turn: a pure status question is answered from observation, and a
1275
+ * supplement is steered into the running task. Jev decides which (falling
1276
+ * back to the regex status shortcut); anything else returns false so the
1277
+ * message becomes an ordinary conversation turn.
1278
+ */
1279
+ async deliverTaskUpdate(event, attachmentsPromise) {
1280
+ if (matchMagicWord(event.text) === "stop" || event.text.startsWith("/"))
1281
+ return false;
1282
+ event.attachments = await attachmentsPromise;
1283
+ const context = this.createContext(event);
1284
+ const post = (text) => event.thread_ts
1285
+ ? this.postInThread(event.channel, event.thread_ts, text)
1286
+ : this.postMessage(event.channel, text);
1287
+ try {
1288
+ const tasks = await context.responder.getTaskStatus(event.thread_ts ? event.sessionKey : undefined);
1289
+ if (!tasks.length)
1290
+ return false;
1291
+ const active = tasks.filter((t) => t.status === "running" || t.status === "stopping");
1292
+ const user = this.users.get(event.user);
1293
+ // A message carrying files is never a pure status question; in a task
1294
+ // thread it is a supplement, at top level an ordinary turn.
1295
+ const intent = event.attachments?.length
1296
+ ? event.thread_ts
1297
+ ? "steer"
1298
+ : "request"
1299
+ : await classifyTaskIntent(buildTaskIntentState(this.conversationDir(event.channel), event, {
1300
+ speaker: user?.displayName ?? user?.userName,
1301
+ tasks,
1302
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1303
+ botUserId: this.botUserId,
1304
+ }), event.text, { conversationId: event.channel, inTaskThread: !!event.thread_ts });
1305
+ if (intent === "status") {
1306
+ const selection = event.thread_ts ? tasks : active.length ? active : tasks.slice(0, 1);
1307
+ const text = selection.length > 1
1308
+ ? "目前有多個任務在處理,請到你想查詢的任務對話串詢問,避免弄錯。"
1309
+ : formatTaskStatus(selection);
1310
+ const ts = await post(text);
1311
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1312
+ return true;
1313
+ }
1314
+ if (intent === "steer") {
1315
+ // In a thread the message already targets that task; at top level it
1316
+ // can only be steered when exactly one task is running.
1317
+ const target = event.thread_ts
1318
+ ? context.message
1319
+ : active.length === 1
1320
+ ? {
1321
+ ...context.message,
1322
+ sessionKey: active[0].sessionKey,
1323
+ threadTs: active[0].threadTs,
1324
+ }
1325
+ : undefined;
1326
+ if (target && (await this.handler.steer?.(target))) {
1327
+ const text = "收到補充,會在下一個處理步驟納入;目前的操作不會立即中斷。";
1328
+ const ts = await post(text);
1329
+ this.logBotResponse(event.channel, text, ts, event.thread_ts);
1330
+ return true;
1331
+ }
1332
+ }
830
1333
  }
831
- if (e.subtype !== undefined && e.subtype !== "file_share") {
832
- ack();
833
- return;
1334
+ catch (error) {
1335
+ await post(error instanceof Error ? error.message : "Could not deliver task update.");
1336
+ return true;
834
1337
  }
835
- if (!hasSlackContent) {
836
- ack();
837
- return;
1338
+ return false;
1339
+ }
1340
+ /**
1341
+ * Ask Jev a single typed yes/no question about whether this unaddressed
1342
+ * shared-channel message (top-level or thread reply) addresses mikan. The
1343
+ * scored state carries the surrounding scope from log.jsonl, since a bare
1344
+ * thread reply cannot be judged from its own text. Only called in `jev`
1345
+ * auto-reply mode, and only awaited by the caller in that mode, so
1346
+ * `on`/`off` stay synchronous (magic-word `stop` handling depends on that).
1347
+ * Any failure (including a missing `OPENROUTER_API_KEY`) fails closed to
1348
+ * "not addressed" so a misconfiguration cannot make the bot noisy in a
1349
+ * shared channel.
1350
+ */
1351
+ async evaluateJevAddressed(event) {
1352
+ if (!event.text.trim() || matchMagicWord(event.text) === "stop")
1353
+ return false;
1354
+ try {
1355
+ const user = this.users.get(event.user);
1356
+ const state = buildAutoReplyState(this.conversationDir(event.channel), event, {
1357
+ speaker: user?.displayName ?? user?.userName,
1358
+ resolveName: (id) => this.users.get(id)?.displayName ?? this.users.get(id)?.userName,
1359
+ botUserId: this.botUserId,
1360
+ });
1361
+ const result = await evaluateWithJev(state, {
1362
+ addressed: { type: "boolean", instructions: JEV_ADDRESSED_INSTRUCTIONS },
1363
+ });
1364
+ const probability = result.answers.addressed.probability;
1365
+ log.logInfo(`[${event.channel}] jev auto-reply: probability=${probability.toFixed(2)} addressed=${probability > 0.5} text="${event.text.slice(0, 80)}"`);
1366
+ return probability > 0.5;
1367
+ }
1368
+ catch (err) {
1369
+ if (err instanceof JevNotConfiguredError) {
1370
+ log.logWarning("Slack auto-reply jev mode requires OPENROUTER_API_KEY; treating message as unaddressed", String(err));
1371
+ return false;
1372
+ }
1373
+ reportUserFacingError(err, {
1374
+ domain: "chat_platform",
1375
+ surface: "slack_auto_reply_jev",
1376
+ operation: "evaluate",
1377
+ severity: "warning",
1378
+ context: { conversationId: event.conversationId },
1379
+ });
1380
+ return false;
838
1381
  }
839
- const isDM = e.channel_type === "im";
1382
+ }
1383
+ async handleMessageEvent({ event, ack, }) {
1384
+ const e = event;
1385
+ if (!this.admitHumanMessage(e, ack))
1386
+ return;
1387
+ // message.im normally carries channel_type "im", but fall back to the
1388
+ // D-prefix convention (used by handleAppMention and session keys) so a
1389
+ // missing channel_type cannot silently classify a DM as an unaddressed
1390
+ // channel message that never triggers.
1391
+ const isDM = e.channel_type === "im" || e.channel.startsWith("D");
840
1392
  const conversationKind = isDM ? "direct" : "shared";
841
1393
  const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
842
1394
  // Skip channel @mentions - already handled by app_mention event
@@ -844,20 +1396,26 @@ export class SlackMessagingBot {
844
1396
  ack();
845
1397
  return;
846
1398
  }
847
- const isThreadReply = !!e.thread_ts;
848
1399
  const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
849
- const slackEvent = {
850
- type: isDM ? "dm" : "mention",
1400
+ // Name the conversation from the first thing the person said. Guarded on
1401
+ // the assistant registry, so classic DM threads are untouched, and it
1402
+ // claims the title once per thread.
1403
+ if (isDM && e.thread_ts && e.text) {
1404
+ void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
1405
+ }
1406
+ const slackEvent = createConversationEvent({
1407
+ platform: "slack",
851
1408
  conversationId: e.channel,
1409
+ type: (isDM ? "dm" : "mention"),
852
1410
  conversationKind,
853
- channel: e.channel,
854
1411
  ts: e.ts,
855
1412
  thread_ts: e.thread_ts,
856
1413
  user: e.user,
857
1414
  text: this.stripOwnMention(e.text),
858
- files: e.files,
859
1415
  sessionKey,
860
- };
1416
+ channel: e.channel,
1417
+ files: e.files,
1418
+ });
861
1419
  const attachmentsPromise = this.logUserMessage(slackEvent);
862
1420
  // Only trigger processing for messages AFTER startup (not replayed old messages)
863
1421
  if (this.startupTs && e.ts < this.startupTs) {
@@ -868,61 +1426,42 @@ export class SlackMessagingBot {
868
1426
  ack();
869
1427
  return;
870
1428
  }
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();
1429
+ const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1430
+ slackEvent.sessionKey = activeSessionKey;
1431
+ // Task threads always go through the task gate. Top-level DMs do only
1432
+ // while a task is running (so supplements can be steered) or when the
1433
+ // regex already recognises a status question about a finished one, so
1434
+ // idle DMs never pay a Jev round-trip.
1435
+ const taskControl = isDM &&
1436
+ (e.thread_ts
1437
+ ? this.isTaskThread(e.channel, e.thread_ts)
1438
+ : this.hasRunningTaskThread(e.channel) || isTaskStatusQuestion(slackEvent.text));
1439
+ if (taskControl) {
913
1440
  ack();
914
- return;
1441
+ if (await this.deliverTaskUpdate(slackEvent, attachmentsPromise))
1442
+ return;
915
1443
  }
916
- const activeSessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
917
- slackEvent.sessionKey = activeSessionKey;
918
- this.processSlackMessageIntake({
1444
+ // Shared-channel messages without a mention, top-level or in a thread,
1445
+ // all go through the same auto-reply gate; jev mode sees the surrounding
1446
+ // scope so thread follow-ups are judged in context rather than dropped.
1447
+ const autoReplyMode = isDM
1448
+ ? "off"
1449
+ : slackConversationAutoReplyMode(this.workspace.office(slackEvent.address));
1450
+ const autoReply = autoReplyMode === "on"
1451
+ ? true
1452
+ : autoReplyMode === "jev"
1453
+ ? await this.evaluateJevAddressed(slackEvent)
1454
+ : false;
1455
+ const intake = this.processSlackMessageIntake({
919
1456
  event: slackEvent,
920
1457
  attachmentsPromise,
921
1458
  queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
922
- isAutoReplyCandidate: true,
923
- onNotTriggered: logOnly,
1459
+ addressed: isDM || autoReply,
1460
+ magicWordAddressed: isDM,
924
1461
  });
925
- ack();
1462
+ if (!taskControl)
1463
+ ack();
1464
+ await intake;
926
1465
  }
927
1466
  async handleSlashCommand({ body, ack, }) {
928
1467
  const payload = body;
@@ -931,73 +1470,44 @@ export class SlackMessagingBot {
931
1470
  return;
932
1471
  }
933
1472
  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({
1473
+ const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
1474
+ if (!entry)
1475
+ return;
1476
+ if (!entry.slackRoute)
1477
+ return;
1478
+ try {
1479
+ await this.routeSlashCommand(entry.slackRoute, {
937
1480
  command,
938
1481
  text,
939
1482
  channel_id,
940
1483
  user_id,
941
1484
  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
1485
  thread_ts,
954
1486
  });
955
1487
  }
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({
969
- command,
970
- text,
971
- channel_id,
972
- user_id,
973
- 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
- thread_ts,
983
- });
984
- }
985
- if (!handlerPromise) {
986
- return;
987
- }
988
- handlerPromise.catch((err) => {
1488
+ catch (err) {
989
1489
  log.logWarning("Slack slash command error", err instanceof Error ? err.message : String(err));
990
- });
1490
+ }
991
1491
  }
992
1492
  handleAppHomeOpened({ event, ack }) {
993
1493
  const e = event;
994
1494
  ack();
1495
+ // Under agent_view this is how Slack says "the person opened the app's
1496
+ // DM" — the signal assistant_thread_started used to carry. It fires on
1497
+ // every open, not once per conversation, so this path refreshes the
1498
+ // pinned prompts and deliberately does not greet.
1499
+ if (e.tab === "messages") {
1500
+ if (e.channel) {
1501
+ void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
1502
+ }
1503
+ return;
1504
+ }
995
1505
  if (e.tab !== "home")
996
1506
  return;
997
1507
  this.webClient.views
998
1508
  .publish({
999
1509
  user_id: e.user,
1000
- view: this.buildHomeView(),
1510
+ view: this.buildHomeView(e.channel),
1001
1511
  })
1002
1512
  .catch((err) => {
1003
1513
  log.logWarning(`Failed to publish App Home view`, String(err));
@@ -1015,21 +1525,24 @@ export class SlackMessagingBot {
1015
1525
  return;
1016
1526
  }
1017
1527
  ack();
1018
- const sessionKey = action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1528
+ // Prefer the verbatim key from `value`; the action_id fallback only
1529
+ // serves buttons rendered before `value` existed and misdecodes session
1530
+ // keys whose conversation id contains "_".
1531
+ const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1019
1532
  const userId = body.user?.id;
1020
- const channelId = body.container?.channel_id || sessionKey.split(":")[0];
1533
+ const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
1021
1534
  log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
1022
1535
  // Use handler's forceStop method
1023
- this.handler.forceStop(sessionKey);
1536
+ this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
1024
1537
  // Notify in channel
1025
1538
  await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
1026
1539
  // Refresh home tab
1027
1540
  if (userId) {
1028
- this.webClient.views
1029
- .publish({
1541
+ this.openDirectConversation(userId)
1542
+ .then((dmChannelId) => this.webClient.views.publish({
1030
1543
  user_id: userId,
1031
- view: this.buildHomeView(),
1032
- })
1544
+ view: this.buildHomeView(dmChannelId),
1545
+ }))
1033
1546
  .catch((err) => {
1034
1547
  log.logWarning(`Failed to refresh App Home view`, String(err));
1035
1548
  });
@@ -1045,6 +1558,8 @@ export class SlackMessagingBot {
1045
1558
  const selectedOptions = Array.isArray(action.selected_options)
1046
1559
  ? action.selected_options
1047
1560
  : undefined;
1561
+ const threadTs = container.thread_ts;
1562
+ const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1048
1563
  const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
1049
1564
  const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
1050
1565
  const valueText = selectedTexts?.length
@@ -1052,8 +1567,6 @@ export class SlackMessagingBot {
1052
1567
  : (selectedText ?? action.value ?? action.action_id);
1053
1568
  const text = `[Slack action] ${action.action_id}: ${valueText}`;
1054
1569
  const ts = `action:${Date.now()}`;
1055
- const threadTs = container.thread_ts;
1056
- const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1057
1570
  this.logToFile(channelId, {
1058
1571
  date: new Date().toISOString(),
1059
1572
  ts,
@@ -1080,9 +1593,10 @@ export class SlackMessagingBot {
1080
1593
  messageTs: container.message_ts,
1081
1594
  },
1082
1595
  });
1083
- const event = {
1084
- type: "slack_action",
1596
+ const event = createConversationEvent({
1597
+ platform: "slack",
1085
1598
  conversationId: channelId,
1599
+ type: "slack_action",
1086
1600
  conversationKind: channelId.startsWith("D") ? "direct" : "shared",
1087
1601
  ts,
1088
1602
  user: userId,
@@ -1090,23 +1604,71 @@ export class SlackMessagingBot {
1090
1604
  attachments: [],
1091
1605
  ...(threadTs ? { thread_ts: threadTs } : {}),
1092
1606
  sessionKey,
1093
- };
1607
+ });
1094
1608
  this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
1095
1609
  const slackEvent = {
1096
- type: event.conversationKind === "direct" ? "dm" : "mention",
1097
- conversationId: channelId,
1098
- conversationKind: event.conversationKind,
1610
+ ...createConversationEvent({
1611
+ platform: "slack",
1612
+ conversationId: channelId,
1613
+ type: (event.conversationKind === "direct" ? "dm" : "mention"),
1614
+ conversationKind: event.conversationKind,
1615
+ ts,
1616
+ thread_ts: threadTs,
1617
+ user: userId,
1618
+ text,
1619
+ attachments: [],
1620
+ sessionKey,
1621
+ }),
1099
1622
  channel: channelId,
1100
- ts,
1101
- thread_ts: threadTs,
1102
- user: userId,
1103
- text,
1104
1623
  attachments: [],
1105
- sessionKey,
1106
1624
  };
1107
1625
  return this.handler.handleEvent(event, this, this.createContext(slackEvent));
1108
1626
  });
1109
1627
  }
1628
+ /**
1629
+ * Download Slack message files into the office's attachments directory.
1630
+ * The Slack contract is all-or-error: any failed download throws after the
1631
+ * successful ones are on disk, so callers still log the message text.
1632
+ */
1633
+ async processAttachments(channelId, files, timestamp) {
1634
+ const items = [];
1635
+ for (const file of files) {
1636
+ const url = file.url_private_download || file.url_private;
1637
+ if (!url)
1638
+ continue;
1639
+ if (!file.name) {
1640
+ throw new Error(`Attachment missing name for URL: ${url}`);
1641
+ }
1642
+ items.push({
1643
+ name: file.name,
1644
+ // Slack timestamps are float seconds; the stored filename uses ms.
1645
+ timestampMs: Math.floor(parseFloat(timestamp) * 1000),
1646
+ download: (destPath) => this.downloadSlackFile(url, destPath),
1647
+ });
1648
+ }
1649
+ const office = this.workspace.office(createOfficeAddress("slack", channelId));
1650
+ const { saved, failed } = await saveIncomingAttachments(office, items);
1651
+ const firstFailure = failed[0];
1652
+ if (firstFailure) {
1653
+ const { name, error } = firstFailure;
1654
+ const errorMsg = error instanceof Error ? error.message : String(error);
1655
+ throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
1656
+ }
1657
+ return saved;
1658
+ }
1659
+ /** Authorized download with retry; Slack file URLs require the bot token. */
1660
+ async downloadSlackFile(url, destPath) {
1661
+ await withRetry(async () => {
1662
+ const response = await fetch(url, {
1663
+ headers: { Authorization: `Bearer ${this.botToken}` },
1664
+ });
1665
+ if (!response.ok) {
1666
+ throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
1667
+ }
1668
+ const buffer = await response.arrayBuffer();
1669
+ await writeFile(destPath, Buffer.from(buffer));
1670
+ }, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
1671
+ }
1110
1672
  /**
1111
1673
  * Log a user message to log.jsonl after attachments are ready.
1112
1674
  */
@@ -1116,7 +1678,7 @@ export class SlackMessagingBot {
1116
1678
  let attachmentError;
1117
1679
  if (event.files) {
1118
1680
  try {
1119
- attachments = await this.store.processAttachments(event.channel, event.files, event.ts);
1681
+ attachments = await this.processAttachments(event.channel, event.files, event.ts);
1120
1682
  }
1121
1683
  catch (err) {
1122
1684
  attachmentError = err;
@@ -1141,7 +1703,7 @@ export class SlackMessagingBot {
1141
1703
  }
1142
1704
  async logExternalMessagingBotMessage(event) {
1143
1705
  const attachments = event.files
1144
- ? await this.store.processAttachments(event.channel, event.files, event.ts)
1706
+ ? await this.processAttachments(event.channel, event.files, event.ts)
1145
1707
  : [];
1146
1708
  const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
1147
1709
  this.logToFile(event.channel, {
@@ -1164,7 +1726,7 @@ export class SlackMessagingBot {
1164
1726
  // Private - Backfill
1165
1727
  // ==========================================================================
1166
1728
  async getExistingTimestamps(channelId) {
1167
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1729
+ const logPath = join(this.conversationDir(channelId), "log.jsonl");
1168
1730
  const timestamps = new Set();
1169
1731
  if (!existsSync(logPath))
1170
1732
  return timestamps;
@@ -1172,7 +1734,7 @@ export class SlackMessagingBot {
1172
1734
  const lines = content.trim().split("\n").filter(Boolean);
1173
1735
  for (let i = 0; i < lines.length; i++) {
1174
1736
  try {
1175
- const entry = JSON.parse(lines[i]);
1737
+ const entry = JSON.parse(lines[i] ?? "");
1176
1738
  if (entry.ts)
1177
1739
  timestamps.add(entry.ts);
1178
1740
  }
@@ -1182,6 +1744,19 @@ export class SlackMessagingBot {
1182
1744
  }
1183
1745
  return timestamps;
1184
1746
  }
1747
+ isBackfillableMessage(message) {
1748
+ if (message.user === this.botUserId)
1749
+ return true;
1750
+ if (hasBotIdentity(message)) {
1751
+ if (this.botId && message.bot_id === this.botId)
1752
+ return false;
1753
+ return BOT_MESSAGE_SUBTYPES.has(message.subtype) && hasMessageContent(message);
1754
+ }
1755
+ // Unlike live intake, historical human posts need text or files, not just blocks.
1756
+ return (!!message.user &&
1757
+ USER_MESSAGE_SUBTYPES.has(message.subtype) &&
1758
+ hasMessageContent(message, false));
1759
+ }
1185
1760
  async backfillChannel(channelId, upperBoundTs) {
1186
1761
  const existingTs = await this.getExistingTimestamps(channelId);
1187
1762
  // Find the biggest ts in log.jsonl
@@ -1213,36 +1788,14 @@ export class SlackMessagingBot {
1213
1788
  const relevantMessages = allMessages.filter((msg) => {
1214
1789
  if (!msg.ts || existingTs.has(msg.ts))
1215
1790
  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
- return false;
1238
- return true;
1791
+ return this.isBackfillableMessage(msg);
1239
1792
  });
1240
1793
  // Reverse to chronological order
1241
1794
  relevantMessages.reverse();
1242
1795
  // Log each message to log.jsonl
1243
1796
  for (const msg of relevantMessages) {
1244
1797
  const isMikanMessage = msg.user === this.botUserId;
1245
- const isExternalMessagingBotMessage = !isMikanMessage && (!!msg.bot_id || msg.subtype === "bot_message");
1798
+ const isExternalMessagingBotMessage = !isMikanMessage && hasBotIdentity(msg);
1246
1799
  if (isExternalMessagingBotMessage) {
1247
1800
  await this.logExternalMessagingBotMessage({ ...msg, channel: channelId, ts: msg.ts });
1248
1801
  continue;
@@ -1250,7 +1803,7 @@ export class SlackMessagingBot {
1250
1803
  const user = this.users.get(msg.user);
1251
1804
  const text = this.stripOwnMention(msg.text);
1252
1805
  const attachments = msg.files
1253
- ? await this.store.processAttachments(channelId, msg.files, msg.ts)
1806
+ ? await this.processAttachments(channelId, msg.files, msg.ts)
1254
1807
  : [];
1255
1808
  this.logToFile(channelId, {
1256
1809
  date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
@@ -1271,7 +1824,7 @@ export class SlackMessagingBot {
1271
1824
  // Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
1272
1825
  const channelsToBackfill = [];
1273
1826
  for (const [channelId, channel] of this.channels) {
1274
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1827
+ const logPath = join(this.conversationDir(channelId), "log.jsonl");
1275
1828
  if (existsSync(logPath)) {
1276
1829
  channelsToBackfill.push([channelId, channel]);
1277
1830
  }
@@ -1289,7 +1842,7 @@ export class SlackMessagingBot {
1289
1842
  log.logWarning(`Failed to backfill #${channel.name}`, String(error));
1290
1843
  }
1291
1844
  // Add delay between channels to avoid hitting Slack rate limits
1292
- if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
1845
+ if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
1293
1846
  await new Promise((resolve) => setTimeout(resolve, 500));
1294
1847
  }
1295
1848
  }
@@ -1304,16 +1857,15 @@ export class SlackMessagingBot {
1304
1857
  do {
1305
1858
  const result = await this.webClient.users.list({ limit: 200, cursor });
1306
1859
  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
- }
1860
+ for (const u of members ?? []) {
1861
+ if (!u.id || !u.name || u.deleted)
1862
+ continue;
1863
+ this.users.set(u.id, {
1864
+ id: u.id,
1865
+ userName: u.name,
1866
+ displayName: u.real_name || u.name,
1867
+ isBot: !!u.is_bot,
1868
+ });
1317
1869
  }
1318
1870
  cursor = result.response_metadata?.next_cursor;
1319
1871
  } while (cursor);
@@ -1329,12 +1881,15 @@ export class SlackMessagingBot {
1329
1881
  cursor,
1330
1882
  });
1331
1883
  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
- }
1884
+ for (const c of channels ?? []) {
1885
+ if (!c.id || !c.name || !c.is_member)
1886
+ continue;
1887
+ this.channels.set(c.id, {
1888
+ id: c.id,
1889
+ name: c.name,
1890
+ ...(typeof c.is_private === "boolean" ? { isPrivate: c.is_private } : {}),
1891
+ ...(c.is_shared || c.is_ext_shared ? { isExternallyShared: true } : {}),
1892
+ });
1338
1893
  }
1339
1894
  cursor = result.response_metadata?.next_cursor;
1340
1895
  } while (cursor);
@@ -1347,15 +1902,13 @@ export class SlackMessagingBot {
1347
1902
  cursor,
1348
1903
  });
1349
1904
  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
- }
1905
+ for (const im of ims ?? []) {
1906
+ if (!im.id)
1907
+ continue;
1908
+ // Use user's name as channel name for DMs
1909
+ const user = im.user ? this.users.get(im.user) : undefined;
1910
+ const name = user ? `DM:${user.userName}` : `DM:${im.id}`;
1911
+ this.channels.set(im.id, { id: im.id, name });
1359
1912
  }
1360
1913
  cursor = result.response_metadata?.next_cursor;
1361
1914
  } while (cursor);