@geminixiang/mikan 1.0.0-beta.4 → 1.0.0-beta.41

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 (661) hide show
  1. package/CHANGELOG.md +714 -0
  2. package/README.md +99 -36
  3. package/dist/adapter.d.ts +11 -1
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js +18 -1
  6. package/dist/adapter.js.map +1 -1
  7. package/dist/adapters/discord/bot.d.ts +6 -7
  8. package/dist/adapters/discord/bot.d.ts.map +1 -1
  9. package/dist/adapters/discord/bot.js +88 -122
  10. package/dist/adapters/discord/bot.js.map +1 -1
  11. package/dist/adapters/discord/components.d.ts +33 -0
  12. package/dist/adapters/discord/components.d.ts.map +1 -0
  13. package/dist/adapters/discord/components.js +32 -0
  14. package/dist/adapters/discord/components.js.map +1 -0
  15. package/dist/adapters/discord/context.d.ts +3 -1
  16. package/dist/adapters/discord/context.d.ts.map +1 -1
  17. package/dist/adapters/discord/context.js +66 -217
  18. package/dist/adapters/discord/context.js.map +1 -1
  19. package/dist/adapters/discord/format.d.ts +3 -0
  20. package/dist/adapters/discord/format.d.ts.map +1 -0
  21. package/dist/adapters/discord/format.js +86 -0
  22. package/dist/adapters/discord/format.js.map +1 -0
  23. package/dist/adapters/github/bot.d.ts +89 -0
  24. package/dist/adapters/github/bot.d.ts.map +1 -0
  25. package/dist/adapters/github/bot.js +653 -0
  26. package/dist/adapters/github/bot.js.map +1 -0
  27. package/dist/adapters/github/client.d.ts +122 -0
  28. package/dist/adapters/github/client.d.ts.map +1 -0
  29. package/dist/adapters/github/client.js +314 -0
  30. package/dist/adapters/github/client.js.map +1 -0
  31. package/dist/adapters/github/cloudbuild.d.ts +19 -0
  32. package/dist/adapters/github/cloudbuild.d.ts.map +1 -0
  33. package/dist/adapters/github/cloudbuild.js +71 -0
  34. package/dist/adapters/github/cloudbuild.js.map +1 -0
  35. package/dist/adapters/github/context.d.ts +12 -0
  36. package/dist/adapters/github/context.d.ts.map +1 -0
  37. package/dist/adapters/github/context.js +101 -0
  38. package/dist/adapters/github/context.js.map +1 -0
  39. package/dist/adapters/github/gcp-auth.d.ts +28 -0
  40. package/dist/adapters/github/gcp-auth.d.ts.map +1 -0
  41. package/dist/adapters/github/gcp-auth.js +196 -0
  42. package/dist/adapters/github/gcp-auth.js.map +1 -0
  43. package/dist/adapters/github/github-ops.d.ts +91 -0
  44. package/dist/adapters/github/github-ops.d.ts.map +1 -0
  45. package/dist/adapters/github/github-ops.js +406 -0
  46. package/dist/adapters/github/github-ops.js.map +1 -0
  47. package/dist/adapters/github/ids.d.ts +34 -0
  48. package/dist/adapters/github/ids.d.ts.map +1 -0
  49. package/dist/adapters/github/ids.js +57 -0
  50. package/dist/adapters/github/ids.js.map +1 -0
  51. package/dist/adapters/github/repo.d.ts +33 -0
  52. package/dist/adapters/github/repo.d.ts.map +1 -0
  53. package/dist/adapters/github/repo.js +141 -0
  54. package/dist/adapters/github/repo.js.map +1 -0
  55. package/dist/adapters/github/tool-pack.d.ts +14 -0
  56. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  57. package/dist/adapters/github/tool-pack.js +56 -0
  58. package/dist/adapters/github/tool-pack.js.map +1 -0
  59. package/dist/adapters/github/tools/checks.d.ts +19 -0
  60. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  61. package/dist/adapters/github/tools/checks.js +105 -0
  62. package/dist/adapters/github/tools/checks.js.map +1 -0
  63. package/dist/adapters/github/tools/issue.d.ts +21 -0
  64. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  65. package/dist/adapters/github/tools/issue.js +48 -0
  66. package/dist/adapters/github/tools/issue.js.map +1 -0
  67. package/dist/adapters/github/tools/pr.d.ts +22 -0
  68. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  69. package/dist/adapters/github/tools/pr.js +48 -0
  70. package/dist/adapters/github/tools/pr.js.map +1 -0
  71. package/dist/adapters/github/tools/read.d.ts +21 -0
  72. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  73. package/dist/adapters/github/tools/read.js +141 -0
  74. package/dist/adapters/github/tools/read.js.map +1 -0
  75. package/dist/adapters/github/tools/review-reply.d.ts +17 -0
  76. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  77. package/dist/adapters/github/tools/review-reply.js +38 -0
  78. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  79. package/dist/adapters/github/tools/sync.d.ts +17 -0
  80. package/dist/adapters/github/tools/sync.d.ts.map +1 -0
  81. package/dist/adapters/github/tools/sync.js +34 -0
  82. package/dist/adapters/github/tools/sync.js.map +1 -0
  83. package/dist/adapters/github/types.d.ts +373 -0
  84. package/dist/adapters/github/types.d.ts.map +1 -0
  85. package/dist/adapters/github/types.js +2 -0
  86. package/dist/adapters/github/types.js.map +1 -0
  87. package/dist/adapters/intake.d.ts +16 -5
  88. package/dist/adapters/intake.d.ts.map +1 -1
  89. package/dist/adapters/intake.js +86 -23
  90. package/dist/adapters/intake.js.map +1 -1
  91. package/dist/adapters/markdown-tables.d.ts +37 -0
  92. package/dist/adapters/markdown-tables.d.ts.map +1 -0
  93. package/dist/adapters/markdown-tables.js +164 -0
  94. package/dist/adapters/markdown-tables.js.map +1 -0
  95. package/dist/adapters/progressive-renderer.d.ts +7 -0
  96. package/dist/adapters/progressive-renderer.d.ts.map +1 -0
  97. package/dist/adapters/progressive-renderer.js +440 -0
  98. package/dist/adapters/progressive-renderer.js.map +1 -0
  99. package/dist/adapters/shared.d.ts +23 -10
  100. package/dist/adapters/shared.d.ts.map +1 -1
  101. package/dist/adapters/shared.js +60 -22
  102. package/dist/adapters/shared.js.map +1 -1
  103. package/dist/adapters/slack/assistant.d.ts +101 -0
  104. package/dist/adapters/slack/assistant.d.ts.map +1 -0
  105. package/dist/adapters/slack/assistant.js +204 -0
  106. package/dist/adapters/slack/assistant.js.map +1 -0
  107. package/dist/adapters/slack/blocks.d.ts +14 -0
  108. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  109. package/dist/adapters/slack/blocks.js +105 -147
  110. package/dist/adapters/slack/blocks.js.map +1 -1
  111. package/dist/adapters/slack/bot.d.ts +95 -13
  112. package/dist/adapters/slack/bot.d.ts.map +1 -1
  113. package/dist/adapters/slack/bot.js +545 -211
  114. package/dist/adapters/slack/bot.js.map +1 -1
  115. package/dist/adapters/slack/context.d.ts +3 -1
  116. package/dist/adapters/slack/context.d.ts.map +1 -1
  117. package/dist/adapters/slack/context.js +11 -17
  118. package/dist/adapters/slack/context.js.map +1 -1
  119. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  120. package/dist/adapters/slack/response-lifecycle.js +197 -323
  121. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  122. package/dist/adapters/slack/session.d.ts.map +1 -1
  123. package/dist/adapters/slack/session.js +7 -11
  124. package/dist/adapters/slack/session.js.map +1 -1
  125. package/dist/adapters/slack/stream-limits.d.ts +35 -0
  126. package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
  127. package/dist/adapters/slack/stream-limits.js +56 -0
  128. package/dist/adapters/slack/stream-limits.js.map +1 -0
  129. package/dist/adapters/slack/tool-pack.d.ts +11 -0
  130. package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
  131. package/dist/adapters/slack/tool-pack.js +45 -0
  132. package/dist/adapters/slack/tool-pack.js.map +1 -0
  133. package/dist/adapters/slack/tools/attach.js +1 -1
  134. package/dist/adapters/slack/tools/attach.js.map +1 -1
  135. package/dist/adapters/slack/tools/blockkit.d.ts +14 -0
  136. package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
  137. package/dist/adapters/slack/tools/blockkit.js +106 -0
  138. package/dist/adapters/slack/tools/blockkit.js.map +1 -0
  139. package/dist/adapters/slack/types.d.ts +40 -2
  140. package/dist/adapters/slack/types.d.ts.map +1 -1
  141. package/dist/adapters/slack/types.js.map +1 -1
  142. package/dist/adapters/telegram/bot.d.ts +8 -9
  143. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  144. package/dist/adapters/telegram/bot.js +115 -163
  145. package/dist/adapters/telegram/bot.js.map +1 -1
  146. package/dist/adapters/telegram/context.d.ts +2 -0
  147. package/dist/adapters/telegram/context.d.ts.map +1 -1
  148. package/dist/adapters/telegram/context.js +53 -188
  149. package/dist/adapters/telegram/context.js.map +1 -1
  150. package/dist/adapters/types.d.ts +132 -12
  151. package/dist/adapters/types.d.ts.map +1 -1
  152. package/dist/adapters/types.js.map +1 -1
  153. package/dist/agent-events.d.ts +3 -2
  154. package/dist/agent-events.d.ts.map +1 -1
  155. package/dist/agent-events.js +19 -1
  156. package/dist/agent-events.js.map +1 -1
  157. package/dist/agent.d.ts +14 -19
  158. package/dist/agent.d.ts.map +1 -1
  159. package/dist/agent.js +841 -298
  160. package/dist/agent.js.map +1 -1
  161. package/dist/cli/arg-grammar.d.ts +25 -0
  162. package/dist/cli/arg-grammar.d.ts.map +1 -0
  163. package/dist/cli/arg-grammar.js +61 -0
  164. package/dist/cli/arg-grammar.js.map +1 -0
  165. package/dist/cli/boot.d.ts +5 -0
  166. package/dist/cli/boot.d.ts.map +1 -0
  167. package/dist/cli/boot.js +117 -0
  168. package/dist/cli/boot.js.map +1 -0
  169. package/dist/cli/download.d.ts.map +1 -0
  170. package/dist/{download.js → cli/download.js} +2 -0
  171. package/dist/cli/download.js.map +1 -0
  172. package/dist/cli/ext-dev.d.ts +2 -0
  173. package/dist/cli/ext-dev.d.ts.map +1 -0
  174. package/dist/cli/ext-dev.js +188 -0
  175. package/dist/cli/ext-dev.js.map +1 -0
  176. package/dist/cli/ext.d.ts +2 -0
  177. package/dist/cli/ext.d.ts.map +1 -0
  178. package/dist/cli/ext.js +324 -0
  179. package/dist/cli/ext.js.map +1 -0
  180. package/dist/cli/office.d.ts +2 -0
  181. package/dist/cli/office.d.ts.map +1 -0
  182. package/dist/cli/office.js +91 -0
  183. package/dist/cli/office.js.map +1 -0
  184. package/dist/cli/types.d.ts +16 -0
  185. package/dist/cli/types.d.ts.map +1 -0
  186. package/dist/cli/types.js +2 -0
  187. package/dist/cli/types.js.map +1 -0
  188. package/dist/commands/admin.d.ts.map +1 -1
  189. package/dist/commands/admin.js +3 -2
  190. package/dist/commands/admin.js.map +1 -1
  191. package/dist/commands/auto-reply.d.ts.map +1 -1
  192. package/dist/commands/auto-reply.js +5 -9
  193. package/dist/commands/auto-reply.js.map +1 -1
  194. package/dist/commands/extensions.d.ts +18 -6
  195. package/dist/commands/extensions.d.ts.map +1 -1
  196. package/dist/commands/extensions.js +64 -14
  197. package/dist/commands/extensions.js.map +1 -1
  198. package/dist/commands/login.d.ts +2 -1
  199. package/dist/commands/login.d.ts.map +1 -1
  200. package/dist/commands/login.js +40 -5
  201. package/dist/commands/login.js.map +1 -1
  202. package/dist/commands/manifest.d.ts +37 -0
  203. package/dist/commands/manifest.d.ts.map +1 -0
  204. package/dist/commands/manifest.js +151 -0
  205. package/dist/commands/manifest.js.map +1 -0
  206. package/dist/commands/model.d.ts +2 -3
  207. package/dist/commands/model.d.ts.map +1 -1
  208. package/dist/commands/model.js +63 -24
  209. package/dist/commands/model.js.map +1 -1
  210. package/dist/commands/new.d.ts.map +1 -1
  211. package/dist/commands/new.js +15 -3
  212. package/dist/commands/new.js.map +1 -1
  213. package/dist/commands/registry.d.ts +2 -2
  214. package/dist/commands/registry.d.ts.map +1 -1
  215. package/dist/commands/registry.js +1 -2
  216. package/dist/commands/registry.js.map +1 -1
  217. package/dist/commands/sandbox.d.ts.map +1 -1
  218. package/dist/commands/sandbox.js +83 -33
  219. package/dist/commands/sandbox.js.map +1 -1
  220. package/dist/commands/session-view.d.ts.map +1 -1
  221. package/dist/commands/session-view.js +6 -3
  222. package/dist/commands/session-view.js.map +1 -1
  223. package/dist/commands/types.d.ts +79 -7
  224. package/dist/commands/types.d.ts.map +1 -1
  225. package/dist/commands/types.js.map +1 -1
  226. package/dist/config.d.ts +23 -8
  227. package/dist/config.d.ts.map +1 -1
  228. package/dist/config.js +222 -135
  229. package/dist/config.js.map +1 -1
  230. package/dist/env-manifest.d.ts +30 -0
  231. package/dist/env-manifest.d.ts.map +1 -0
  232. package/dist/env-manifest.js +233 -0
  233. package/dist/env-manifest.js.map +1 -0
  234. package/dist/events.d.ts.map +1 -1
  235. package/dist/events.js +20 -81
  236. package/dist/events.js.map +1 -1
  237. package/dist/execution-resolver.d.ts +7 -8
  238. package/dist/execution-resolver.d.ts.map +1 -1
  239. package/dist/execution-resolver.js +106 -110
  240. package/dist/execution-resolver.js.map +1 -1
  241. package/dist/extension-schedules.d.ts +31 -0
  242. package/dist/extension-schedules.d.ts.map +1 -0
  243. package/dist/extension-schedules.js +229 -0
  244. package/dist/extension-schedules.js.map +1 -0
  245. package/dist/harness/auth.d.ts +2 -1
  246. package/dist/harness/auth.d.ts.map +1 -1
  247. package/dist/harness/auth.js +11 -5
  248. package/dist/harness/auth.js.map +1 -1
  249. package/dist/harness/event-format.d.ts +27 -0
  250. package/dist/harness/event-format.d.ts.map +1 -0
  251. package/dist/harness/event-format.js +160 -0
  252. package/dist/harness/event-format.js.map +1 -0
  253. package/dist/harness/extensions/loader.d.ts +27 -49
  254. package/dist/harness/extensions/loader.d.ts.map +1 -1
  255. package/dist/harness/extensions/loader.js +486 -130
  256. package/dist/harness/extensions/loader.js.map +1 -1
  257. package/dist/harness/extensions/registry.d.ts +107 -2
  258. package/dist/harness/extensions/registry.d.ts.map +1 -1
  259. package/dist/harness/extensions/registry.js +316 -0
  260. package/dist/harness/extensions/registry.js.map +1 -1
  261. package/dist/harness/extensions/types.d.ts +482 -42
  262. package/dist/harness/extensions/types.d.ts.map +1 -1
  263. package/dist/harness/extensions/types.js.map +1 -1
  264. package/dist/harness/index.d.ts +18 -8
  265. package/dist/harness/index.d.ts.map +1 -1
  266. package/dist/harness/index.js +7 -4
  267. package/dist/harness/index.js.map +1 -1
  268. package/dist/harness/models.d.ts +7 -4
  269. package/dist/harness/models.d.ts.map +1 -1
  270. package/dist/harness/models.js +46 -24
  271. package/dist/harness/models.js.map +1 -1
  272. package/dist/harness/runner.d.ts +50 -54
  273. package/dist/harness/runner.d.ts.map +1 -1
  274. package/dist/harness/runner.js +232 -46
  275. package/dist/harness/runner.js.map +1 -1
  276. package/dist/harness/session-store.d.ts +11 -5
  277. package/dist/harness/session-store.d.ts.map +1 -1
  278. package/dist/harness/session-store.js +141 -36
  279. package/dist/harness/session-store.js.map +1 -1
  280. package/dist/harness/settings.d.ts +2 -20
  281. package/dist/harness/settings.d.ts.map +1 -1
  282. package/dist/harness/settings.js +12 -1
  283. package/dist/harness/settings.js.map +1 -1
  284. package/dist/harness/skills.d.ts +10 -29
  285. package/dist/harness/skills.d.ts.map +1 -1
  286. package/dist/harness/skills.js +48 -6
  287. package/dist/harness/skills.js.map +1 -1
  288. package/dist/harness/subagent-profiles.d.ts +8 -0
  289. package/dist/harness/subagent-profiles.d.ts.map +1 -0
  290. package/dist/harness/subagent-profiles.js +299 -0
  291. package/dist/harness/subagent-profiles.js.map +1 -0
  292. package/dist/harness/subagent-runner.d.ts +45 -0
  293. package/dist/harness/subagent-runner.d.ts.map +1 -0
  294. package/dist/harness/subagent-runner.js +680 -0
  295. package/dist/harness/subagent-runner.js.map +1 -0
  296. package/dist/harness/types.d.ts +267 -1
  297. package/dist/harness/types.d.ts.map +1 -1
  298. package/dist/harness/types.js.map +1 -1
  299. package/dist/harness/usage.d.ts +6 -0
  300. package/dist/harness/usage.d.ts.map +1 -0
  301. package/dist/harness/usage.js +32 -0
  302. package/dist/harness/usage.js.map +1 -0
  303. package/dist/index.d.ts +36 -8
  304. package/dist/index.d.ts.map +1 -1
  305. package/dist/index.js +22 -6
  306. package/dist/index.js.map +1 -1
  307. package/dist/main.js +391 -127
  308. package/dist/main.js.map +1 -1
  309. package/dist/observability/instrument.js +7 -5
  310. package/dist/observability/instrument.js.map +1 -1
  311. package/dist/observability/sentry.d.ts +2 -0
  312. package/dist/observability/sentry.d.ts.map +1 -1
  313. package/dist/observability/sentry.js +10 -2
  314. package/dist/observability/sentry.js.map +1 -1
  315. package/dist/office/address.d.ts +31 -0
  316. package/dist/office/address.d.ts.map +1 -0
  317. package/dist/office/address.js +113 -0
  318. package/dist/office/address.js.map +1 -0
  319. package/dist/office/index.d.ts +11 -0
  320. package/dist/office/index.d.ts.map +1 -0
  321. package/dist/office/index.js +11 -0
  322. package/dist/office/index.js.map +1 -0
  323. package/dist/office/layout.d.ts +27 -0
  324. package/dist/office/layout.d.ts.map +1 -0
  325. package/dist/office/layout.js +97 -0
  326. package/dist/office/layout.js.map +1 -0
  327. package/dist/office/migration.d.ts +44 -0
  328. package/dist/office/migration.d.ts.map +1 -0
  329. package/dist/office/migration.js +255 -0
  330. package/dist/office/migration.js.map +1 -0
  331. package/dist/office/registry.d.ts +75 -0
  332. package/dist/office/registry.d.ts.map +1 -0
  333. package/dist/office/registry.js +646 -0
  334. package/dist/office/registry.js.map +1 -0
  335. package/dist/office/types.d.ts +71 -0
  336. package/dist/office/types.d.ts.map +1 -0
  337. package/dist/office/types.js +2 -0
  338. package/dist/office/types.js.map +1 -0
  339. package/dist/packages/admin.d.ts +17 -0
  340. package/dist/packages/admin.d.ts.map +1 -0
  341. package/dist/packages/admin.js +98 -0
  342. package/dist/packages/admin.js.map +1 -0
  343. package/dist/packages/index.d.ts +6 -0
  344. package/dist/packages/index.d.ts.map +1 -0
  345. package/dist/packages/index.js +6 -0
  346. package/dist/packages/index.js.map +1 -0
  347. package/dist/packages/inspect.d.ts +11 -0
  348. package/dist/packages/inspect.d.ts.map +1 -0
  349. package/dist/packages/inspect.js +98 -0
  350. package/dist/packages/inspect.js.map +1 -0
  351. package/dist/packages/materialize.d.ts +40 -0
  352. package/dist/packages/materialize.d.ts.map +1 -0
  353. package/dist/packages/materialize.js +297 -0
  354. package/dist/packages/materialize.js.map +1 -0
  355. package/dist/packages/resolve.d.ts +25 -0
  356. package/dist/packages/resolve.d.ts.map +1 -0
  357. package/dist/packages/resolve.js +208 -0
  358. package/dist/packages/resolve.js.map +1 -0
  359. package/dist/packages/source.d.ts +39 -0
  360. package/dist/packages/source.d.ts.map +1 -0
  361. package/dist/packages/source.js +189 -0
  362. package/dist/packages/source.js.map +1 -0
  363. package/dist/packages/types.d.ts +104 -0
  364. package/dist/packages/types.d.ts.map +1 -0
  365. package/dist/packages/types.js +2 -0
  366. package/dist/packages/types.js.map +1 -0
  367. package/dist/provisioner.d.ts +66 -0
  368. package/dist/provisioner.d.ts.map +1 -1
  369. package/dist/provisioner.js +335 -13
  370. package/dist/provisioner.js.map +1 -1
  371. package/dist/runtime/conversation-runtime.d.ts +1 -1
  372. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  373. package/dist/runtime/conversation-runtime.js +562 -118
  374. package/dist/runtime/conversation-runtime.js.map +1 -1
  375. package/dist/runtime/session-lifecycle.d.ts +38 -0
  376. package/dist/runtime/session-lifecycle.d.ts.map +1 -0
  377. package/dist/runtime/session-lifecycle.js +193 -0
  378. package/dist/runtime/session-lifecycle.js.map +1 -0
  379. package/dist/runtime/types.d.ts +66 -7
  380. package/dist/runtime/types.d.ts.map +1 -1
  381. package/dist/runtime/types.js.map +1 -1
  382. package/dist/sandbox/cloudflare.d.ts +3 -0
  383. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  384. package/dist/sandbox/cloudflare.js +13 -1
  385. package/dist/sandbox/cloudflare.js.map +1 -1
  386. package/dist/sandbox/container.d.ts +4 -0
  387. package/dist/sandbox/container.d.ts.map +1 -1
  388. package/dist/sandbox/container.js +13 -3
  389. package/dist/sandbox/container.js.map +1 -1
  390. package/dist/sandbox/firecracker.d.ts +3 -0
  391. package/dist/sandbox/firecracker.d.ts.map +1 -1
  392. package/dist/sandbox/firecracker.js +13 -2
  393. package/dist/sandbox/firecracker.js.map +1 -1
  394. package/dist/sandbox/gondolin.d.ts +57 -0
  395. package/dist/sandbox/gondolin.d.ts.map +1 -0
  396. package/dist/sandbox/gondolin.js +764 -0
  397. package/dist/sandbox/gondolin.js.map +1 -0
  398. package/dist/sandbox/host.d.ts +3 -0
  399. package/dist/sandbox/host.d.ts.map +1 -1
  400. package/dist/sandbox/host.js +18 -3
  401. package/dist/sandbox/host.js.map +1 -1
  402. package/dist/sandbox/identity.d.ts +44 -0
  403. package/dist/sandbox/identity.d.ts.map +1 -0
  404. package/dist/sandbox/identity.js +68 -0
  405. package/dist/sandbox/identity.js.map +1 -0
  406. package/dist/sandbox/image.d.ts.map +1 -1
  407. package/dist/sandbox/image.js +2 -0
  408. package/dist/sandbox/image.js.map +1 -1
  409. package/dist/sandbox/index.d.ts +17 -4
  410. package/dist/sandbox/index.d.ts.map +1 -1
  411. package/dist/sandbox/index.js +37 -3
  412. package/dist/sandbox/index.js.map +1 -1
  413. package/dist/sandbox/types.d.ts +65 -1
  414. package/dist/sandbox/types.d.ts.map +1 -1
  415. package/dist/sandbox/types.js.map +1 -1
  416. package/dist/sandbox/utils.d.ts +14 -0
  417. package/dist/sandbox/utils.d.ts.map +1 -1
  418. package/dist/sandbox/utils.js +68 -1
  419. package/dist/sandbox/utils.js.map +1 -1
  420. package/dist/sessions/chat-history-sync.d.ts +25 -0
  421. package/dist/sessions/chat-history-sync.d.ts.map +1 -0
  422. package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +135 -131
  423. package/dist/sessions/chat-history-sync.js.map +1 -0
  424. package/dist/sessions/conversation-log.d.ts +9 -0
  425. package/dist/sessions/conversation-log.d.ts.map +1 -0
  426. package/dist/sessions/conversation-log.js +50 -0
  427. package/dist/sessions/conversation-log.js.map +1 -0
  428. package/dist/sessions/history-line.d.ts +29 -0
  429. package/dist/sessions/history-line.d.ts.map +1 -0
  430. package/dist/sessions/history-line.js +45 -0
  431. package/dist/sessions/history-line.js.map +1 -0
  432. package/dist/sessions/rotation.d.ts +2 -0
  433. package/dist/sessions/rotation.d.ts.map +1 -0
  434. package/dist/sessions/rotation.js +39 -0
  435. package/dist/sessions/rotation.js.map +1 -0
  436. package/dist/sessions/session-key.d.ts +33 -0
  437. package/dist/sessions/session-key.d.ts.map +1 -0
  438. package/dist/sessions/session-key.js +115 -0
  439. package/dist/sessions/session-key.js.map +1 -0
  440. package/dist/sessions/store.d.ts +15 -7
  441. package/dist/sessions/store.d.ts.map +1 -1
  442. package/dist/sessions/store.js +127 -36
  443. package/dist/sessions/store.js.map +1 -1
  444. package/dist/sessions/types.d.ts +19 -5
  445. package/dist/sessions/types.d.ts.map +1 -1
  446. package/dist/sessions/types.js.map +1 -1
  447. package/dist/settings-mutation.d.ts +40 -0
  448. package/dist/settings-mutation.d.ts.map +1 -0
  449. package/dist/settings-mutation.js +61 -0
  450. package/dist/settings-mutation.js.map +1 -0
  451. package/dist/subagent-progress.d.ts +39 -0
  452. package/dist/subagent-progress.d.ts.map +1 -0
  453. package/dist/subagent-progress.js +225 -0
  454. package/dist/subagent-progress.js.map +1 -0
  455. package/dist/tools/bash.d.ts +3 -1
  456. package/dist/tools/bash.d.ts.map +1 -1
  457. package/dist/tools/bash.js +26 -18
  458. package/dist/tools/bash.js.map +1 -1
  459. package/dist/tools/edit.d.ts.map +1 -1
  460. package/dist/tools/edit.js +14 -13
  461. package/dist/tools/edit.js.map +1 -1
  462. package/dist/tools/event.d.ts +1 -1
  463. package/dist/tools/event.d.ts.map +1 -1
  464. package/dist/tools/event.js +58 -45
  465. package/dist/tools/event.js.map +1 -1
  466. package/dist/tools/generate-image.d.ts +23 -0
  467. package/dist/tools/generate-image.d.ts.map +1 -0
  468. package/dist/tools/generate-image.js +65 -0
  469. package/dist/tools/generate-image.js.map +1 -0
  470. package/dist/tools/host-fn-tool.d.ts +21 -0
  471. package/dist/tools/host-fn-tool.d.ts.map +1 -0
  472. package/dist/tools/host-fn-tool.js +32 -0
  473. package/dist/tools/host-fn-tool.js.map +1 -0
  474. package/dist/tools/index.d.ts +19 -2
  475. package/dist/tools/index.d.ts.map +1 -1
  476. package/dist/tools/index.js +25 -2
  477. package/dist/tools/index.js.map +1 -1
  478. package/dist/tools/react.d.ts +15 -0
  479. package/dist/tools/react.d.ts.map +1 -0
  480. package/dist/tools/react.js +29 -0
  481. package/dist/tools/react.js.map +1 -0
  482. package/dist/tools/read.d.ts +1 -0
  483. package/dist/tools/read.d.ts.map +1 -1
  484. package/dist/tools/read.js +39 -33
  485. package/dist/tools/read.js.map +1 -1
  486. package/dist/tools/sandbox.d.ts +2 -2
  487. package/dist/tools/sandbox.d.ts.map +1 -1
  488. package/dist/tools/sandbox.js +14 -9
  489. package/dist/tools/sandbox.js.map +1 -1
  490. package/dist/tools/subagent-slots.d.ts +28 -0
  491. package/dist/tools/subagent-slots.d.ts.map +1 -0
  492. package/dist/tools/subagent-slots.js +72 -0
  493. package/dist/tools/subagent-slots.js.map +1 -0
  494. package/dist/tools/subagent.d.ts +61 -0
  495. package/dist/tools/subagent.d.ts.map +1 -0
  496. package/dist/tools/subagent.js +434 -0
  497. package/dist/tools/subagent.js.map +1 -0
  498. package/dist/tools/types.d.ts +40 -32
  499. package/dist/tools/types.d.ts.map +1 -1
  500. package/dist/tools/types.js.map +1 -1
  501. package/dist/tools/write.d.ts.map +1 -1
  502. package/dist/tools/write.js +2 -8
  503. package/dist/tools/write.js.map +1 -1
  504. package/dist/trigger.d.ts +2 -1
  505. package/dist/trigger.d.ts.map +1 -1
  506. package/dist/trigger.js +6 -9
  507. package/dist/trigger.js.map +1 -1
  508. package/dist/types.d.ts +411 -59
  509. package/dist/types.d.ts.map +1 -1
  510. package/dist/types.js +2 -2
  511. package/dist/types.js.map +1 -1
  512. package/dist/utils/file-guards.d.ts +9 -0
  513. package/dist/utils/file-guards.d.ts.map +1 -1
  514. package/dist/utils/file-guards.js +44 -1
  515. package/dist/utils/file-guards.js.map +1 -1
  516. package/dist/vault/disabled.d.ts +9 -0
  517. package/dist/vault/disabled.d.ts.map +1 -0
  518. package/dist/vault/disabled.js +25 -0
  519. package/dist/vault/disabled.js.map +1 -0
  520. package/dist/vault/index.d.ts +45 -2
  521. package/dist/vault/index.d.ts.map +1 -1
  522. package/dist/vault/index.js +127 -36
  523. package/dist/vault/index.js.map +1 -1
  524. package/dist/vault/types.d.ts +6 -2
  525. package/dist/vault/types.d.ts.map +1 -1
  526. package/dist/vault/types.js.map +1 -1
  527. package/dist/web/admin/portal.d.ts +31 -3
  528. package/dist/web/admin/portal.d.ts.map +1 -1
  529. package/dist/web/admin/portal.js +850 -345
  530. package/dist/web/admin/portal.js.map +1 -1
  531. package/dist/web/admin/provider-models.js +1 -1
  532. package/dist/web/admin/provider-models.js.map +1 -1
  533. package/dist/web/admin/types.d.ts +23 -3
  534. package/dist/web/admin/types.d.ts.map +1 -1
  535. package/dist/web/admin/types.js.map +1 -1
  536. package/dist/web/login/oauth.d.ts +2 -3
  537. package/dist/web/login/oauth.d.ts.map +1 -1
  538. package/dist/web/login/oauth.js +1 -32
  539. package/dist/web/login/oauth.js.map +1 -1
  540. package/dist/web/login/portal.d.ts +1 -1
  541. package/dist/web/login/portal.d.ts.map +1 -1
  542. package/dist/web/login/portal.js +13 -36
  543. package/dist/web/login/portal.js.map +1 -1
  544. package/dist/web/login/types.d.ts +0 -11
  545. package/dist/web/login/types.d.ts.map +1 -1
  546. package/dist/web/login/types.js.map +1 -1
  547. package/dist/web/portal-shell.d.ts +21 -0
  548. package/dist/web/portal-shell.d.ts.map +1 -0
  549. package/dist/{portal-shell.js → web/portal-shell.js} +67 -2
  550. package/dist/web/portal-shell.js.map +1 -0
  551. package/dist/web/server.d.ts +3 -2
  552. package/dist/web/server.d.ts.map +1 -1
  553. package/dist/web/server.js +11 -4
  554. package/dist/web/server.js.map +1 -1
  555. package/dist/web/session-view/portal.d.ts +1 -1
  556. package/dist/web/session-view/portal.d.ts.map +1 -1
  557. package/dist/web/session-view/portal.js +37 -28
  558. package/dist/web/session-view/portal.js.map +1 -1
  559. package/dist/web/session-view/service.d.ts +1 -1
  560. package/dist/web/session-view/service.d.ts.map +1 -1
  561. package/dist/web/session-view/service.js +61 -18
  562. package/dist/web/session-view/service.js.map +1 -1
  563. package/dist/web/session-view/types.d.ts +0 -3
  564. package/dist/web/session-view/types.d.ts.map +1 -1
  565. package/dist/web/session-view/types.js.map +1 -1
  566. package/dist/web/token-store.js +1 -1
  567. package/dist/web/token-store.js.map +1 -1
  568. package/dist/workspace-projection/index.d.ts +10 -0
  569. package/dist/workspace-projection/index.d.ts.map +1 -0
  570. package/dist/workspace-projection/index.js +120 -0
  571. package/dist/workspace-projection/index.js.map +1 -0
  572. package/dist/workspace-projection/types.d.ts +16 -0
  573. package/dist/workspace-projection/types.d.ts.map +1 -0
  574. package/dist/workspace-projection/types.js +2 -0
  575. package/dist/workspace-projection/types.js.map +1 -0
  576. package/package.json +68 -37
  577. package/dist/adapters/streaming.d.ts +0 -18
  578. package/dist/adapters/streaming.d.ts.map +0 -1
  579. package/dist/adapters/streaming.js +0 -44
  580. package/dist/adapters/streaming.js.map +0 -1
  581. package/dist/adapters/telegram/html.d.ts +0 -3
  582. package/dist/adapters/telegram/html.d.ts.map +0 -1
  583. package/dist/adapters/telegram/html.js +0 -98
  584. package/dist/adapters/telegram/html.js.map +0 -1
  585. package/dist/commands/parse.d.ts +0 -7
  586. package/dist/commands/parse.d.ts.map +0 -1
  587. package/dist/commands/parse.js +0 -14
  588. package/dist/commands/parse.js.map +0 -1
  589. package/dist/download.d.ts.map +0 -1
  590. package/dist/download.js.map +0 -1
  591. package/dist/model-registry.d.ts +0 -4
  592. package/dist/model-registry.d.ts.map +0 -1
  593. package/dist/model-registry.js +0 -7
  594. package/dist/model-registry.js.map +0 -1
  595. package/dist/portal-shell.d.ts +0 -4
  596. package/dist/portal-shell.d.ts.map +0 -1
  597. package/dist/portal-shell.js.map +0 -1
  598. package/dist/runtime/agent-run-controller.d.ts +0 -31
  599. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  600. package/dist/runtime/agent-run-controller.js +0 -195
  601. package/dist/runtime/agent-run-controller.js.map +0 -1
  602. package/dist/sandbox/path-context.d.ts +0 -3
  603. package/dist/sandbox/path-context.d.ts.map +0 -1
  604. package/dist/sandbox/path-context.js +0 -20
  605. package/dist/sandbox/path-context.js.map +0 -1
  606. package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
  607. package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
  608. package/dist/sessions/agent-memory-file-manager.js.map +0 -1
  609. package/dist/sessions/metadata.d.ts +0 -3
  610. package/dist/sessions/metadata.d.ts.map +0 -1
  611. package/dist/sessions/metadata.js +0 -11
  612. package/dist/sessions/metadata.js.map +0 -1
  613. package/dist/sessions/policy.d.ts +0 -6
  614. package/dist/sessions/policy.d.ts.map +0 -1
  615. package/dist/sessions/policy.js +0 -23
  616. package/dist/sessions/policy.js.map +0 -1
  617. package/dist/store.d.ts +0 -46
  618. package/dist/store.d.ts.map +0 -1
  619. package/dist/store.js +0 -177
  620. package/dist/store.js.map +0 -1
  621. package/dist/tools/truncate.d.ts +0 -33
  622. package/dist/tools/truncate.d.ts.map +0 -1
  623. package/dist/tools/truncate.js +0 -184
  624. package/dist/tools/truncate.js.map +0 -1
  625. package/dist/utils/date.d.ts +0 -10
  626. package/dist/utils/date.d.ts.map +0 -1
  627. package/dist/utils/date.js +0 -23
  628. package/dist/utils/date.js.map +0 -1
  629. package/dist/utils/env.d.ts +0 -3
  630. package/dist/utils/env.d.ts.map +0 -1
  631. package/dist/utils/env.js +0 -12
  632. package/dist/utils/env.js.map +0 -1
  633. package/dist/utils/fs-atomic.d.ts +0 -10
  634. package/dist/utils/fs-atomic.d.ts.map +0 -1
  635. package/dist/utils/fs-atomic.js +0 -45
  636. package/dist/utils/fs-atomic.js.map +0 -1
  637. package/dist/utils/html.d.ts +0 -2
  638. package/dist/utils/html.d.ts.map +0 -1
  639. package/dist/utils/html.js +0 -4
  640. package/dist/utils/html.js.map +0 -1
  641. package/dist/utils/http-body.d.ts +0 -10
  642. package/dist/utils/http-body.d.ts.map +0 -1
  643. package/dist/utils/http-body.js +0 -34
  644. package/dist/utils/http-body.js.map +0 -1
  645. package/dist/vault/routing.d.ts +0 -3
  646. package/dist/vault/routing.d.ts.map +0 -1
  647. package/dist/vault/routing.js +0 -13
  648. package/dist/vault/routing.js.map +0 -1
  649. package/dist/web/agent-events/portal.d.ts +0 -3
  650. package/dist/web/agent-events/portal.d.ts.map +0 -1
  651. package/dist/web/agent-events/portal.js +0 -18
  652. package/dist/web/agent-events/portal.js.map +0 -1
  653. package/dist/web/request.d.ts +0 -3
  654. package/dist/web/request.d.ts.map +0 -1
  655. package/dist/web/request.js +0 -13
  656. package/dist/web/request.js.map +0 -1
  657. package/dist/web/session-view/command.d.ts +0 -4
  658. package/dist/web/session-view/command.d.ts.map +0 -1
  659. package/dist/web/session-view/command.js +0 -7
  660. package/dist/web/session-view/command.js.map +0 -1
  661. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
@@ -1,22 +1,49 @@
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";
2
+ import { WebAPIRateLimitedError, WebClient } from "@slack/web-api";
3
+ import { readEnv } from "../../env-manifest.js";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { readFile, writeFile } from "node:fs/promises";
6
+ import { basename, join } from "node:path";
7
+ import { createConversationEvent, createConversationMessage, } from "../../adapter.js";
8
+ import { createOfficeAddress } from "../../office/index.js";
9
+ import { COMMAND_MANIFEST } from "../../commands/manifest.js";
6
10
  import { resolveConversationSettings } from "../../config.js";
11
+ import { parseExtActionId } from "../../harness/extensions/registry.js";
7
12
  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";
13
+ import { isRecord, readTextFileIfExists } from "../../utils/file-guards.js";
14
+ import { PRODUCT_NAME, formatForceStopped } from "../../platform-messages.js";
15
+ import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, saveIncomingAttachments, withRetry, } from "../shared.js";
10
16
  import { processMessageIntake } from "../intake.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
22
  import { reportUserFacingError } from "../../observability/sentry.js";
15
- import { renderSlackBlocks } from "./blocks.js";
23
+ import { renderSlackBlocks, resolveSlackMentions } from "./blocks.js";
24
+ import { StreamStartLimiter } from "./stream-limits.js";
16
25
  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.
26
+ /**
27
+ * Slack's per-call limit on `markdown_text`: "Limit this field to 12,000
28
+ * characters." mikan allows far more in a message, so streamed text is split
29
+ * to fit rather than truncated.
30
+ */
31
+ const MAX_STREAM_TEXT_CHARS = 12_000;
32
+ /** Slices `text` into pieces that each fit one streaming call. */
33
+ export function chunkStreamText(text, limit = MAX_STREAM_TEXT_CHARS) {
34
+ if (text.length <= limit)
35
+ return [text];
36
+ const chunks = [];
37
+ for (let index = 0; index < text.length; index += limit) {
38
+ chunks.push(text.slice(index, index + limit));
39
+ }
40
+ return chunks;
41
+ }
42
+ // web-api v8 throws WebAPIRateLimitedError; the duck-typed shapes cover
43
+ // platform `data.error === "rate_limited"` / 429 responses and older callers.
19
44
  function slackIsRateLimited(err) {
45
+ if (err instanceof WebAPIRateLimitedError)
46
+ return true;
20
47
  if (err.code === "rate_limited")
21
48
  return true;
22
49
  const data = err.data;
@@ -72,11 +99,28 @@ export function buildMrkdwnContextBlock(text) {
72
99
  // ============================================================================
73
100
  // SlackMessagingBot
74
101
  // ============================================================================
102
+ class AttachmentDownloadHttpError extends Error {
103
+ constructor(message, status) {
104
+ super(message);
105
+ this.status = status;
106
+ }
107
+ }
108
+ function isRetryableAttachmentDownloadError(error) {
109
+ if (!(error instanceof AttachmentDownloadHttpError))
110
+ return true;
111
+ return error.status === 408 || error.status === 429 || error.status >= 500;
112
+ }
75
113
  export class SlackMessagingBot {
114
+ /** Host office dir for a Slack conversation. */
115
+ conversationDir(channelId) {
116
+ return this.workspace.office(createOfficeAddress("slack", channelId)).dir;
117
+ }
118
+ resolveReplyMode(address) {
119
+ return (resolveConversationSettings(this.workspace.office(address)).slack?.replyMode ?? "top-level");
120
+ }
76
121
  createContext(event) {
77
122
  return createSlackAdapters(event, this, {
78
- replyMode: resolveConversationSettings(join(this.workingDir, event.conversationId)).slack?.replyMode ??
79
- "top-level",
123
+ replyMode: this.resolveReplyMode(event.address),
80
124
  });
81
125
  }
82
126
  constructor(handler, config) {
@@ -87,11 +131,20 @@ export class SlackMessagingBot {
87
131
  this.startupTs = null; // Messages older than this are just logged, not processed
88
132
  this.users = new Map();
89
133
  this.channels = new Map();
134
+ /** Which threads belong to the assistant pane, and their last known context. */
135
+ this.assistantThreads = new AssistantThreadRegistry();
136
+ /**
137
+ * Native response streams opened recently. Per bot rather than per module:
138
+ * the rate tier applies per workspace, and this bot is the connection to
139
+ * one — which also means a test constructing a bot gets a fresh budget
140
+ * instead of inheriting whatever ran before it.
141
+ */
142
+ this.streamStarts = new StreamStartLimiter();
90
143
  this.queues = new Map();
91
144
  this.eventsWatcher = null;
92
145
  this.handler = handler;
93
- this.workingDir = config.workingDir;
94
- this.store = config.store;
146
+ this.workspace = config.workspace;
147
+ this.botToken = config.botToken;
95
148
  this.socketClient = new SocketModeClient({
96
149
  appToken: config.appToken,
97
150
  // Default 5s is too tight: brief event-loop stalls (e.g. backfill, sync fs)
@@ -145,13 +198,36 @@ export class SlackMessagingBot {
145
198
  }
146
199
  return source.replace(this.ownMentionRegex, "").trim();
147
200
  }
201
+ /**
202
+ * Response-source `<@userName>` mentions become native `<@U…>` here — the
203
+ * one conversion every outgoing response path funnels through. Slack only
204
+ * links and notifies on raw user ids.
205
+ */
206
+ resolveMentions(text) {
207
+ return resolveSlackMentions(text, this.users.values());
208
+ }
148
209
  async postMessage(channel, text) {
149
210
  return slackRetry(async () => {
150
- const payload = { channel, ...renderSlackBlocks(text) };
211
+ const payload = { channel, ...renderSlackBlocks(this.resolveMentions(text)) };
151
212
  const result = await this.webClient.chat.postMessage(payload);
152
213
  return result.ts;
153
214
  });
154
215
  }
216
+ async addReaction(channel, messageTs, emoji) {
217
+ // Slack reaction names are colon-free short names; strip any wrapping colons.
218
+ const name = emoji.replace(/^:|:$/g, "");
219
+ await slackRetry(async () => {
220
+ try {
221
+ await this.webClient.reactions.add({ channel, timestamp: messageTs, name });
222
+ }
223
+ catch (err) {
224
+ // Re-reacting with the same emoji is not an error worth surfacing.
225
+ if (err?.data?.error === "already_reacted")
226
+ return;
227
+ throw err;
228
+ }
229
+ });
230
+ }
155
231
  async postEphemeral(channel, user, text, threadTs) {
156
232
  return slackRetry(async () => {
157
233
  await this.webClient.chat.postEphemeral({
@@ -180,6 +256,11 @@ export class SlackMessagingBot {
180
256
  return result.ts;
181
257
  });
182
258
  }
259
+ async updateMessageBlocks(channel, ts, text, blocks) {
260
+ return slackRetry(async () => {
261
+ await this.webClient.chat.update({ channel, ts, text, blocks: blocks });
262
+ });
263
+ }
183
264
  async postPrivate(conversationId, userId, text) {
184
265
  await this.postEphemeral(conversationId, userId, text);
185
266
  }
@@ -190,7 +271,7 @@ export class SlackMessagingBot {
190
271
  }
191
272
  await this.postEphemeralBlocks(conversationId, userId, text, [buildMrkdwnContextBlock(text)]);
192
273
  }
193
- async openDirectMessage(userId) {
274
+ async openDirectConversation(userId) {
194
275
  return slackRetry(async () => {
195
276
  const result = await this.webClient.conversations.open({ users: userId });
196
277
  const channelId = result.channel?.id;
@@ -200,33 +281,127 @@ export class SlackMessagingBot {
200
281
  return channelId;
201
282
  });
202
283
  }
284
+ /**
285
+ * Fetch recent messages from a channel, oldest first. Without `threadTs`
286
+ * this is one `conversations.history` page of top-level messages (thread
287
+ * replies are not expanded); with it, one `conversations.replies` page of
288
+ * that thread's replies, excluding the parent. Callers page forward by
289
+ * passing the last returned `ts` as `oldest`.
290
+ */
291
+ async fetchHistory(channel, options) {
292
+ const limit = Math.min(Math.max(options?.limit ?? 200, 1), 999);
293
+ const threadTs = options?.threadTs;
294
+ return slackRetry(async () => {
295
+ const result = threadTs
296
+ ? await this.webClient.conversations.replies({
297
+ channel,
298
+ ts: threadTs,
299
+ ...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
300
+ limit,
301
+ })
302
+ : await this.webClient.conversations.history({
303
+ channel,
304
+ ...(options?.oldest ? { oldest: options.oldest, inclusive: false } : {}),
305
+ limit,
306
+ });
307
+ const messages = (result.messages ?? []);
308
+ const mapped = messages
309
+ .filter((msg) => !!msg.ts)
310
+ // conversations.replies leads with the thread parent; the caller asked
311
+ // for the replies to it, and including it would re-ingest the bot's
312
+ // own message on every poll.
313
+ .filter((msg) => msg.ts !== threadTs)
314
+ .map((msg) => {
315
+ const user = msg.user ? this.users.get(msg.user) : undefined;
316
+ const message = {
317
+ ts: msg.ts,
318
+ text: msg.text ?? "",
319
+ isBot: !!msg.bot_id || msg.subtype === "bot_message" || user?.isBot === true,
320
+ };
321
+ if (msg.thread_ts && msg.thread_ts !== msg.ts)
322
+ message.threadTs = msg.thread_ts;
323
+ if (msg.user)
324
+ message.userId = msg.user;
325
+ if (user)
326
+ message.userName = user.userName;
327
+ return message;
328
+ });
329
+ // history returns newest-first, replies oldest-first; the contract is
330
+ // oldest-first either way.
331
+ return threadTs ? mapped : mapped.toReversed();
332
+ });
333
+ }
334
+ /** Refresh and list the workspace's active users (`users.list`). */
335
+ async listUsers() {
336
+ await slackRetry(() => this.fetchUsers());
337
+ return this.getAllUsers().map((user) => ({
338
+ id: user.id,
339
+ userName: user.userName,
340
+ displayName: user.displayName,
341
+ isBot: user.isBot === true,
342
+ }));
343
+ }
203
344
  async updateMessage(channel, ts, text) {
204
345
  return slackRetry(async () => {
205
- const payload = { channel, ts, ...renderSlackBlocks(text) };
346
+ const payload = { channel, ts, ...renderSlackBlocks(this.resolveMentions(text)) };
206
347
  await this.webClient.chat.update(payload);
207
348
  });
208
349
  }
350
+ /**
351
+ * Claim one native stream, or decline. Declining is not an error: the caller
352
+ * falls back to edit-based updates, which is a working answer rather than a
353
+ * degraded one, and costs no request against the tighter start tier.
354
+ */
355
+ tryReserveStreamStart() {
356
+ return this.streamStarts.tryReserve();
357
+ }
358
+ /**
359
+ * Open a stream, carrying text of any length.
360
+ *
361
+ * Slack caps `markdown_text` at 12,000 characters per call, and a long first
362
+ * render exceeds that easily — mikan allows 35,000 in a message. Rather than
363
+ * truncating (losing the answer) or failing (falling back to edit mode for
364
+ * the whole response), the opening call takes the first slice and the rest
365
+ * follows as appends before this resolves. The caller sees one operation
366
+ * that sent everything, so the renderer's idea of what has been streamed
367
+ * stays true.
368
+ *
369
+ * Splitting mid-word or mid-fence is harmless here: a stream concatenates,
370
+ * so Slack renders the reassembled text, not the pieces.
371
+ */
209
372
  async startMessageStream(channel, text, threadTs, recipientUserId) {
210
- return slackRetry(async () => {
373
+ const [head, ...tail] = chunkStreamText(text);
374
+ const ts = await slackRetry(async () => {
211
375
  const result = await this.webClient.apiCall("chat.startStream", {
212
376
  channel,
213
- markdown_text: text,
377
+ markdown_text: this.resolveMentions(head ?? ""),
214
378
  ...(threadTs ? { thread_ts: threadTs } : {}),
215
379
  ...(this.teamId ? { recipient_team_id: this.teamId } : {}),
216
380
  ...(recipientUserId ? { recipient_user_id: recipientUserId } : {}),
217
381
  });
218
- const ts = result.ts;
219
- if (!ts)
382
+ const streamTs = result.ts;
383
+ if (!streamTs)
220
384
  throw new Error("Slack chat.startStream did not return ts");
221
- return ts;
385
+ return streamTs;
222
386
  });
387
+ for (const part of tail)
388
+ await this.appendStreamChunk(channel, ts, part);
389
+ return ts;
223
390
  }
391
+ /** Append a delta of any length, split to the per-call cap. */
224
392
  async appendMessageStream(channel, ts, text) {
393
+ for (const part of chunkStreamText(text)) {
394
+ await this.appendStreamChunk(channel, ts, part);
395
+ }
396
+ }
397
+ async appendStreamChunk(channel, ts, text) {
225
398
  return slackRetry(async () => {
226
399
  await this.webClient.apiCall("chat.appendStream", {
227
400
  channel,
228
401
  ts,
229
- markdown_text: text,
402
+ // A mention split across stream deltas stays unresolved here; the
403
+ // canonical final render (updateMessage) resolves the full text.
404
+ markdown_text: this.resolveMentions(text),
230
405
  });
231
406
  });
232
407
  }
@@ -253,9 +428,76 @@ export class SlackMessagingBot {
253
428
  });
254
429
  });
255
430
  }
431
+ /** Offer starting points in a new assistant thread. */
432
+ async setAssistantSuggestedPrompts(channel, threadTs, prompts) {
433
+ return slackRetry(async () => {
434
+ // Omitting thread_ts pins the prompts to the DM itself, which is what
435
+ // the agent surface wants: there is no thread yet when it opens.
436
+ await this.webClient.assistant.threads.setSuggestedPrompts({
437
+ channel_id: channel,
438
+ ...(threadTs ? { thread_ts: threadTs } : {}),
439
+ prompts,
440
+ });
441
+ });
442
+ }
443
+ /** Name an assistant thread, which is what the sidebar conversation list shows. */
444
+ async setAssistantTitle(channel, threadTs, title) {
445
+ return slackRetry(async () => {
446
+ await this.webClient.assistant.threads.setTitle({
447
+ channel_id: channel,
448
+ thread_ts: threadTs,
449
+ title,
450
+ });
451
+ });
452
+ }
453
+ /** The assistant surface's view of this bot, handed to the lifecycle handlers. */
454
+ assistantOps() {
455
+ return {
456
+ postInThread: (channel, threadTs, text) => this.postInThread(channel, threadTs, text),
457
+ setSuggestedPrompts: (channel, threadTs, prompts) => this.setAssistantSuggestedPrompts(channel, threadTs, prompts),
458
+ setTitle: (channel, threadTs, title) => this.setAssistantTitle(channel, threadTs, title),
459
+ channelName: (channelId) => this.channels.get(channelId)?.name,
460
+ };
461
+ }
462
+ /**
463
+ * A new conversation opened in the assistant pane. Slack waits for the app
464
+ * to speak first here; staying silent is what makes the pane look broken.
465
+ */
466
+ handleAssistantThreadStarted({ event, ack }) {
467
+ ack();
468
+ const payload = event;
469
+ if (!payload.assistant_thread)
470
+ return;
471
+ void handleAssistantThreadStarted(this.assistantOps(), this.assistantThreads, payload.assistant_thread);
472
+ }
473
+ /**
474
+ * The person navigated to another channel while the pane is open. Slack
475
+ * sends this as `assistant_thread_context_changed` under `assistant_view`
476
+ * and `app_context_changed` under `agent_view`; the payload differs enough
477
+ * to normalize here rather than in the handler.
478
+ */
479
+ handleAgentContextChangedEvent({ event, ack, }) {
480
+ ack();
481
+ const payload = event;
482
+ if (payload.assistant_thread) {
483
+ handleAgentContextChanged(this.assistantThreads, payload.assistant_thread);
484
+ return;
485
+ }
486
+ const channelId = payload.channel_id ?? payload.channel;
487
+ if (channelId) {
488
+ handleAgentContextChanged(this.assistantThreads, {
489
+ channel_id: channelId,
490
+ context: payload.context,
491
+ });
492
+ }
493
+ }
256
494
  async postInThread(channel, threadTs, text) {
257
495
  return slackRetry(async () => {
258
- const payload = { channel, thread_ts: threadTs, ...renderSlackBlocks(text) };
496
+ const payload = {
497
+ channel,
498
+ thread_ts: threadTs,
499
+ ...renderSlackBlocks(this.resolveMentions(text)),
500
+ };
259
501
  const result = await this.webClient.chat.postMessage(payload);
260
502
  return result.ts;
261
503
  });
@@ -286,18 +528,39 @@ export class SlackMessagingBot {
286
528
  });
287
529
  }
288
530
  logToFile(channel, entry) {
289
- appendChannelLog(this.workingDir, channel, entry);
531
+ appendChannelLog(this.workspace.office(createOfficeAddress("slack", channel)), entry);
290
532
  }
291
533
  logBotResponse(channel, text, ts, threadTs, slackBlocks) {
292
- appendBotResponseLog(this.workingDir, channel, text, ts, threadTs, {
534
+ appendBotResponseLog(this.workspace.office(createOfficeAddress("slack", channel)), text, ts, threadTs, {
293
535
  platform: "slack",
294
536
  ...(slackBlocks ? { slackBlocks } : {}),
295
537
  });
296
538
  }
539
+ ownsBlockKitMessage(channel, ts, threadTs) {
540
+ const content = readTextFileIfExists(join(this.conversationDir(channel), "log.jsonl"));
541
+ if (content === undefined)
542
+ return false;
543
+ for (const line of content.trim().split("\n").toReversed()) {
544
+ try {
545
+ const entry = JSON.parse(line);
546
+ if (!isRecord(entry) || entry.ts !== ts)
547
+ continue;
548
+ return (entry.isMessagingBot === true &&
549
+ entry.platform === "slack" &&
550
+ Array.isArray(entry.slackBlocks) &&
551
+ entry.threadTs === threadTs);
552
+ }
553
+ catch {
554
+ continue;
555
+ }
556
+ }
557
+ return false;
558
+ }
297
559
  getMessagingInfo() {
298
560
  return {
299
561
  name: "slack",
300
- 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).",
562
+ trustModel: "membership",
563
+ 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.",
301
564
  channels: this.getAllChannels().map((c) => ({ id: c.id, name: c.name })),
302
565
  users: this.getAllUsers().map((u) => ({
303
566
  id: u.id,
@@ -352,7 +615,7 @@ export class SlackMessagingBot {
352
615
  const eventForRun = eventPlan.event;
353
616
  if (eventPlan.initialMessageTs && eventForRun.sessionKey) {
354
617
  registerThreadSession({
355
- conversationDir: join(this.workingDir, conversationId),
618
+ conversationDir: this.conversationDir(conversationId),
356
619
  sessionKey: eventForRun.sessionKey,
357
620
  });
358
621
  }
@@ -360,7 +623,9 @@ export class SlackMessagingBot {
360
623
  initialMessageTs: eventPlan.initialMessageTs,
361
624
  }).sessionKey;
362
625
  this.getQueue(runQueueKey).enqueue(async () => {
363
- const slackEvent = {
626
+ const slackEvent = createConversationEvent({
627
+ platform: "slack",
628
+ address: eventForRun.address,
364
629
  type: eventForRun.type,
365
630
  conversationId,
366
631
  conversationKind: eventForRun.conversationKind,
@@ -369,16 +634,12 @@ export class SlackMessagingBot {
369
634
  thread_ts: eventForRun.thread_ts,
370
635
  user: eventForRun.user,
371
636
  text: eventForRun.text,
372
- attachments: eventForRun.attachments?.map((attachment) => ({
373
- original: attachment.name,
374
- localPath: attachment.localPath,
375
- })),
637
+ attachments: eventForRun.attachments,
376
638
  sessionKey: eventForRun.sessionKey,
377
- };
639
+ });
378
640
  const context = createSlackAdapters(slackEvent, this, {
379
641
  initialMessageTs: eventPlan.initialMessageTs,
380
- replyMode: resolveConversationSettings(join(this.workingDir, eventForRun.conversationId)).slack
381
- ?.replyMode ?? "top-level",
642
+ replyMode: this.resolveReplyMode(eventForRun.address),
382
643
  });
383
644
  return this.handler.handleEvent(eventForRun, this, context);
384
645
  });
@@ -399,21 +660,29 @@ export class SlackMessagingBot {
399
660
  resolveQueueKey(conversationId, sessionKey) {
400
661
  if (!isSlackThreadSessionKey(sessionKey))
401
662
  return sessionKey;
402
- if (this.handler.isRunning(sessionKey))
663
+ if (this.handler.isRunning(createOfficeAddress("slack", conversationId), sessionKey)) {
403
664
  return sessionKey;
665
+ }
404
666
  return this.hasKnownThreadSession(conversationId, sessionKey) ? sessionKey : conversationId;
405
667
  }
406
668
  hasKnownThreadSession(conversationId, sessionKey) {
407
669
  return hasMaterializedChatSession({
408
- conversationDir: join(this.workingDir, conversationId),
670
+ conversationDir: this.conversationDir(conversationId),
409
671
  sessionKey,
410
672
  });
411
673
  }
412
674
  processSlackMessageIntake(options) {
413
- void processMessageIntake({
675
+ const absorbAttachmentFailure = () => {
676
+ void options.attachmentsPromise.catch((err) => {
677
+ log.logWarning("Failed to log Slack message", String(err));
678
+ });
679
+ };
680
+ processMessageIntake({
414
681
  eventBase: options.event,
415
- workingDir: this.workingDir,
682
+ office: this.workspace.office(options.event.address),
416
683
  isAutoReplyCandidate: options.isAutoReplyCandidate,
684
+ magicWord: { addressed: options.addressed, scopeFallback: "top-level" },
685
+ busyPolicy: "queue",
417
686
  logEntryBase: {},
418
687
  processAttachments: () => options.attachmentsPromise,
419
688
  queueKey: options.queueKey,
@@ -421,8 +690,15 @@ export class SlackMessagingBot {
421
690
  handler: this.handler,
422
691
  bot: this,
423
692
  createContext: (event) => this.createContext(event),
424
- onNotTriggered: options.onNotTriggered,
425
693
  deferAttachmentsUntilRun: true,
694
+ }).then((outcome) => {
695
+ // Slack logs eagerly via logUserMessage; when intake does not enqueue,
696
+ // nothing awaits the attachment download, so absorb its failure here.
697
+ if (outcome !== "enqueued")
698
+ absorbAttachmentFailure();
699
+ }, (err) => {
700
+ log.logWarning("Slack message intake failed", String(err));
701
+ absorbAttachmentFailure();
426
702
  });
427
703
  }
428
704
  buildHomeView() {
@@ -460,7 +736,7 @@ export class SlackMessagingBot {
460
736
  // Threshold for "stuck" detection (10 minutes)
461
737
  const STUCK_THRESHOLD_MS = 10 * 60 * 1000;
462
738
  for (const session of runningSessions) {
463
- const channelId = session.sessionKey.split(":")[0];
739
+ const channelId = conversationIdOf(session.sessionKey);
464
740
  const channel = this.channels.get(channelId);
465
741
  const channelName = channel ? `#${channel.name}` : channelId;
466
742
  const elapsed = Math.floor((Date.now() - session.startedAt) / 60000);
@@ -500,7 +776,14 @@ export class SlackMessagingBot {
500
776
  {
501
777
  type: "button",
502
778
  text: { type: "plain_text", text: "Force Stop", emoji: true },
779
+ // The raw session key travels in `value` (Slack returns it
780
+ // verbatim); the action_id only routes and must be unique per
781
+ // view, so it carries a sanitized copy of the key. Never
782
+ // decode the key from the action_id — the `:`↔`_` rewrite is
783
+ // irreversible for conversation ids that contain `_` (GitHub's
784
+ // GH_owner_repo_number do, by design).
503
785
  action_id: `force_stop_${session.sessionKey.replace(/:/g, "_")}`,
786
+ value: session.sessionKey,
504
787
  style: "danger",
505
788
  },
506
789
  ],
@@ -559,24 +842,10 @@ export class SlackMessagingBot {
559
842
  });
560
843
  return { type: "home", blocks: blocks };
561
844
  }
562
- resolveStopTarget(channelId, threadTs) {
563
- const directTarget = resolveStopTarget({
564
- handler: this.handler,
565
- conversationId: channelId,
566
- sessionKey: resolveSlackSessionKey(channelId, threadTs),
567
- });
568
- if (directTarget)
569
- return directTarget;
570
- if (threadTs)
571
- return null;
572
- return resolveOnlyScopedStopTarget(this.handler, channelId);
573
- }
574
- isStopText(text) {
575
- const normalized = text.trim().toLowerCase();
576
- return normalized === "stop" || normalized === "/stop";
577
- }
578
845
  createCommandAdapters(conversationId, userId, userName, text, ts, options = {}) {
579
- const message = {
846
+ const message = createConversationMessage({
847
+ platform: "slack",
848
+ conversationId,
580
849
  id: ts,
581
850
  sessionKey: conversationId,
582
851
  conversationKind: options.ephemeralChannelId ? "shared" : "direct",
@@ -584,7 +853,7 @@ export class SlackMessagingBot {
584
853
  userName,
585
854
  text,
586
855
  attachments: [],
587
- };
856
+ });
588
857
  const respond = async (responseText) => {
589
858
  if (options.ephemeralChannelId) {
590
859
  await this.postEphemeral(options.ephemeralChannelId, userId, responseText, options.threadTs);
@@ -624,6 +893,7 @@ export class SlackMessagingBot {
624
893
  deleteResponse: async () => { },
625
894
  };
626
895
  return {
896
+ address: message.address,
627
897
  message,
628
898
  responder,
629
899
  platform: this.getMessagingInfo(),
@@ -651,9 +921,10 @@ export class SlackMessagingBot {
651
921
  isMessagingBot: false,
652
922
  ...(threadTs ? { threadTs } : {}),
653
923
  });
654
- const event = {
655
- type: options.type ?? (isDirectMessage ? "dm" : "mention"),
924
+ const event = createConversationEvent({
925
+ platform: "slack",
656
926
  conversationId,
927
+ type: options.type ?? (isDirectMessage ? "dm" : "mention"),
657
928
  conversationKind: isDirectMessage ? "direct" : "shared",
658
929
  ts: eventTs,
659
930
  user: payload.user_id,
@@ -661,14 +932,27 @@ export class SlackMessagingBot {
661
932
  attachments: [],
662
933
  ...(threadTs ? { thread_ts: threadTs } : {}),
663
934
  sessionKey,
664
- };
935
+ });
665
936
  const context = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? { threadTs } : { ephemeralChannelId: conversationId, threadTs });
666
937
  return { event, context };
667
938
  }
668
- async routeSlashLoginCommand(payload) {
939
+ /**
940
+ * Generic slash-command route: synthesize the command text per the
941
+ * manifest's SlackSlashRoute data and dispatch to the handler. `/pi-new`
942
+ * stays bespoke (routeSlashNewCommand) because it replies in 中文 and calls
943
+ * handleNewCommand directly.
944
+ */
945
+ async routeSlashCommand(route, payload) {
669
946
  const { event, context } = this.buildSlashCommandEvent(payload, {
670
- type: payload.channel_id.startsWith("D") ? "dm" : "private_command",
671
- includeText: true,
947
+ includeText: route.includeText,
948
+ thread: route.thread,
949
+ ...(route.privateCommand
950
+ ? {
951
+ type: payload.channel_id.startsWith("D")
952
+ ? "dm"
953
+ : "private_command",
954
+ }
955
+ : {}),
672
956
  });
673
957
  await this.handler.handleEvent(event, this, context);
674
958
  }
@@ -697,19 +981,22 @@ export class SlackMessagingBot {
697
981
  enqueueEvent: (event) => this.enqueueEvent(event),
698
982
  getMessagingInfo: () => this.getMessagingInfo(),
699
983
  };
700
- await this.handler.handleNewCommand(conversationId, conversationId, commandMessagingBot);
701
- }
702
- async routeSlashModelCommand(payload) {
703
- const { event, context } = this.buildSlashCommandEvent(payload, { includeText: true });
704
- await this.handler.handleEvent(event, this, context);
705
- }
706
- async routeSlashSessionCommand(payload) {
707
- const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
708
- await this.handler.handleEvent(event, this, context);
709
- }
710
- async routeSlashAdminCommand(payload) {
711
- const { event, context } = this.buildSlashCommandEvent(payload, { thread: true });
712
- await this.handler.handleEvent(event, this, context);
984
+ const event = {
985
+ ...createConversationEvent({
986
+ platform: "slack",
987
+ conversationId,
988
+ type: "dm",
989
+ conversationKind: "direct",
990
+ ts: eventTs,
991
+ user: payload.user_id,
992
+ text: payload.command,
993
+ attachments: [],
994
+ sessionKey: conversationId,
995
+ }),
996
+ channel: conversationId,
997
+ };
998
+ const context = createSlackAdapters(event, this);
999
+ await this.handler.handleNewCommand(conversationId, conversationId, commandMessagingBot, context.message, context.responder, context.platform);
713
1000
  }
714
1001
  setupEventHandlers() {
715
1002
  this.socketClient.on("disconnect", (err) => {
@@ -721,10 +1008,27 @@ export class SlackMessagingBot {
721
1008
  this.socketClient.on("unable_to_socket_mode_start", (err) => {
722
1009
  log.logWarning("Slack socket unable_to_start", err ? String(err) : "");
723
1010
  });
1011
+ // Which events Slack actually delivers is otherwise unknowable from here:
1012
+ // a listener that never fires looks the same whether the subscription is
1013
+ // missing, the app needs reinstalling, or the surface never emits it at
1014
+ // all. Off by default — this is one line per event on a busy workspace.
1015
+ if (readEnv("SLACK_TRACE_EVENTS")) {
1016
+ this.socketClient.on("slack_event", ({ type, body }) => {
1017
+ const inner = body?.event;
1018
+ log.logInfo(`Slack socket event: ${type ?? "?"}${inner?.type ? ` → ${inner.type}` : ""}${inner?.tab ? ` (tab: ${inner.tab})` : ""}`);
1019
+ });
1020
+ }
724
1021
  this.socketClient.on("app_mention", (payload) => this.handleAppMention(payload));
725
1022
  this.socketClient.on("message", (payload) => this.handleMessageEvent(payload));
726
1023
  this.socketClient.on("slash_commands", (payload) => void this.handleSlashCommand(payload));
727
1024
  this.socketClient.on("app_home_opened", (payload) => this.handleAppHomeOpened(payload));
1025
+ // The assistant surface's lifecycle. Both events were already subscribed
1026
+ // in the app manifest and then dropped on the floor, which is why the pane
1027
+ // opened empty and the sidebar filled with untitled conversations.
1028
+ this.socketClient.on("assistant_thread_started", (payload) => this.handleAssistantThreadStarted(payload));
1029
+ this.socketClient.on("assistant_thread_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
1030
+ // agent_view's spelling of the same signal.
1031
+ this.socketClient.on("app_context_changed", (payload) => this.handleAgentContextChangedEvent(payload));
728
1032
  this.socketClient.on("block_actions", (payload) => void this.handleBlockAction(payload));
729
1033
  this.socketClient.on("interactive", (payload) => void this.handleBlockAction(payload));
730
1034
  }
@@ -739,18 +1043,19 @@ export class SlackMessagingBot {
739
1043
  // Thread replies get their own isolated session (channelId:thread_ts).
740
1044
  const sessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
741
1045
  const mentionText = this.stripOwnMention(e.text);
742
- const slackEvent = {
743
- type: "mention",
1046
+ const slackEvent = createConversationEvent({
1047
+ platform: "slack",
744
1048
  conversationId: e.channel,
1049
+ type: "mention",
745
1050
  conversationKind: "shared",
746
- channel: e.channel,
747
1051
  ts: e.ts,
748
1052
  thread_ts: e.thread_ts,
749
1053
  user: e.user,
750
1054
  text: mentionText || "Please respond to the recent conversation context.",
751
- files: e.files,
752
1055
  sessionKey,
753
- };
1056
+ channel: e.channel,
1057
+ files: e.files,
1058
+ });
754
1059
  const attachmentsPromise = this.logUserMessage(slackEvent);
755
1060
  // Only trigger processing for messages AFTER startup (not replayed old messages)
756
1061
  if (this.startupTs && e.ts < this.startupTs) {
@@ -761,26 +1066,12 @@ export class SlackMessagingBot {
761
1066
  ack();
762
1067
  return;
763
1068
  }
764
- // Check for stop command - execute immediately, don't queue!
765
- if (this.isStopText(slackEvent.text)) {
766
- const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
767
- if (stopTarget) {
768
- this.handler.handleStop(stopTarget, e.channel, this);
769
- }
770
- else {
771
- this.postMessage(e.channel, formatNothingRunning("slack"));
772
- }
773
- void attachmentsPromise.catch((err) => {
774
- log.logWarning("Failed to log Slack message", String(err));
775
- });
776
- ack();
777
- return;
778
- }
779
1069
  this.processSlackMessageIntake({
780
1070
  event: slackEvent,
781
1071
  attachmentsPromise,
782
1072
  queueKey: this.resolveQueueKey(e.channel, sessionKey),
783
1073
  isAutoReplyCandidate: false,
1074
+ addressed: true,
784
1075
  });
785
1076
  ack();
786
1077
  }
@@ -793,7 +1084,13 @@ export class SlackMessagingBot {
793
1084
  ack();
794
1085
  return;
795
1086
  }
796
- const isExternalMessagingBotMessage = !!e.bot_id || e.subtype === "bot_message";
1087
+ // API posts made with a user token carry the posting app's bot_id/app_id
1088
+ // alongside the human `user` — bot_id alone does not make the author a
1089
+ // bot. Only treat the message as bot-authored when the author is not a
1090
+ // known human user (bot users have is_bot=true; unknown authors stay on
1091
+ // the conservative bot path so loop protection holds).
1092
+ const authorIsKnownHuman = !!e.user && this.users.get(e.user)?.isBot === false;
1093
+ const isExternalMessagingBotMessage = e.subtype === "bot_message" || (!!e.bot_id && !authorIsKnownHuman);
797
1094
  if (isExternalMessagingBotMessage) {
798
1095
  if (e.subtype !== undefined && e.subtype !== "bot_message" && e.subtype !== "file_share") {
799
1096
  ack();
@@ -821,7 +1118,11 @@ export class SlackMessagingBot {
821
1118
  ack();
822
1119
  return;
823
1120
  }
824
- const isDM = e.channel_type === "im";
1121
+ // message.im normally carries channel_type "im", but fall back to the
1122
+ // D-prefix convention (used by handleAppMention and session keys) so a
1123
+ // missing channel_type cannot silently demote a DM to an auto-reply
1124
+ // candidate that never triggers.
1125
+ const isDM = e.channel_type === "im" || e.channel.startsWith("D");
825
1126
  const conversationKind = isDM ? "direct" : "shared";
826
1127
  const isMessagingBotMention = e.text?.includes(`<@${this.botUserId}>`);
827
1128
  // Skip channel @mentions - already handled by app_mention event
@@ -831,18 +1132,25 @@ export class SlackMessagingBot {
831
1132
  }
832
1133
  const isThreadReply = !!e.thread_ts;
833
1134
  const sessionKey = isDM ? resolveSlackSessionKey(e.channel, e.thread_ts) : undefined;
834
- const slackEvent = {
835
- type: isDM ? "dm" : "mention",
1135
+ // Name the conversation from the first thing the person said. Guarded on
1136
+ // the assistant registry, so classic DM threads are untouched, and it
1137
+ // claims the title once per thread.
1138
+ if (isDM && e.thread_ts && e.text) {
1139
+ void titleAssistantThread(this.assistantOps(), this.assistantThreads, e.channel, e.thread_ts, this.stripOwnMention(e.text));
1140
+ }
1141
+ const slackEvent = createConversationEvent({
1142
+ platform: "slack",
836
1143
  conversationId: e.channel,
1144
+ type: (isDM ? "dm" : "mention"),
837
1145
  conversationKind,
838
- channel: e.channel,
839
1146
  ts: e.ts,
840
1147
  thread_ts: e.thread_ts,
841
1148
  user: e.user,
842
1149
  text: this.stripOwnMention(e.text),
843
- files: e.files,
844
1150
  sessionKey,
845
- };
1151
+ channel: e.channel,
1152
+ files: e.files,
1153
+ });
846
1154
  const attachmentsPromise = this.logUserMessage(slackEvent);
847
1155
  // Only trigger processing for messages AFTER startup (not replayed old messages)
848
1156
  if (this.startupTs && e.ts < this.startupTs) {
@@ -853,59 +1161,26 @@ export class SlackMessagingBot {
853
1161
  ack();
854
1162
  return;
855
1163
  }
856
- // Stop command for DM only (app_mention handles shared-channel "@mikan stop").
857
- if (isDM && this.isStopText(slackEvent.text)) {
858
- const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
859
- if (stopTarget) {
860
- this.handler.handleStop(stopTarget, e.channel, this);
861
- }
862
- else {
863
- this.postMessage(e.channel, formatNothingRunning("slack"));
864
- }
1164
+ if (!isDM && isThreadReply) {
865
1165
  void attachmentsPromise.catch((err) => {
866
1166
  log.logWarning("Failed to log Slack message", String(err));
867
1167
  });
868
1168
  ack();
869
1169
  return;
870
1170
  }
871
- const enqueueTriggered = () => {
872
- const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
873
- // Auto-reply top-level channel messages start with no sessionKey because
874
- // they are only candidates until the policy allows them. Once triggered,
875
- // persist the resolved key on the event; otherwise the runtime fallback
876
- // treats the message ts as a thread session (`channel:ts`) instead of the
877
- // persistent top-level channel session.
878
- slackEvent.sessionKey = activeSessionKey;
879
- this.processSlackMessageIntake({
880
- event: slackEvent,
881
- attachmentsPromise,
882
- queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
883
- isAutoReplyCandidate: false,
884
- });
885
- };
886
- const logOnly = () => {
887
- void attachmentsPromise.catch((err) => {
888
- log.logWarning("Failed to log Slack message", String(err));
889
- });
890
- };
891
- if (isDM) {
892
- enqueueTriggered();
893
- ack();
894
- return;
895
- }
896
- if (isThreadReply) {
897
- logOnly();
898
- ack();
899
- return;
900
- }
901
- const activeSessionKey = resolveSlackSessionKey(e.channel, e.thread_ts);
1171
+ const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
1172
+ // Auto-reply top-level channel messages start with no sessionKey because
1173
+ // they are only candidates until the policy allows them. Persist the
1174
+ // resolved key on the event; otherwise the runtime fallback treats the
1175
+ // message ts as a thread session (`channel:ts`) instead of the persistent
1176
+ // top-level channel session.
902
1177
  slackEvent.sessionKey = activeSessionKey;
903
1178
  this.processSlackMessageIntake({
904
1179
  event: slackEvent,
905
1180
  attachmentsPromise,
906
1181
  queueKey: this.resolveQueueKey(e.channel, activeSessionKey),
907
- isAutoReplyCandidate: true,
908
- onNotTriggered: logOnly,
1182
+ isAutoReplyCandidate: !isDM,
1183
+ addressed: isDM,
909
1184
  });
910
1185
  ack();
911
1186
  }
@@ -916,54 +1191,20 @@ export class SlackMessagingBot {
916
1191
  return;
917
1192
  }
918
1193
  const { command, text, channel_id, user_id, user_name, thread_ts } = payload;
1194
+ const entry = COMMAND_MANIFEST.find((candidate) => candidate.slackCommand === command);
1195
+ if (!entry)
1196
+ return;
919
1197
  let handlerPromise = null;
920
- if (command === "/pi-login") {
921
- handlerPromise = this.routeSlashLoginCommand({
922
- command,
923
- text,
924
- channel_id,
925
- user_id,
926
- user_name,
927
- });
928
- }
929
- else if (command === "/pi-new") {
1198
+ if (entry.name === "new") {
930
1199
  handlerPromise = this.routeSlashNewCommand({ command, channel_id, user_id, user_name });
931
1200
  }
932
- else if (command === "/pi-session") {
933
- handlerPromise = this.routeSlashSessionCommand({
934
- command,
935
- channel_id,
936
- user_id,
937
- user_name,
938
- thread_ts,
939
- });
940
- }
941
- else if (command === "/pi-model") {
942
- handlerPromise = this.routeSlashModelCommand({
943
- command,
944
- text,
945
- channel_id,
946
- user_id,
947
- user_name,
948
- });
949
- }
950
- else if (command === "/pi-sandbox" ||
951
- command === "/pi-auto-reply" ||
952
- command === "/pi-extensions") {
953
- handlerPromise = this.routeSlashModelCommand({
1201
+ else if (entry.slackRoute) {
1202
+ handlerPromise = this.routeSlashCommand(entry.slackRoute, {
954
1203
  command,
955
1204
  text,
956
1205
  channel_id,
957
1206
  user_id,
958
1207
  user_name,
959
- });
960
- }
961
- else if (command === "/pi-admin") {
962
- handlerPromise = this.routeSlashAdminCommand({
963
- command,
964
- channel_id,
965
- user_id,
966
- user_name,
967
1208
  thread_ts,
968
1209
  });
969
1210
  }
@@ -977,6 +1218,16 @@ export class SlackMessagingBot {
977
1218
  handleAppHomeOpened({ event, ack }) {
978
1219
  const e = event;
979
1220
  ack();
1221
+ // Under agent_view this is how Slack says "the person opened the app's
1222
+ // DM" — the signal assistant_thread_started used to carry. It fires on
1223
+ // every open, not once per conversation, so this path refreshes the
1224
+ // pinned prompts and deliberately does not greet.
1225
+ if (e.tab === "messages") {
1226
+ if (e.channel) {
1227
+ void handleAgentDmOpened(this.assistantOps(), this.assistantThreads, e.channel, e.context);
1228
+ }
1229
+ return;
1230
+ }
980
1231
  if (e.tab !== "home")
981
1232
  return;
982
1233
  this.webClient.views
@@ -1000,12 +1251,15 @@ export class SlackMessagingBot {
1000
1251
  return;
1001
1252
  }
1002
1253
  ack();
1003
- const sessionKey = action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1254
+ // Prefer the verbatim key from `value`; the action_id fallback only
1255
+ // serves buttons rendered before `value` existed and misdecodes session
1256
+ // keys whose conversation id contains "_".
1257
+ const sessionKey = action.value ?? action.action_id.replace("force_stop_", "").replace(/_/g, ":");
1004
1258
  const userId = body.user?.id;
1005
- const channelId = body.container?.channel_id || sessionKey.split(":")[0];
1259
+ const channelId = body.container?.channel_id || conversationIdOf(sessionKey);
1006
1260
  log.logInfo(`[Force Stop] User ${userId} requested force stop for ${sessionKey}`);
1007
1261
  // Use handler's forceStop method
1008
- this.handler.forceStop(sessionKey);
1262
+ this.handler.forceStop(createOfficeAddress("slack", channelId), sessionKey);
1009
1263
  // Notify in channel
1010
1264
  await this.postMessage(channelId, formatForceStopped("slack", userId ?? "unknown"));
1011
1265
  // Refresh home tab
@@ -1030,6 +1284,38 @@ export class SlackMessagingBot {
1030
1284
  const selectedOptions = Array.isArray(action.selected_options)
1031
1285
  ? action.selected_options
1032
1286
  : undefined;
1287
+ const threadTs = container.thread_ts;
1288
+ const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1289
+ // Extension-owned actions (ext:<slug>: namespaced by api.blockkit.post)
1290
+ // dispatch exclusively to the extension's onAction handler — no agent
1291
+ // run, no chat-history entry. They never fall through to the model: an
1292
+ // unconsumed namespaced action means the extension is gone, and its
1293
+ // routing id would only confuse an agent run.
1294
+ const extAction = parseExtActionId(action.action_id ?? "");
1295
+ if (extAction) {
1296
+ this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
1297
+ const consumed = await this.handler.handleExtensionAction({
1298
+ address: createOfficeAddress("slack", channelId),
1299
+ sessionKey,
1300
+ conversationKind: channelId.startsWith("D") ? "direct" : "shared",
1301
+ slug: extAction.slug,
1302
+ action: {
1303
+ actionId: extAction.actionId,
1304
+ value: action.value ?? selectedOption?.value ?? selectedOption?.text?.text,
1305
+ selectedValues: selectedOptions?.map((option) => option.value ?? option.text?.text ?? ""),
1306
+ userId,
1307
+ userName: body.user?.username ?? body.user?.name,
1308
+ conversationId: channelId,
1309
+ messageTs: container.message_ts,
1310
+ threadTs,
1311
+ },
1312
+ });
1313
+ if (!consumed) {
1314
+ log.logWarning(`[${channelId}] Dropped extension action ${action.action_id}`, `no handler registered by extension "${extAction.slug}"`);
1315
+ }
1316
+ });
1317
+ return;
1318
+ }
1033
1319
  const selectedText = selectedOption?.text?.text ?? selectedOption?.value;
1034
1320
  const selectedTexts = selectedOptions?.map((option) => option.text?.text ?? option.value);
1035
1321
  const valueText = selectedTexts?.length
@@ -1037,8 +1323,6 @@ export class SlackMessagingBot {
1037
1323
  : (selectedText ?? action.value ?? action.action_id);
1038
1324
  const text = `[Slack action] ${action.action_id}: ${valueText}`;
1039
1325
  const ts = `action:${Date.now()}`;
1040
- const threadTs = container.thread_ts;
1041
- const sessionKey = resolveSlackSessionKey(channelId, threadTs);
1042
1326
  this.logToFile(channelId, {
1043
1327
  date: new Date().toISOString(),
1044
1328
  ts,
@@ -1065,9 +1349,10 @@ export class SlackMessagingBot {
1065
1349
  messageTs: container.message_ts,
1066
1350
  },
1067
1351
  });
1068
- const event = {
1069
- type: "slack_action",
1352
+ const event = createConversationEvent({
1353
+ platform: "slack",
1070
1354
  conversationId: channelId,
1355
+ type: "slack_action",
1071
1356
  conversationKind: channelId.startsWith("D") ? "direct" : "shared",
1072
1357
  ts,
1073
1358
  user: userId,
@@ -1075,23 +1360,71 @@ export class SlackMessagingBot {
1075
1360
  attachments: [],
1076
1361
  ...(threadTs ? { thread_ts: threadTs } : {}),
1077
1362
  sessionKey,
1078
- };
1363
+ });
1079
1364
  this.getQueue(this.resolveQueueKey(channelId, sessionKey)).enqueue(async () => {
1080
1365
  const slackEvent = {
1081
- type: event.conversationKind === "direct" ? "dm" : "mention",
1082
- conversationId: channelId,
1083
- conversationKind: event.conversationKind,
1366
+ ...createConversationEvent({
1367
+ platform: "slack",
1368
+ conversationId: channelId,
1369
+ type: (event.conversationKind === "direct" ? "dm" : "mention"),
1370
+ conversationKind: event.conversationKind,
1371
+ ts,
1372
+ thread_ts: threadTs,
1373
+ user: userId,
1374
+ text,
1375
+ attachments: [],
1376
+ sessionKey,
1377
+ }),
1084
1378
  channel: channelId,
1085
- ts,
1086
- thread_ts: threadTs,
1087
- user: userId,
1088
- text,
1089
1379
  attachments: [],
1090
- sessionKey,
1091
1380
  };
1092
1381
  return this.handler.handleEvent(event, this, this.createContext(slackEvent));
1093
1382
  });
1094
1383
  }
1384
+ /**
1385
+ * Download Slack message files into the office's attachments directory.
1386
+ * The Slack contract is all-or-error: any failed download throws after the
1387
+ * successful ones are on disk, so callers still log the message text.
1388
+ */
1389
+ async processAttachments(channelId, files, timestamp) {
1390
+ const items = [];
1391
+ for (const file of files) {
1392
+ const url = file.url_private_download || file.url_private;
1393
+ if (!url)
1394
+ continue;
1395
+ if (!file.name) {
1396
+ throw new Error(`Attachment missing name for URL: ${url}`);
1397
+ }
1398
+ items.push({
1399
+ name: file.name,
1400
+ // Slack timestamps are float seconds; the stored filename uses ms.
1401
+ timestampMs: Math.floor(parseFloat(timestamp) * 1000),
1402
+ download: (destPath) => this.downloadSlackFile(url, destPath),
1403
+ });
1404
+ }
1405
+ const office = this.workspace.office(createOfficeAddress("slack", channelId));
1406
+ const { saved, failed } = await saveIncomingAttachments(office, items);
1407
+ const firstFailure = failed[0];
1408
+ if (firstFailure) {
1409
+ const { name, error } = firstFailure;
1410
+ const errorMsg = error instanceof Error ? error.message : String(error);
1411
+ throw new Error(`Failed to download attachment ${name}: ${errorMsg}`, { cause: error });
1412
+ }
1413
+ return saved;
1414
+ }
1415
+ /** Authorized download with retry; Slack file URLs require the bot token. */
1416
+ async downloadSlackFile(url, destPath) {
1417
+ await withRetry(async () => {
1418
+ const response = await fetch(url, {
1419
+ headers: { Authorization: `Bearer ${this.botToken}` },
1420
+ });
1421
+ if (!response.ok) {
1422
+ throw new AttachmentDownloadHttpError(`HTTP ${response.status}: ${response.statusText}`, response.status);
1423
+ }
1424
+ const buffer = await response.arrayBuffer();
1425
+ await writeFile(destPath, Buffer.from(buffer));
1426
+ }, { maxAttempts: 3, baseDelayMs: 250, isRateLimited: isRetryableAttachmentDownloadError });
1427
+ }
1095
1428
  /**
1096
1429
  * Log a user message to log.jsonl after attachments are ready.
1097
1430
  */
@@ -1101,7 +1434,7 @@ export class SlackMessagingBot {
1101
1434
  let attachmentError;
1102
1435
  if (event.files) {
1103
1436
  try {
1104
- attachments = await this.store.processAttachments(event.channel, event.files, event.ts);
1437
+ attachments = await this.processAttachments(event.channel, event.files, event.ts);
1105
1438
  }
1106
1439
  catch (err) {
1107
1440
  attachmentError = err;
@@ -1126,7 +1459,7 @@ export class SlackMessagingBot {
1126
1459
  }
1127
1460
  async logExternalMessagingBotMessage(event) {
1128
1461
  const attachments = event.files
1129
- ? await this.store.processAttachments(event.channel, event.files, event.ts)
1462
+ ? await this.processAttachments(event.channel, event.files, event.ts)
1130
1463
  : [];
1131
1464
  const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
1132
1465
  this.logToFile(event.channel, {
@@ -1149,7 +1482,7 @@ export class SlackMessagingBot {
1149
1482
  // Private - Backfill
1150
1483
  // ==========================================================================
1151
1484
  async getExistingTimestamps(channelId) {
1152
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1485
+ const logPath = join(this.conversationDir(channelId), "log.jsonl");
1153
1486
  const timestamps = new Set();
1154
1487
  if (!existsSync(logPath))
1155
1488
  return timestamps;
@@ -1157,7 +1490,7 @@ export class SlackMessagingBot {
1157
1490
  const lines = content.trim().split("\n").filter(Boolean);
1158
1491
  for (let i = 0; i < lines.length; i++) {
1159
1492
  try {
1160
- const entry = JSON.parse(lines[i]);
1493
+ const entry = JSON.parse(lines[i] ?? "");
1161
1494
  if (entry.ts)
1162
1495
  timestamps.add(entry.ts);
1163
1496
  }
@@ -1235,7 +1568,7 @@ export class SlackMessagingBot {
1235
1568
  const user = this.users.get(msg.user);
1236
1569
  const text = this.stripOwnMention(msg.text);
1237
1570
  const attachments = msg.files
1238
- ? await this.store.processAttachments(channelId, msg.files, msg.ts)
1571
+ ? await this.processAttachments(channelId, msg.files, msg.ts)
1239
1572
  : [];
1240
1573
  this.logToFile(channelId, {
1241
1574
  date: new Date(parseFloat(msg.ts) * 1000).toISOString(),
@@ -1256,7 +1589,7 @@ export class SlackMessagingBot {
1256
1589
  // Only backfill channels that already have a log.jsonl (mikan has interacted with them before)
1257
1590
  const channelsToBackfill = [];
1258
1591
  for (const [channelId, channel] of this.channels) {
1259
- const logPath = join(this.workingDir, channelId, "log.jsonl");
1592
+ const logPath = join(this.conversationDir(channelId), "log.jsonl");
1260
1593
  if (existsSync(logPath)) {
1261
1594
  channelsToBackfill.push([channelId, channel]);
1262
1595
  }
@@ -1274,7 +1607,7 @@ export class SlackMessagingBot {
1274
1607
  log.logWarning(`Failed to backfill #${channel.name}`, String(error));
1275
1608
  }
1276
1609
  // Add delay between channels to avoid hitting Slack rate limits
1277
- if (channelId !== channelsToBackfill[channelsToBackfill.length - 1][0]) {
1610
+ if (channelId !== channelsToBackfill[channelsToBackfill.length - 1]?.[0]) {
1278
1611
  await new Promise((resolve) => setTimeout(resolve, 500));
1279
1612
  }
1280
1613
  }
@@ -1296,6 +1629,7 @@ export class SlackMessagingBot {
1296
1629
  id: u.id,
1297
1630
  userName: u.name,
1298
1631
  displayName: u.real_name || u.name,
1632
+ isBot: !!u.is_bot,
1299
1633
  });
1300
1634
  }
1301
1635
  }