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

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 (639) hide show
  1. package/CHANGELOG.md +688 -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 +82 -117
  10. package/dist/adapters/discord/bot.js.map +1 -1
  11. package/dist/adapters/discord/context.d.ts +3 -1
  12. package/dist/adapters/discord/context.d.ts.map +1 -1
  13. package/dist/adapters/discord/context.js +54 -215
  14. package/dist/adapters/discord/context.js.map +1 -1
  15. package/dist/adapters/github/bot.d.ts +89 -0
  16. package/dist/adapters/github/bot.d.ts.map +1 -0
  17. package/dist/adapters/github/bot.js +653 -0
  18. package/dist/adapters/github/bot.js.map +1 -0
  19. package/dist/adapters/github/client.d.ts +122 -0
  20. package/dist/adapters/github/client.d.ts.map +1 -0
  21. package/dist/adapters/github/client.js +314 -0
  22. package/dist/adapters/github/client.js.map +1 -0
  23. package/dist/adapters/github/cloudbuild.d.ts +19 -0
  24. package/dist/adapters/github/cloudbuild.d.ts.map +1 -0
  25. package/dist/adapters/github/cloudbuild.js +71 -0
  26. package/dist/adapters/github/cloudbuild.js.map +1 -0
  27. package/dist/adapters/github/context.d.ts +12 -0
  28. package/dist/adapters/github/context.d.ts.map +1 -0
  29. package/dist/adapters/github/context.js +101 -0
  30. package/dist/adapters/github/context.js.map +1 -0
  31. package/dist/adapters/github/gcp-auth.d.ts +28 -0
  32. package/dist/adapters/github/gcp-auth.d.ts.map +1 -0
  33. package/dist/adapters/github/gcp-auth.js +196 -0
  34. package/dist/adapters/github/gcp-auth.js.map +1 -0
  35. package/dist/adapters/github/github-ops.d.ts +91 -0
  36. package/dist/adapters/github/github-ops.d.ts.map +1 -0
  37. package/dist/adapters/github/github-ops.js +406 -0
  38. package/dist/adapters/github/github-ops.js.map +1 -0
  39. package/dist/adapters/github/ids.d.ts +34 -0
  40. package/dist/adapters/github/ids.d.ts.map +1 -0
  41. package/dist/adapters/github/ids.js +57 -0
  42. package/dist/adapters/github/ids.js.map +1 -0
  43. package/dist/adapters/github/repo.d.ts +33 -0
  44. package/dist/adapters/github/repo.d.ts.map +1 -0
  45. package/dist/adapters/github/repo.js +141 -0
  46. package/dist/adapters/github/repo.js.map +1 -0
  47. package/dist/adapters/github/tool-pack.d.ts +14 -0
  48. package/dist/adapters/github/tool-pack.d.ts.map +1 -0
  49. package/dist/adapters/github/tool-pack.js +56 -0
  50. package/dist/adapters/github/tool-pack.js.map +1 -0
  51. package/dist/adapters/github/tools/checks.d.ts +19 -0
  52. package/dist/adapters/github/tools/checks.d.ts.map +1 -0
  53. package/dist/adapters/github/tools/checks.js +105 -0
  54. package/dist/adapters/github/tools/checks.js.map +1 -0
  55. package/dist/adapters/github/tools/issue.d.ts +21 -0
  56. package/dist/adapters/github/tools/issue.d.ts.map +1 -0
  57. package/dist/adapters/github/tools/issue.js +48 -0
  58. package/dist/adapters/github/tools/issue.js.map +1 -0
  59. package/dist/adapters/github/tools/pr.d.ts +22 -0
  60. package/dist/adapters/github/tools/pr.d.ts.map +1 -0
  61. package/dist/adapters/github/tools/pr.js +48 -0
  62. package/dist/adapters/github/tools/pr.js.map +1 -0
  63. package/dist/adapters/github/tools/read.d.ts +21 -0
  64. package/dist/adapters/github/tools/read.d.ts.map +1 -0
  65. package/dist/adapters/github/tools/read.js +141 -0
  66. package/dist/adapters/github/tools/read.js.map +1 -0
  67. package/dist/adapters/github/tools/review-reply.d.ts +17 -0
  68. package/dist/adapters/github/tools/review-reply.d.ts.map +1 -0
  69. package/dist/adapters/github/tools/review-reply.js +38 -0
  70. package/dist/adapters/github/tools/review-reply.js.map +1 -0
  71. package/dist/adapters/github/tools/sync.d.ts +17 -0
  72. package/dist/adapters/github/tools/sync.d.ts.map +1 -0
  73. package/dist/adapters/github/tools/sync.js +34 -0
  74. package/dist/adapters/github/tools/sync.js.map +1 -0
  75. package/dist/adapters/github/types.d.ts +373 -0
  76. package/dist/adapters/github/types.d.ts.map +1 -0
  77. package/dist/adapters/github/types.js +2 -0
  78. package/dist/adapters/github/types.js.map +1 -0
  79. package/dist/adapters/intake.d.ts +16 -5
  80. package/dist/adapters/intake.d.ts.map +1 -1
  81. package/dist/adapters/intake.js +86 -23
  82. package/dist/adapters/intake.js.map +1 -1
  83. package/dist/adapters/progressive-renderer.d.ts +7 -0
  84. package/dist/adapters/progressive-renderer.d.ts.map +1 -0
  85. package/dist/adapters/progressive-renderer.js +383 -0
  86. package/dist/adapters/progressive-renderer.js.map +1 -0
  87. package/dist/adapters/shared.d.ts +23 -10
  88. package/dist/adapters/shared.d.ts.map +1 -1
  89. package/dist/adapters/shared.js +60 -22
  90. package/dist/adapters/shared.js.map +1 -1
  91. package/dist/adapters/slack/blocks.d.ts +14 -0
  92. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  93. package/dist/adapters/slack/blocks.js +147 -111
  94. package/dist/adapters/slack/blocks.js.map +1 -1
  95. package/dist/adapters/slack/bot.d.ts +43 -13
  96. package/dist/adapters/slack/bot.d.ts.map +1 -1
  97. package/dist/adapters/slack/bot.js +385 -207
  98. package/dist/adapters/slack/bot.js.map +1 -1
  99. package/dist/adapters/slack/context.d.ts +3 -1
  100. package/dist/adapters/slack/context.d.ts.map +1 -1
  101. package/dist/adapters/slack/context.js +11 -17
  102. package/dist/adapters/slack/context.js.map +1 -1
  103. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  104. package/dist/adapters/slack/response-lifecycle.js +126 -323
  105. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  106. package/dist/adapters/slack/session.d.ts.map +1 -1
  107. package/dist/adapters/slack/session.js +7 -11
  108. package/dist/adapters/slack/session.js.map +1 -1
  109. package/dist/adapters/slack/tool-pack.d.ts +11 -0
  110. package/dist/adapters/slack/tool-pack.d.ts.map +1 -0
  111. package/dist/adapters/slack/tool-pack.js +45 -0
  112. package/dist/adapters/slack/tool-pack.js.map +1 -0
  113. package/dist/adapters/slack/tools/attach.js +1 -1
  114. package/dist/adapters/slack/tools/attach.js.map +1 -1
  115. package/dist/adapters/slack/tools/blockkit.d.ts +14 -0
  116. package/dist/adapters/slack/tools/blockkit.d.ts.map +1 -0
  117. package/dist/adapters/slack/tools/blockkit.js +106 -0
  118. package/dist/adapters/slack/tools/blockkit.js.map +1 -0
  119. package/dist/adapters/slack/types.d.ts +40 -2
  120. package/dist/adapters/slack/types.d.ts.map +1 -1
  121. package/dist/adapters/slack/types.js.map +1 -1
  122. package/dist/adapters/telegram/bot.d.ts +8 -9
  123. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  124. package/dist/adapters/telegram/bot.js +90 -117
  125. package/dist/adapters/telegram/bot.js.map +1 -1
  126. package/dist/adapters/telegram/context.d.ts +2 -0
  127. package/dist/adapters/telegram/context.d.ts.map +1 -1
  128. package/dist/adapters/telegram/context.js +61 -184
  129. package/dist/adapters/telegram/context.js.map +1 -1
  130. package/dist/adapters/telegram/html.d.ts.map +1 -1
  131. package/dist/adapters/telegram/html.js +10 -7
  132. package/dist/adapters/telegram/html.js.map +1 -1
  133. package/dist/adapters/types.d.ts +109 -12
  134. package/dist/adapters/types.d.ts.map +1 -1
  135. package/dist/adapters/types.js.map +1 -1
  136. package/dist/agent-events.d.ts +3 -2
  137. package/dist/agent-events.d.ts.map +1 -1
  138. package/dist/agent-events.js +19 -1
  139. package/dist/agent-events.js.map +1 -1
  140. package/dist/agent.d.ts +14 -19
  141. package/dist/agent.d.ts.map +1 -1
  142. package/dist/agent.js +840 -298
  143. package/dist/agent.js.map +1 -1
  144. package/dist/cli/arg-grammar.d.ts +25 -0
  145. package/dist/cli/arg-grammar.d.ts.map +1 -0
  146. package/dist/cli/arg-grammar.js +61 -0
  147. package/dist/cli/arg-grammar.js.map +1 -0
  148. package/dist/cli/boot.d.ts +5 -0
  149. package/dist/cli/boot.d.ts.map +1 -0
  150. package/dist/cli/boot.js +117 -0
  151. package/dist/cli/boot.js.map +1 -0
  152. package/dist/cli/download.d.ts.map +1 -0
  153. package/dist/{download.js → cli/download.js} +2 -0
  154. package/dist/cli/download.js.map +1 -0
  155. package/dist/cli/ext-dev.d.ts +2 -0
  156. package/dist/cli/ext-dev.d.ts.map +1 -0
  157. package/dist/cli/ext-dev.js +188 -0
  158. package/dist/cli/ext-dev.js.map +1 -0
  159. package/dist/cli/ext.d.ts +2 -0
  160. package/dist/cli/ext.d.ts.map +1 -0
  161. package/dist/cli/ext.js +324 -0
  162. package/dist/cli/ext.js.map +1 -0
  163. package/dist/cli/office.d.ts +2 -0
  164. package/dist/cli/office.d.ts.map +1 -0
  165. package/dist/cli/office.js +91 -0
  166. package/dist/cli/office.js.map +1 -0
  167. package/dist/cli/types.d.ts +16 -0
  168. package/dist/cli/types.d.ts.map +1 -0
  169. package/dist/cli/types.js +2 -0
  170. package/dist/cli/types.js.map +1 -0
  171. package/dist/commands/admin.d.ts.map +1 -1
  172. package/dist/commands/admin.js +3 -2
  173. package/dist/commands/admin.js.map +1 -1
  174. package/dist/commands/auto-reply.d.ts.map +1 -1
  175. package/dist/commands/auto-reply.js +5 -9
  176. package/dist/commands/auto-reply.js.map +1 -1
  177. package/dist/commands/extensions.d.ts.map +1 -1
  178. package/dist/commands/extensions.js +9 -6
  179. package/dist/commands/extensions.js.map +1 -1
  180. package/dist/commands/login.d.ts +2 -1
  181. package/dist/commands/login.d.ts.map +1 -1
  182. package/dist/commands/login.js +40 -5
  183. package/dist/commands/login.js.map +1 -1
  184. package/dist/commands/manifest.d.ts +37 -0
  185. package/dist/commands/manifest.d.ts.map +1 -0
  186. package/dist/commands/manifest.js +151 -0
  187. package/dist/commands/manifest.js.map +1 -0
  188. package/dist/commands/model.d.ts +2 -3
  189. package/dist/commands/model.d.ts.map +1 -1
  190. package/dist/commands/model.js +63 -24
  191. package/dist/commands/model.js.map +1 -1
  192. package/dist/commands/new.d.ts.map +1 -1
  193. package/dist/commands/new.js +15 -3
  194. package/dist/commands/new.js.map +1 -1
  195. package/dist/commands/registry.d.ts +2 -2
  196. package/dist/commands/registry.d.ts.map +1 -1
  197. package/dist/commands/registry.js +1 -2
  198. package/dist/commands/registry.js.map +1 -1
  199. package/dist/commands/sandbox.d.ts.map +1 -1
  200. package/dist/commands/sandbox.js +83 -33
  201. package/dist/commands/sandbox.js.map +1 -1
  202. package/dist/commands/session-view.d.ts.map +1 -1
  203. package/dist/commands/session-view.js +6 -3
  204. package/dist/commands/session-view.js.map +1 -1
  205. package/dist/commands/types.d.ts +79 -7
  206. package/dist/commands/types.d.ts.map +1 -1
  207. package/dist/commands/types.js.map +1 -1
  208. package/dist/config.d.ts +23 -8
  209. package/dist/config.d.ts.map +1 -1
  210. package/dist/config.js +222 -135
  211. package/dist/config.js.map +1 -1
  212. package/dist/env-manifest.d.ts +30 -0
  213. package/dist/env-manifest.d.ts.map +1 -0
  214. package/dist/env-manifest.js +229 -0
  215. package/dist/env-manifest.js.map +1 -0
  216. package/dist/events.d.ts.map +1 -1
  217. package/dist/events.js +20 -81
  218. package/dist/events.js.map +1 -1
  219. package/dist/execution-resolver.d.ts +7 -8
  220. package/dist/execution-resolver.d.ts.map +1 -1
  221. package/dist/execution-resolver.js +106 -110
  222. package/dist/execution-resolver.js.map +1 -1
  223. package/dist/extension-schedules.d.ts +31 -0
  224. package/dist/extension-schedules.d.ts.map +1 -0
  225. package/dist/extension-schedules.js +229 -0
  226. package/dist/extension-schedules.js.map +1 -0
  227. package/dist/harness/auth.d.ts +2 -1
  228. package/dist/harness/auth.d.ts.map +1 -1
  229. package/dist/harness/auth.js +11 -5
  230. package/dist/harness/auth.js.map +1 -1
  231. package/dist/harness/event-format.d.ts +27 -0
  232. package/dist/harness/event-format.d.ts.map +1 -0
  233. package/dist/harness/event-format.js +160 -0
  234. package/dist/harness/event-format.js.map +1 -0
  235. package/dist/harness/extensions/loader.d.ts +27 -49
  236. package/dist/harness/extensions/loader.d.ts.map +1 -1
  237. package/dist/harness/extensions/loader.js +486 -130
  238. package/dist/harness/extensions/loader.js.map +1 -1
  239. package/dist/harness/extensions/registry.d.ts +107 -2
  240. package/dist/harness/extensions/registry.d.ts.map +1 -1
  241. package/dist/harness/extensions/registry.js +316 -0
  242. package/dist/harness/extensions/registry.js.map +1 -1
  243. package/dist/harness/extensions/types.d.ts +482 -42
  244. package/dist/harness/extensions/types.d.ts.map +1 -1
  245. package/dist/harness/extensions/types.js.map +1 -1
  246. package/dist/harness/index.d.ts +18 -8
  247. package/dist/harness/index.d.ts.map +1 -1
  248. package/dist/harness/index.js +7 -4
  249. package/dist/harness/index.js.map +1 -1
  250. package/dist/harness/models.d.ts +7 -4
  251. package/dist/harness/models.d.ts.map +1 -1
  252. package/dist/harness/models.js +46 -24
  253. package/dist/harness/models.js.map +1 -1
  254. package/dist/harness/runner.d.ts +50 -54
  255. package/dist/harness/runner.d.ts.map +1 -1
  256. package/dist/harness/runner.js +232 -46
  257. package/dist/harness/runner.js.map +1 -1
  258. package/dist/harness/session-store.d.ts +11 -5
  259. package/dist/harness/session-store.d.ts.map +1 -1
  260. package/dist/harness/session-store.js +141 -36
  261. package/dist/harness/session-store.js.map +1 -1
  262. package/dist/harness/settings.d.ts +2 -20
  263. package/dist/harness/settings.d.ts.map +1 -1
  264. package/dist/harness/settings.js +12 -1
  265. package/dist/harness/settings.js.map +1 -1
  266. package/dist/harness/skills.d.ts +10 -29
  267. package/dist/harness/skills.d.ts.map +1 -1
  268. package/dist/harness/skills.js +48 -6
  269. package/dist/harness/skills.js.map +1 -1
  270. package/dist/harness/subagent-profiles.d.ts +8 -0
  271. package/dist/harness/subagent-profiles.d.ts.map +1 -0
  272. package/dist/harness/subagent-profiles.js +299 -0
  273. package/dist/harness/subagent-profiles.js.map +1 -0
  274. package/dist/harness/subagent-runner.d.ts +39 -0
  275. package/dist/harness/subagent-runner.d.ts.map +1 -0
  276. package/dist/harness/subagent-runner.js +617 -0
  277. package/dist/harness/subagent-runner.js.map +1 -0
  278. package/dist/harness/types.d.ts +267 -1
  279. package/dist/harness/types.d.ts.map +1 -1
  280. package/dist/harness/types.js.map +1 -1
  281. package/dist/harness/usage.d.ts +6 -0
  282. package/dist/harness/usage.d.ts.map +1 -0
  283. package/dist/harness/usage.js +32 -0
  284. package/dist/harness/usage.js.map +1 -0
  285. package/dist/index.d.ts +36 -8
  286. package/dist/index.d.ts.map +1 -1
  287. package/dist/index.js +22 -6
  288. package/dist/index.js.map +1 -1
  289. package/dist/main.js +391 -127
  290. package/dist/main.js.map +1 -1
  291. package/dist/observability/instrument.js +7 -5
  292. package/dist/observability/instrument.js.map +1 -1
  293. package/dist/observability/sentry.d.ts +2 -0
  294. package/dist/observability/sentry.d.ts.map +1 -1
  295. package/dist/observability/sentry.js +10 -2
  296. package/dist/observability/sentry.js.map +1 -1
  297. package/dist/office/address.d.ts +31 -0
  298. package/dist/office/address.d.ts.map +1 -0
  299. package/dist/office/address.js +113 -0
  300. package/dist/office/address.js.map +1 -0
  301. package/dist/office/index.d.ts +11 -0
  302. package/dist/office/index.d.ts.map +1 -0
  303. package/dist/office/index.js +11 -0
  304. package/dist/office/index.js.map +1 -0
  305. package/dist/office/layout.d.ts +27 -0
  306. package/dist/office/layout.d.ts.map +1 -0
  307. package/dist/office/layout.js +97 -0
  308. package/dist/office/layout.js.map +1 -0
  309. package/dist/office/migration.d.ts +44 -0
  310. package/dist/office/migration.d.ts.map +1 -0
  311. package/dist/office/migration.js +255 -0
  312. package/dist/office/migration.js.map +1 -0
  313. package/dist/office/registry.d.ts +75 -0
  314. package/dist/office/registry.d.ts.map +1 -0
  315. package/dist/office/registry.js +646 -0
  316. package/dist/office/registry.js.map +1 -0
  317. package/dist/office/types.d.ts +71 -0
  318. package/dist/office/types.d.ts.map +1 -0
  319. package/dist/office/types.js +2 -0
  320. package/dist/office/types.js.map +1 -0
  321. package/dist/packages/admin.d.ts +17 -0
  322. package/dist/packages/admin.d.ts.map +1 -0
  323. package/dist/packages/admin.js +98 -0
  324. package/dist/packages/admin.js.map +1 -0
  325. package/dist/packages/index.d.ts +6 -0
  326. package/dist/packages/index.d.ts.map +1 -0
  327. package/dist/packages/index.js +6 -0
  328. package/dist/packages/index.js.map +1 -0
  329. package/dist/packages/inspect.d.ts +11 -0
  330. package/dist/packages/inspect.d.ts.map +1 -0
  331. package/dist/packages/inspect.js +98 -0
  332. package/dist/packages/inspect.js.map +1 -0
  333. package/dist/packages/materialize.d.ts +40 -0
  334. package/dist/packages/materialize.d.ts.map +1 -0
  335. package/dist/packages/materialize.js +297 -0
  336. package/dist/packages/materialize.js.map +1 -0
  337. package/dist/packages/resolve.d.ts +25 -0
  338. package/dist/packages/resolve.d.ts.map +1 -0
  339. package/dist/packages/resolve.js +208 -0
  340. package/dist/packages/resolve.js.map +1 -0
  341. package/dist/packages/source.d.ts +39 -0
  342. package/dist/packages/source.d.ts.map +1 -0
  343. package/dist/packages/source.js +189 -0
  344. package/dist/packages/source.js.map +1 -0
  345. package/dist/packages/types.d.ts +104 -0
  346. package/dist/packages/types.d.ts.map +1 -0
  347. package/dist/packages/types.js +2 -0
  348. package/dist/packages/types.js.map +1 -0
  349. package/dist/provisioner.d.ts +66 -0
  350. package/dist/provisioner.d.ts.map +1 -1
  351. package/dist/provisioner.js +335 -13
  352. package/dist/provisioner.js.map +1 -1
  353. package/dist/runtime/conversation-runtime.d.ts +1 -1
  354. package/dist/runtime/conversation-runtime.d.ts.map +1 -1
  355. package/dist/runtime/conversation-runtime.js +562 -118
  356. package/dist/runtime/conversation-runtime.js.map +1 -1
  357. package/dist/runtime/session-lifecycle.d.ts +38 -0
  358. package/dist/runtime/session-lifecycle.d.ts.map +1 -0
  359. package/dist/runtime/session-lifecycle.js +193 -0
  360. package/dist/runtime/session-lifecycle.js.map +1 -0
  361. package/dist/runtime/types.d.ts +66 -7
  362. package/dist/runtime/types.d.ts.map +1 -1
  363. package/dist/runtime/types.js.map +1 -1
  364. package/dist/sandbox/cloudflare.d.ts +3 -0
  365. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  366. package/dist/sandbox/cloudflare.js +13 -1
  367. package/dist/sandbox/cloudflare.js.map +1 -1
  368. package/dist/sandbox/container.d.ts +4 -0
  369. package/dist/sandbox/container.d.ts.map +1 -1
  370. package/dist/sandbox/container.js +13 -3
  371. package/dist/sandbox/container.js.map +1 -1
  372. package/dist/sandbox/firecracker.d.ts +3 -0
  373. package/dist/sandbox/firecracker.d.ts.map +1 -1
  374. package/dist/sandbox/firecracker.js +13 -2
  375. package/dist/sandbox/firecracker.js.map +1 -1
  376. package/dist/sandbox/gondolin.d.ts +57 -0
  377. package/dist/sandbox/gondolin.d.ts.map +1 -0
  378. package/dist/sandbox/gondolin.js +764 -0
  379. package/dist/sandbox/gondolin.js.map +1 -0
  380. package/dist/sandbox/host.d.ts +3 -0
  381. package/dist/sandbox/host.d.ts.map +1 -1
  382. package/dist/sandbox/host.js +18 -3
  383. package/dist/sandbox/host.js.map +1 -1
  384. package/dist/sandbox/identity.d.ts +44 -0
  385. package/dist/sandbox/identity.d.ts.map +1 -0
  386. package/dist/sandbox/identity.js +68 -0
  387. package/dist/sandbox/identity.js.map +1 -0
  388. package/dist/sandbox/image.d.ts.map +1 -1
  389. package/dist/sandbox/image.js +2 -0
  390. package/dist/sandbox/image.js.map +1 -1
  391. package/dist/sandbox/index.d.ts +17 -4
  392. package/dist/sandbox/index.d.ts.map +1 -1
  393. package/dist/sandbox/index.js +37 -3
  394. package/dist/sandbox/index.js.map +1 -1
  395. package/dist/sandbox/types.d.ts +65 -1
  396. package/dist/sandbox/types.d.ts.map +1 -1
  397. package/dist/sandbox/types.js.map +1 -1
  398. package/dist/sandbox/utils.d.ts +14 -0
  399. package/dist/sandbox/utils.d.ts.map +1 -1
  400. package/dist/sandbox/utils.js +68 -1
  401. package/dist/sandbox/utils.js.map +1 -1
  402. package/dist/sessions/chat-history-sync.d.ts +25 -0
  403. package/dist/sessions/chat-history-sync.d.ts.map +1 -0
  404. package/dist/sessions/{agent-memory-file-manager.js → chat-history-sync.js} +135 -131
  405. package/dist/sessions/chat-history-sync.js.map +1 -0
  406. package/dist/sessions/conversation-log.d.ts +9 -0
  407. package/dist/sessions/conversation-log.d.ts.map +1 -0
  408. package/dist/sessions/conversation-log.js +50 -0
  409. package/dist/sessions/conversation-log.js.map +1 -0
  410. package/dist/sessions/history-line.d.ts +29 -0
  411. package/dist/sessions/history-line.d.ts.map +1 -0
  412. package/dist/sessions/history-line.js +45 -0
  413. package/dist/sessions/history-line.js.map +1 -0
  414. package/dist/sessions/rotation.d.ts +2 -0
  415. package/dist/sessions/rotation.d.ts.map +1 -0
  416. package/dist/sessions/rotation.js +39 -0
  417. package/dist/sessions/rotation.js.map +1 -0
  418. package/dist/sessions/session-key.d.ts +33 -0
  419. package/dist/sessions/session-key.d.ts.map +1 -0
  420. package/dist/sessions/session-key.js +115 -0
  421. package/dist/sessions/session-key.js.map +1 -0
  422. package/dist/sessions/store.d.ts +15 -7
  423. package/dist/sessions/store.d.ts.map +1 -1
  424. package/dist/sessions/store.js +127 -36
  425. package/dist/sessions/store.js.map +1 -1
  426. package/dist/sessions/types.d.ts +19 -5
  427. package/dist/sessions/types.d.ts.map +1 -1
  428. package/dist/sessions/types.js.map +1 -1
  429. package/dist/settings-mutation.d.ts +40 -0
  430. package/dist/settings-mutation.d.ts.map +1 -0
  431. package/dist/settings-mutation.js +61 -0
  432. package/dist/settings-mutation.js.map +1 -0
  433. package/dist/subagent-progress.d.ts +43 -0
  434. package/dist/subagent-progress.d.ts.map +1 -0
  435. package/dist/subagent-progress.js +220 -0
  436. package/dist/subagent-progress.js.map +1 -0
  437. package/dist/tools/bash.d.ts +3 -1
  438. package/dist/tools/bash.d.ts.map +1 -1
  439. package/dist/tools/bash.js +26 -18
  440. package/dist/tools/bash.js.map +1 -1
  441. package/dist/tools/edit.d.ts.map +1 -1
  442. package/dist/tools/edit.js +14 -13
  443. package/dist/tools/edit.js.map +1 -1
  444. package/dist/tools/event.d.ts +1 -1
  445. package/dist/tools/event.d.ts.map +1 -1
  446. package/dist/tools/event.js +58 -45
  447. package/dist/tools/event.js.map +1 -1
  448. package/dist/tools/generate-image.d.ts +23 -0
  449. package/dist/tools/generate-image.d.ts.map +1 -0
  450. package/dist/tools/generate-image.js +65 -0
  451. package/dist/tools/generate-image.js.map +1 -0
  452. package/dist/tools/host-fn-tool.d.ts +21 -0
  453. package/dist/tools/host-fn-tool.d.ts.map +1 -0
  454. package/dist/tools/host-fn-tool.js +32 -0
  455. package/dist/tools/host-fn-tool.js.map +1 -0
  456. package/dist/tools/index.d.ts +19 -2
  457. package/dist/tools/index.d.ts.map +1 -1
  458. package/dist/tools/index.js +25 -2
  459. package/dist/tools/index.js.map +1 -1
  460. package/dist/tools/react.d.ts +15 -0
  461. package/dist/tools/react.d.ts.map +1 -0
  462. package/dist/tools/react.js +29 -0
  463. package/dist/tools/react.js.map +1 -0
  464. package/dist/tools/read.d.ts +1 -0
  465. package/dist/tools/read.d.ts.map +1 -1
  466. package/dist/tools/read.js +39 -33
  467. package/dist/tools/read.js.map +1 -1
  468. package/dist/tools/sandbox.d.ts +2 -2
  469. package/dist/tools/sandbox.d.ts.map +1 -1
  470. package/dist/tools/sandbox.js +14 -9
  471. package/dist/tools/sandbox.js.map +1 -1
  472. package/dist/tools/subagent-slots.d.ts +28 -0
  473. package/dist/tools/subagent-slots.d.ts.map +1 -0
  474. package/dist/tools/subagent-slots.js +72 -0
  475. package/dist/tools/subagent-slots.js.map +1 -0
  476. package/dist/tools/subagent.d.ts +54 -0
  477. package/dist/tools/subagent.d.ts.map +1 -0
  478. package/dist/tools/subagent.js +411 -0
  479. package/dist/tools/subagent.js.map +1 -0
  480. package/dist/tools/types.d.ts +40 -32
  481. package/dist/tools/types.d.ts.map +1 -1
  482. package/dist/tools/types.js.map +1 -1
  483. package/dist/tools/write.d.ts.map +1 -1
  484. package/dist/tools/write.js +2 -8
  485. package/dist/tools/write.js.map +1 -1
  486. package/dist/trigger.d.ts +2 -1
  487. package/dist/trigger.d.ts.map +1 -1
  488. package/dist/trigger.js +6 -9
  489. package/dist/trigger.js.map +1 -1
  490. package/dist/types.d.ts +403 -59
  491. package/dist/types.d.ts.map +1 -1
  492. package/dist/types.js +2 -2
  493. package/dist/types.js.map +1 -1
  494. package/dist/utils/file-guards.d.ts +9 -0
  495. package/dist/utils/file-guards.d.ts.map +1 -1
  496. package/dist/utils/file-guards.js +44 -1
  497. package/dist/utils/file-guards.js.map +1 -1
  498. package/dist/vault/disabled.d.ts +9 -0
  499. package/dist/vault/disabled.d.ts.map +1 -0
  500. package/dist/vault/disabled.js +25 -0
  501. package/dist/vault/disabled.js.map +1 -0
  502. package/dist/vault/index.d.ts +45 -2
  503. package/dist/vault/index.d.ts.map +1 -1
  504. package/dist/vault/index.js +127 -36
  505. package/dist/vault/index.js.map +1 -1
  506. package/dist/vault/types.d.ts +6 -2
  507. package/dist/vault/types.d.ts.map +1 -1
  508. package/dist/vault/types.js.map +1 -1
  509. package/dist/web/admin/portal.d.ts +31 -3
  510. package/dist/web/admin/portal.d.ts.map +1 -1
  511. package/dist/web/admin/portal.js +846 -345
  512. package/dist/web/admin/portal.js.map +1 -1
  513. package/dist/web/admin/provider-models.js +1 -1
  514. package/dist/web/admin/provider-models.js.map +1 -1
  515. package/dist/web/admin/types.d.ts +23 -3
  516. package/dist/web/admin/types.d.ts.map +1 -1
  517. package/dist/web/admin/types.js.map +1 -1
  518. package/dist/web/login/oauth.d.ts +2 -3
  519. package/dist/web/login/oauth.d.ts.map +1 -1
  520. package/dist/web/login/oauth.js +1 -32
  521. package/dist/web/login/oauth.js.map +1 -1
  522. package/dist/web/login/portal.d.ts +1 -1
  523. package/dist/web/login/portal.d.ts.map +1 -1
  524. package/dist/web/login/portal.js +13 -36
  525. package/dist/web/login/portal.js.map +1 -1
  526. package/dist/web/login/types.d.ts +0 -11
  527. package/dist/web/login/types.d.ts.map +1 -1
  528. package/dist/web/login/types.js.map +1 -1
  529. package/dist/web/portal-shell.d.ts +21 -0
  530. package/dist/web/portal-shell.d.ts.map +1 -0
  531. package/dist/{portal-shell.js → web/portal-shell.js} +67 -2
  532. package/dist/web/portal-shell.js.map +1 -0
  533. package/dist/web/server.d.ts +3 -2
  534. package/dist/web/server.d.ts.map +1 -1
  535. package/dist/web/server.js +11 -4
  536. package/dist/web/server.js.map +1 -1
  537. package/dist/web/session-view/portal.d.ts +1 -1
  538. package/dist/web/session-view/portal.d.ts.map +1 -1
  539. package/dist/web/session-view/portal.js +37 -28
  540. package/dist/web/session-view/portal.js.map +1 -1
  541. package/dist/web/session-view/service.d.ts +1 -1
  542. package/dist/web/session-view/service.d.ts.map +1 -1
  543. package/dist/web/session-view/service.js +61 -18
  544. package/dist/web/session-view/service.js.map +1 -1
  545. package/dist/web/session-view/types.d.ts +0 -3
  546. package/dist/web/session-view/types.d.ts.map +1 -1
  547. package/dist/web/session-view/types.js.map +1 -1
  548. package/dist/web/token-store.js +1 -1
  549. package/dist/web/token-store.js.map +1 -1
  550. package/dist/workspace-projection/index.d.ts +10 -0
  551. package/dist/workspace-projection/index.d.ts.map +1 -0
  552. package/dist/workspace-projection/index.js +120 -0
  553. package/dist/workspace-projection/index.js.map +1 -0
  554. package/dist/workspace-projection/types.d.ts +16 -0
  555. package/dist/workspace-projection/types.d.ts.map +1 -0
  556. package/dist/workspace-projection/types.js +2 -0
  557. package/dist/workspace-projection/types.js.map +1 -0
  558. package/package.json +67 -37
  559. package/dist/adapters/streaming.d.ts +0 -18
  560. package/dist/adapters/streaming.d.ts.map +0 -1
  561. package/dist/adapters/streaming.js +0 -44
  562. package/dist/adapters/streaming.js.map +0 -1
  563. package/dist/commands/parse.d.ts +0 -7
  564. package/dist/commands/parse.d.ts.map +0 -1
  565. package/dist/commands/parse.js +0 -14
  566. package/dist/commands/parse.js.map +0 -1
  567. package/dist/download.d.ts.map +0 -1
  568. package/dist/download.js.map +0 -1
  569. package/dist/model-registry.d.ts +0 -4
  570. package/dist/model-registry.d.ts.map +0 -1
  571. package/dist/model-registry.js +0 -7
  572. package/dist/model-registry.js.map +0 -1
  573. package/dist/portal-shell.d.ts +0 -4
  574. package/dist/portal-shell.d.ts.map +0 -1
  575. package/dist/portal-shell.js.map +0 -1
  576. package/dist/runtime/agent-run-controller.d.ts +0 -31
  577. package/dist/runtime/agent-run-controller.d.ts.map +0 -1
  578. package/dist/runtime/agent-run-controller.js +0 -195
  579. package/dist/runtime/agent-run-controller.js.map +0 -1
  580. package/dist/sandbox/path-context.d.ts +0 -3
  581. package/dist/sandbox/path-context.d.ts.map +0 -1
  582. package/dist/sandbox/path-context.js +0 -20
  583. package/dist/sandbox/path-context.js.map +0 -1
  584. package/dist/sessions/agent-memory-file-manager.d.ts +0 -19
  585. package/dist/sessions/agent-memory-file-manager.d.ts.map +0 -1
  586. package/dist/sessions/agent-memory-file-manager.js.map +0 -1
  587. package/dist/sessions/metadata.d.ts +0 -3
  588. package/dist/sessions/metadata.d.ts.map +0 -1
  589. package/dist/sessions/metadata.js +0 -11
  590. package/dist/sessions/metadata.js.map +0 -1
  591. package/dist/sessions/policy.d.ts +0 -6
  592. package/dist/sessions/policy.d.ts.map +0 -1
  593. package/dist/sessions/policy.js +0 -23
  594. package/dist/sessions/policy.js.map +0 -1
  595. package/dist/store.d.ts +0 -46
  596. package/dist/store.d.ts.map +0 -1
  597. package/dist/store.js +0 -177
  598. package/dist/store.js.map +0 -1
  599. package/dist/tools/truncate.d.ts +0 -33
  600. package/dist/tools/truncate.d.ts.map +0 -1
  601. package/dist/tools/truncate.js +0 -184
  602. package/dist/tools/truncate.js.map +0 -1
  603. package/dist/utils/date.d.ts +0 -10
  604. package/dist/utils/date.d.ts.map +0 -1
  605. package/dist/utils/date.js +0 -23
  606. package/dist/utils/date.js.map +0 -1
  607. package/dist/utils/env.d.ts +0 -3
  608. package/dist/utils/env.d.ts.map +0 -1
  609. package/dist/utils/env.js +0 -12
  610. package/dist/utils/env.js.map +0 -1
  611. package/dist/utils/fs-atomic.d.ts +0 -10
  612. package/dist/utils/fs-atomic.d.ts.map +0 -1
  613. package/dist/utils/fs-atomic.js +0 -45
  614. package/dist/utils/fs-atomic.js.map +0 -1
  615. package/dist/utils/html.d.ts +0 -2
  616. package/dist/utils/html.d.ts.map +0 -1
  617. package/dist/utils/html.js +0 -4
  618. package/dist/utils/html.js.map +0 -1
  619. package/dist/utils/http-body.d.ts +0 -10
  620. package/dist/utils/http-body.d.ts.map +0 -1
  621. package/dist/utils/http-body.js +0 -34
  622. package/dist/utils/http-body.js.map +0 -1
  623. package/dist/vault/routing.d.ts +0 -3
  624. package/dist/vault/routing.d.ts.map +0 -1
  625. package/dist/vault/routing.js +0 -13
  626. package/dist/vault/routing.js.map +0 -1
  627. package/dist/web/agent-events/portal.d.ts +0 -3
  628. package/dist/web/agent-events/portal.d.ts.map +0 -1
  629. package/dist/web/agent-events/portal.js +0 -18
  630. package/dist/web/agent-events/portal.js.map +0 -1
  631. package/dist/web/request.d.ts +0 -3
  632. package/dist/web/request.d.ts.map +0 -1
  633. package/dist/web/request.js +0 -13
  634. package/dist/web/request.js.map +0 -1
  635. package/dist/web/session-view/command.d.ts +0 -4
  636. package/dist/web/session-view/command.d.ts.map +0 -1
  637. package/dist/web/session-view/command.js +0 -7
  638. package/dist/web/session-view/command.js.map +0 -1
  639. /package/dist/{download.d.ts → cli/download.d.ts} +0 -0
package/dist/agent.js CHANGED
@@ -1,24 +1,104 @@
1
- import { DEFAULT_EVENT_BUDGET, defaultExtensionDirs, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, } from "./harness/index.js";
2
- import { createHash } from "crypto";
3
- import { existsSync, readFileSync } from "fs";
4
- import { mkdir, readFile, writeFile } from "fs/promises";
5
- import { join, posix } from "path";
1
+ /**
2
+ * Agent runner: prompt construction, path translation, run lifecycle, and createRunner.
3
+ * Single module — do not split helpers that only this file uses.
4
+ */
5
+ import {} from "@earendil-works/pi-ai";
6
+ import { DEFAULT_EVENT_BUDGET, formatSkillsForPrompt, loadExtensions, loadSkillsFromDir, MikanAgentSession, MikanModels, parseCommandInput, } from "./harness/index.js";
7
+ import { runSubagent } from "./harness/subagent-runner.js";
8
+ import { loadSubagentProfiles } from "./harness/subagent-profiles.js";
9
+ import { mergeSubagentProgress, parseSubagentProgressSnapshot, renderSubagentDashboard, settleSubagentProgress, } from "./subagent-progress.js";
10
+ import { createHash } from "node:crypto";
11
+ import { existsSync, lstatSync } from "node:fs";
12
+ import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { basename, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
6
15
  import { resolveConversationSettings } from "./config.js";
7
- import { readEnv } from "./utils/env.js";
16
+ import { resolveWorkspaceProjection } from "./workspace-projection/index.js";
17
+ import { packageSkillRuntimeDir, resolveConversationPackages } from "./packages/index.js";
8
18
  import { ActorExecutionResolver } from "./execution-resolver.js";
9
19
  import * as log from "./log.js";
10
- import { reportUserFacingError } from "./observability/sentry.js";
11
- import { createExecutor, } from "./sandbox/index.js";
12
- import { createMountedRuntimePathContext } from "./sandbox/path-context.js";
13
- import { addLifecycleBreadcrumb, metricAttributes, updateActiveSpanAttribution, } from "./observability/sentry.js";
14
- import { AgentMemoryFileManager } from "./sessions/agent-memory-file-manager.js";
20
+ import { assertSandboxSupportsWorkspacePolicy, createExecutor, getUnresolvedSandboxPathContext, } from "./sandbox/index.js";
21
+ import { addLifecycleBreadcrumb, metricAttributes, reportUserFacingError, updateActiveSpanAttribution, } from "./observability/sentry.js";
22
+ import { ChatHistorySync } from "./sessions/chat-history-sync.js";
23
+ import { formatHistoryLine } from "./sessions/history-line.js";
24
+ import { conversationIdOf, isThreadSessionKey } from "./sessions/session-key.js";
15
25
  import { extractSessionUuid, openManagedSession, } from "./sessions/store.js";
16
26
  import { HostEventStore } from "./tools/event.js";
17
- import { createMikanTools } from "./tools/index.js";
27
+ import { createMikanTools, createSubagentTool, DEFAULT_GLOBAL_SUBAGENT_SLOTS, SubagentSlotPool, } from "./tools/index.js";
28
+ // One process-wide fan-out account: per-conversation queues serialize runs,
29
+ // but each run can fan out up to the per-run cap — without this shared
30
+ // ceiling, N busy conversations hold N × cap live subagent sessions.
31
+ const globalSubagentSlots = new SubagentSlotPool(DEFAULT_GLOBAL_SUBAGENT_SLOTS);
18
32
  import * as Sentry from "@sentry/node";
19
- import { formatLocalTimestamp } from "./utils/date.js";
20
- import { resolveConfiguredModel } from "./model-registry.js";
21
33
  import { emitAgentEvent } from "./agent-events.js";
34
+ export function translateRuntimePathToHost(runtimePath, pathContext) {
35
+ return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;
36
+ }
37
+ function isWithinPathRoot(path, root) {
38
+ const pathRelative = relative(root, path);
39
+ return (pathRelative === "" ||
40
+ (pathRelative !== ".." && !pathRelative.startsWith(`..${sep}`) && !isAbsolute(pathRelative)));
41
+ }
42
+ function hasParentTraversal(path) {
43
+ return path.split(/[\\/]/).some((segment) => segment === "..");
44
+ }
45
+ export function translateAttachPathToHost(filePath, pathContext) {
46
+ if (!pathContext.runtimeToHostPath) {
47
+ throw new Error("Cannot attach files: this sandbox has no host-backed runtime path mapping; attachments are unavailable for remote sandboxes such as Cloudflare or Firecracker");
48
+ }
49
+ if (hasParentTraversal(filePath)) {
50
+ throw new Error("Cannot attach files: parent-directory traversal is not allowed");
51
+ }
52
+ const runtimeRoot = resolve(pathContext.runtimeWorkspaceRoot);
53
+ const runtimePath = posix.isAbsolute(filePath)
54
+ ? filePath
55
+ : posix.join(pathContext.runtimeWorkspaceRoot, filePath);
56
+ const normalizedRuntimePath = resolve(runtimePath);
57
+ if (!isWithinPathRoot(normalizedRuntimePath, runtimeRoot)) {
58
+ throw new Error("Cannot attach files: path must be within the runtime workspace");
59
+ }
60
+ const hostRoot = resolve(pathContext.hostWorkspaceRoot);
61
+ const translatedPath = pathContext.runtimeToHostPath(runtimePath);
62
+ const hostPath = resolve(translatedPath);
63
+ if (!isWithinPathRoot(hostPath, hostRoot)) {
64
+ throw new Error("Cannot attach files: path must be within the host workspace");
65
+ }
66
+ return hostPath;
67
+ }
68
+ /**
69
+ * Normalize an attachment path using runtime lexical semantics only. The
70
+ * executor remains the authority for reading the resulting runtime path.
71
+ */
72
+ function normalizeAttachRuntimePath(filePath, runtimeWorkspaceRoot) {
73
+ if (hasParentTraversal(filePath)) {
74
+ throw new Error("Cannot attach files: parent-directory traversal is not allowed");
75
+ }
76
+ const runtimeRoot = posix.resolve(runtimeWorkspaceRoot);
77
+ const runtimePath = posix.resolve(runtimeRoot, filePath);
78
+ const runtimeRelativePath = posix.relative(runtimeRoot, runtimePath);
79
+ if (runtimeRelativePath === ".." ||
80
+ runtimeRelativePath.startsWith("../") ||
81
+ posix.isAbsolute(runtimeRelativePath)) {
82
+ throw new Error("Cannot attach files: path must be within the runtime workspace");
83
+ }
84
+ return runtimePath;
85
+ }
86
+ async function withStagedRuntimeFile(executor, runtimePath, upload) {
87
+ const content = Buffer.from(await executor.readFileBase64(runtimePath), "base64");
88
+ let stagingDir;
89
+ try {
90
+ stagingDir = await mkdtemp(join(tmpdir(), "mikan-upload-"));
91
+ await chmod(stagingDir, 0o700);
92
+ const stagedPath = join(stagingDir, basename(runtimePath));
93
+ await writeFile(stagedPath, content, { mode: 0o600, flag: "wx" });
94
+ await chmod(stagedPath, 0o600);
95
+ await upload(stagedPath);
96
+ }
97
+ finally {
98
+ if (stagingDir)
99
+ await rm(stagingDir, { recursive: true, force: true });
100
+ }
101
+ }
22
102
  const IMAGE_MIME_TYPES = {
23
103
  jpg: "image/jpeg",
24
104
  jpeg: "image/jpeg",
@@ -29,18 +109,51 @@ const IMAGE_MIME_TYPES = {
29
109
  function getImageMimeType(filename) {
30
110
  return IMAGE_MIME_TYPES[filename.toLowerCase().split(".").pop() || ""];
31
111
  }
32
- function buildThreadSessionName(message) {
33
- const text = message?.text?.trim();
34
- if (!text)
35
- return undefined;
36
- const userLabel = message?.userName || message?.user || "unknown";
37
- return `[${userLabel}]: ${text}`;
112
+ function formatTimestampedUserMessage(message) {
113
+ return formatHistoryLine({
114
+ date: new Date(),
115
+ userName: message.userName,
116
+ threadTs: message.threadTs,
117
+ text: message.text,
118
+ });
119
+ }
120
+ async function collectMessageAttachments(message, workspacePath, pathContext, readAttachment) {
121
+ const imageAttachments = [];
122
+ const nonImagePaths = [];
123
+ for (const attachment of message.attachments || []) {
124
+ const runtimePath = `${workspacePath}/${attachment.localPath}`;
125
+ const hostPath = pathContext?.runtimeToHostPath?.(runtimePath) ?? runtimePath;
126
+ const mimeType = getImageMimeType(attachment.localPath);
127
+ if (mimeType && existsSync(hostPath) && readAttachment) {
128
+ try {
129
+ imageAttachments.push({
130
+ type: "image",
131
+ mimeType,
132
+ data: await readAttachment(runtimePath),
133
+ });
134
+ }
135
+ catch {
136
+ nonImagePaths.push(runtimePath);
137
+ }
138
+ }
139
+ else {
140
+ nonImagePaths.push(runtimePath);
141
+ }
142
+ }
143
+ return { imageAttachments, nonImagePaths };
144
+ }
145
+ export async function buildPromptPayload(message, workspacePath, pathContext, readAttachment) {
146
+ let userMessage = formatTimestampedUserMessage(message);
147
+ const { imageAttachments, nonImagePaths } = await collectMessageAttachments(message, workspacePath, pathContext, readAttachment);
148
+ if (nonImagePaths.length > 0) {
149
+ userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
150
+ }
151
+ return { userMessage, imageAttachments };
38
152
  }
39
- async function getMemory(conversationDir) {
153
+ async function getMemory(projection) {
40
154
  const parts = [];
41
- // Read workspace-level memory (shared across all conversations)
42
- const workspaceMemoryPath = join(conversationDir, "..", "MEMORY.md");
43
- if (existsSync(workspaceMemoryPath)) {
155
+ const workspaceMemoryPath = projection.promptSources.globalMemoryPath;
156
+ if (workspaceMemoryPath && isRegularFile(workspaceMemoryPath)) {
44
157
  try {
45
158
  const content = (await readFile(workspaceMemoryPath, "utf-8")).trim();
46
159
  if (content) {
@@ -51,9 +164,8 @@ async function getMemory(conversationDir) {
51
164
  log.logWarning("Failed to read workspace memory", `${workspaceMemoryPath}: ${error}`);
52
165
  }
53
166
  }
54
- // Read conversation-specific memory
55
- const conversationMemoryPath = join(conversationDir, "MEMORY.md");
56
- if (existsSync(conversationMemoryPath)) {
167
+ const conversationMemoryPath = projection.promptSources.conversationMemoryPath;
168
+ if (isRegularFile(conversationMemoryPath)) {
57
169
  try {
58
170
  const content = (await readFile(conversationMemoryPath, "utf-8")).trim();
59
171
  if (content) {
@@ -69,12 +181,20 @@ async function getMemory(conversationDir) {
69
181
  }
70
182
  return parts.join("\n\n");
71
183
  }
72
- function loadMikanSkills(conversationDir, workspacePath) {
184
+ function isRegularFile(path) {
185
+ try {
186
+ const stats = lstatSync(path);
187
+ return stats.isFile() && !stats.isSymbolicLink();
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ }
193
+ function loadMikanSkills(office, workspacePath, projection) {
73
194
  const skillMap = new Map();
74
- // conversationDir is the host path (e.g., /Users/.../data/C0A34FL8PMH)
75
- // hostWorkspacePath is the parent directory on host
76
- // workspacePath is the container path (e.g., /workspace)
77
- const hostWorkspacePath = join(conversationDir, "..");
195
+ // workspacePath is the runtime-side root (e.g. /workspace); host paths under
196
+ // the workspace root translate onto it for prompt references.
197
+ const hostWorkspacePath = office.workspace.root;
78
198
  // Helper to translate host paths to container paths
79
199
  const translatePath = (hostPath) => {
80
200
  if (hostPath.startsWith(hostWorkspacePath)) {
@@ -82,26 +202,66 @@ function loadMikanSkills(conversationDir, workspacePath) {
82
202
  }
83
203
  return hostPath;
84
204
  };
85
- // Load workspace-level skills (global)
86
- const workspaceSkillsDir = join(hostWorkspacePath, "skills");
87
- for (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: "workspace" }).skills) {
88
- // Translate paths to container paths for system prompt
89
- skill.filePath = translatePath(skill.filePath);
90
- skill.baseDir = translatePath(skill.baseDir);
91
- skillMap.set(skill.name, skill);
205
+ // Package skills first: they are the lowest precedence, so a workspace or
206
+ // conversation skill of the same name below overwrites them. A package can
207
+ // offer a capability without taking the name away from whoever wants to
208
+ // replace it locally.
209
+ //
210
+ // Unlike extension-shipped skills, these are NOT inlined into the prompt:
211
+ // conversationPackageSkillMounts exposes them read-only at a runtime path,
212
+ // so the agent can read the files — including any scripts or templates the
213
+ // skill ships alongside its SKILL.md, which inlining could never carry.
214
+ const mounted = workspacePath !== hostWorkspacePath;
215
+ for (const { slug, dir } of resolveConversationPackages({ office }).skillDirs) {
216
+ const runtimeDir = packageSkillRuntimeDir(slug);
217
+ for (const skill of loadSkillsFromDir({ dir, source: `package:${slug}` }).skills) {
218
+ // In host mode there is no mount, so the host path is already the path
219
+ // the agent will use.
220
+ if (mounted) {
221
+ skill.filePath = runtimeDir + skill.filePath.slice(dir.length);
222
+ skill.baseDir = runtimeDir + skill.baseDir.slice(dir.length);
223
+ }
224
+ skillMap.set(skill.name, skill);
225
+ }
92
226
  }
93
- // Load conversation-specific skills (override workspace skills on collision)
94
- const conversationSkillsDir = join(conversationDir, "skills");
95
- for (const skill of loadSkillsFromDir({ dir: conversationSkillsDir, source: "channel" }).skills) {
227
+ // Load workspace-level skills only when the office projection authorizes it.
228
+ const workspaceSkillsDir = projection.promptSources.globalSkillsDir;
229
+ if (workspaceSkillsDir) {
230
+ for (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: "workspace" })
231
+ .skills) {
232
+ skill.filePath = translatePath(skill.filePath);
233
+ skill.baseDir = translatePath(skill.baseDir);
234
+ skillMap.set(skill.name, skill);
235
+ }
236
+ }
237
+ // Load conversation-specific skills (override workspace skills on collision).
238
+ // Host prompt construction must never follow an agent-created symlink out of
239
+ // the conversation office, so this load rejects symlinks per entry — on
240
+ // exactly the paths it reads. Vendored node_modules and dot directories are
241
+ // never read and never disqualify anything.
242
+ const conversationSkillsDir = projection.promptSources.conversationSkillsDir;
243
+ const conversationSkills = loadSkillsFromDir({
244
+ dir: conversationSkillsDir,
245
+ source: "channel",
246
+ rejectSymlinks: true,
247
+ });
248
+ const skippedSkillLinks = [];
249
+ for (const diagnostic of conversationSkills.diagnostics) {
250
+ if (diagnostic.code !== "symlink")
251
+ continue;
252
+ log.logWarning("Skipping conversation skill entry (symlink)", diagnostic.path);
253
+ skippedSkillLinks.push(translatePath(diagnostic.path));
254
+ }
255
+ for (const skill of conversationSkills.skills) {
96
256
  skill.filePath = translatePath(skill.filePath);
97
257
  skill.baseDir = translatePath(skill.baseDir);
98
258
  skillMap.set(skill.name, skill);
99
259
  }
100
- return Array.from(skillMap.values());
260
+ return { skills: Array.from(skillMap.values()), skippedSkillLinks };
101
261
  }
102
- function buildRuntimePaths(runtimeWorkspaceRoot, conversationId) {
262
+ function buildRuntimePaths(runtimeWorkspaceRoot, office) {
103
263
  const workspaceRoot = runtimeWorkspaceRoot.replace(/\/+$/, "") || "/";
104
- const conversationPath = posix.join(workspaceRoot, conversationId);
264
+ const conversationPath = posix.join(workspaceRoot, office.key);
105
265
  return {
106
266
  workspaceRoot,
107
267
  conversationPath,
@@ -111,7 +271,7 @@ function buildRuntimePaths(runtimeWorkspaceRoot, conversationId) {
111
271
  function buildEnvDescription(sandboxType, workspaceRoot) {
112
272
  switch (sandboxType) {
113
273
  case "image":
114
- return `You are running inside a managed per-user container.
274
+ return `You are running inside a managed per-conversation container.
115
275
  - Runtime workspace root: ${workspaceRoot}
116
276
  - Bash commands start in: ${workspaceRoot}
117
277
  - Install tools with the image's package manager
@@ -152,8 +312,8 @@ export function resolveTriggerAttribution(message) {
152
312
  return `@${message.userName}`;
153
313
  return undefined;
154
314
  }
155
- function buildSystemPrompt(workspacePath, conversationId, conversationKind, currentUserId, memory, sandboxConfig, platform, skills) {
156
- const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, conversationId);
315
+ function buildSystemPrompt(workspacePath, office, conversationKind, currentUserId, memory, sandboxConfig, platform, skills, projection, skippedSkillLinks = []) {
316
+ const { workspaceRoot, conversationPath, scratchPath } = buildRuntimePaths(workspacePath, office);
157
317
  const sandboxType = sandboxConfig.type;
158
318
  const isContainerLike = sandboxType === "container" || sandboxType === "image";
159
319
  const isFirecracker = sandboxType === "firecracker";
@@ -171,11 +331,26 @@ function buildSystemPrompt(workspacePath, conversationId, conversationKind, curr
171
331
  // system prompt stays byte-stable across a conversation's turns and keeps the
172
332
  // provider prompt cache warm (a changing system prefix invalidates the cache
173
333
  // for the whole request, including the far larger conversation history).
334
+ const workspaceLayout = projection.layout === "full"
335
+ ? `${workspaceRoot}/ contains the complete trusted workspace.`
336
+ : projection.layout === "shared-support"
337
+ ? `${workspaceRoot}/ contains shared MEMORY.md, skills/, events/, and this conversation's directory.`
338
+ : `${conversationPath}/ is the only conversation workspace mounted; global memory, skills, and events are not available.`;
339
+ const skillStorageGuidance = projection.doorPolicy === "trusted"
340
+ ? `Store shared skills in \`${workspaceRoot}/skills/<name>/\` or conversation-specific skills in \`${conversationPath}/skills/<name>/\`.`
341
+ : `Store skills in \`${conversationPath}/skills/<name>/\`; this office cannot access workspace-global skills.`;
342
+ const memoryGuidance = projection.doorPolicy === "trusted"
343
+ ? `Write important shared knowledge to \`${workspaceRoot}/MEMORY.md\` and conversation-specific knowledge to \`${conversationPath}/MEMORY.md\`.`
344
+ : `Write durable knowledge only to \`${conversationPath}/MEMORY.md\`; this office cannot access workspace-global memory.`;
345
+ const systemLogPath = projection.layout === "conversation"
346
+ ? `${conversationPath}/SYSTEM.md`
347
+ : `${workspaceRoot}/SYSTEM.md`;
174
348
  const slackBlockKitInstructions = platform.name === "slack"
175
349
  ? `
176
350
  ## Slack Rendering
177
- - The Slack adapter renders responses with Block Kit automatically. Answer normally using Slack mrkdwn.
178
- - Do not emit markdown pipe tables; Slack does not render them as tables. Prefer short sections or bullet lists.
351
+ - The Slack adapter renders responses natively from standard Markdown. Answer in normal Markdown/GFM.
352
+ - Markdown pipe tables are rendered as native Slack tables.
353
+ - For interactive elements (buttons, select menus), use the slack_blockkit tool; user interactions arrive as "[Slack action] <action_id>: <value>" messages.
179
354
  `
180
355
  : "";
181
356
  return `You are mikan, a ${platform.name} bot assistant. Be concise. No emojis.
@@ -196,7 +371,7 @@ Channels: ${channelMappings}
196
371
 
197
372
  Users: ${userMappings}
198
373
 
199
- When mentioning users, use <@username> format (e.g., <@mario>).
374
+ When mentioning users, write <@userName> using the exact userName from the Users table above (e.g., <@mario>). Never invent handles from other platforms (GitHub, email); the platform adapter converts <@userName> to the platform's native mention form.
200
375
 
201
376
  ## Environment
202
377
  ${envDescription}
@@ -204,10 +379,13 @@ ${envDescription}
204
379
  - Do not use host-only paths unless you are running in host mode and verified they exist.
205
380
 
206
381
  ## Workspace Layout
207
- ${workspaceRoot}/
382
+ ${workspaceLayout}
383
+ ${projection.layout === "conversation"
384
+ ? `${conversationPath}/ # This conversation`
385
+ : `${workspaceRoot}/
208
386
  ├── MEMORY.md # Global memory (all conversations)
209
387
  ├── skills/ # Global CLI tools you create
210
- └── ${conversationId}/ # This conversation
388
+ └── ${office.key}/ # This conversation`}
211
389
  ├── MEMORY.md # Conversation-specific memory
212
390
  ├── log.jsonl # Human-readable message history (no tool results)
213
391
  ├── sessions/ # Structured session history used for context reconstruction
@@ -222,7 +400,7 @@ ${workspaceRoot}/
222
400
  You can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).
223
401
 
224
402
  ### Creating Skills
225
- Store in \`${workspaceRoot}/skills/<name>/\` (global) or \`${conversationPath}/skills/<name>/\` (conversation-specific).
403
+ ${skillStorageGuidance}
226
404
  Each skill directory needs a \`SKILL.md\` with YAML frontmatter:
227
405
 
228
406
  \`\`\`markdown
@@ -240,7 +418,9 @@ Scripts are in: {baseDir}/
240
418
  \`name\` and \`description\` are required. Use \`{baseDir}\` as placeholder for the skill's directory path.
241
419
 
242
420
  ### Available Skills
243
- ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}
421
+ ${skills.length > 0 ? formatSkillsForPrompt(skills) : "(no skills installed yet)"}${skippedSkillLinks.length > 0
422
+ ? `\n\nNote: these skill entries were skipped because they are symlinks, which the host never follows when reading this conversation's skills: ${skippedSkillLinks.join(", ")}. Replace each with a real file or directory (for example \`cp -rL\`) to load it.`
423
+ : ""}
244
424
 
245
425
  ## Events
246
426
  Use the \`event\` tool to schedule immediate, one-shot, or periodic follow-ups. It writes to the host-side mikan control plane and fills routing fields for the current conversation automatically.
@@ -254,16 +434,14 @@ When events trigger, messages are prefixed like \`[EVENT:filename:type:time]\`.
254
434
  For periodic events where there's nothing to report, respond with exactly \`[SILENT]\`. Debounce external triggers; prefer one summarized event over many.
255
435
 
256
436
  ## Memory
257
- Write to MEMORY.md files to persist context across conversations.
258
- - Global (${workspaceRoot}/MEMORY.md): skills, preferences, project info
259
- - Conversation (${conversationPath}/MEMORY.md): conversation-specific decisions, ongoing work
260
- Update when you learn something important or when asked to remember something.
437
+ ${memoryGuidance}
438
+ Update it when you learn something important or when asked to remember something.
261
439
 
262
440
  ### Current Memory
263
441
  ${memory}
264
442
 
265
443
  ## System Configuration Log
266
- Maintain ${workspaceRoot}/SYSTEM.md to log all environment modifications:
444
+ Maintain ${systemLogPath} to log all environment modifications:
267
445
  - Installed packages (apt install, npm install, uv pip install)
268
446
  - Environment variables set
269
447
  - Config files modified (~/.gitconfig, cron jobs, etc.)
@@ -329,81 +507,21 @@ Do not add this to \`[SILENT]\` responses.`);
329
507
  }
330
508
  return parts.join("\n\n");
331
509
  }
332
- export function getUnresolvedSandboxPathContext(sandboxConfig, hostWorkspaceRoot) {
333
- if (sandboxConfig.type === "image") {
334
- return createMountedRuntimePathContext(hostWorkspaceRoot, "/workspace");
335
- }
336
- return createExecutor(sandboxConfig).getPathContext(hostWorkspaceRoot);
337
- }
338
- function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, hostWorkspacePath) {
339
- const executionResolver = vaultManager && sandboxConfig.type !== "host"
340
- ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspaceDir)
341
- : undefined;
342
- // activeExecutor is replaced at the start of each run() call when executionResolver
343
- // is present, so the stable `executor` wrapper always delegates to the latest resolved value.
344
- let activeExecutor = executionResolver !== undefined
345
- ? createExecutor({ type: "host" })
346
- : createExecutor(sandboxConfig);
347
- const executor = {
348
- exec(command, options) {
349
- return activeExecutor.exec(command, options);
350
- },
351
- getWorkspacePath(hostPath) {
352
- return activeExecutor.getWorkspacePath(hostPath);
353
- },
354
- getSandboxConfig() {
355
- return activeExecutor.getSandboxConfig();
356
- },
357
- getPathContext(hostWorkspaceRoot) {
358
- return activeExecutor.getPathContext(hostWorkspaceRoot);
359
- },
360
- };
361
- return {
362
- executionResolver,
363
- executor,
364
- getPathContext: () => executor.getPathContext(hostWorkspacePath),
365
- async resolveExecutorForRun(context) {
366
- if (!executionResolver)
367
- return;
368
- activeExecutor = await executionResolver.resolve(context);
369
- },
370
- };
371
- }
372
- /**
373
- * Extension host services over mikan's runtime infrastructure: schedules
374
- * become event files under `<workspaceDir>/events` (picked up live by
375
- * EventsWatcher), secrets come from `vaults/extensions/<slug>/env`, and
376
- * notify posts through the platform bots when main.ts provides a notifier.
377
- */
378
- function buildExtensionHostServices(params) {
379
- const { workspaceDir, vaultManager, platformNotifier } = params;
380
- const eventStore = HostEventStore.fromWorkspaceDir(workspaceDir);
381
- return {
382
- stateDir: readEnv("STATE_DIR"),
383
- scheduleStore: {
384
- write: async (filename, payload) => {
385
- // Event files tolerate a missing platform (single-platform default),
386
- // so the harness payload is a valid event payload as written.
387
- await eventStore.write(filename, payload);
388
- },
389
- delete: async (filename) => (await eventStore.delete(filename)).deleted,
390
- list: async () => (await eventStore.list()).map((entry) => ({
391
- filename: entry.filename,
392
- payload: entry.payload,
393
- })),
394
- },
395
- ...(platformNotifier ? { postMessage: platformNotifier } : {}),
396
- ...(vaultManager
397
- ? {
398
- resolveSecrets: (slug) => vaultManager.resolve(`extensions/${slug}`)?.env ?? {},
399
- }
400
- : {}),
401
- };
510
+ export function appendTriggerAttribution(text, triggerAttribution, sessionLink) {
511
+ if (!triggerAttribution)
512
+ return text;
513
+ const trimmed = text.trimEnd();
514
+ const legacySuffix = `_Triggered by ${triggerAttribution}_`;
515
+ // Slack mrkdwn italics cannot span a URL — a `_..._` wrapping a link renders
516
+ // the underscores literally. Keep the session link outside the italic span.
517
+ const suffix = sessionLink ? `${legacySuffix} · session: ${sessionLink}` : legacySuffix;
518
+ if (trimmed.endsWith(suffix))
519
+ return text;
520
+ const body = trimmed.endsWith(legacySuffix)
521
+ ? trimmed.slice(0, -legacySuffix.length).trimEnd()
522
+ : trimmed;
523
+ return `${body}\n\n${suffix}`;
402
524
  }
403
- /**
404
- * Local (workspace/conversation) skills override extension skills on name
405
- * collision: an admin's on-disk skill always beats an extension's.
406
- */
407
525
  function mergeExtensionSkills(local, extension) {
408
526
  if (extension.length === 0)
409
527
  return local;
@@ -414,37 +532,6 @@ function mergeExtensionSkills(local, extension) {
414
532
  byName.set(skill.name, skill);
415
533
  return [...byName.values()];
416
534
  }
417
- async function createConfiguredAgentSession(params) {
418
- const { conversationId, workspaceDir, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, } = params;
419
- // Host-only dirs under the state dir: extension code runs in the mikan
420
- // process, so it must never load from workspace paths — those are mounted
421
- // into sandbox containers and agent-writable (sandbox escape otherwise).
422
- const extensionsResult = await loadExtensions({
423
- dirs: defaultExtensionDirs(conversationId, readEnv("STATE_DIR")),
424
- context: { conversationId, workspaceDir, model, thinkingLevel },
425
- services: buildExtensionHostServices({ workspaceDir, vaultManager, platformNotifier }),
426
- });
427
- for (const err of extensionsResult.errors) {
428
- log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
429
- }
430
- if (extensionsResult.extensions.length > 0) {
431
- log.logInfo(`[${conversationId}] Loaded ${extensionsResult.extensions.length} extension(s): ${extensionsResult.extensions.map((extension) => extension.name).join(", ")}`);
432
- }
433
- const session = new MikanAgentSession({
434
- systemPrompt,
435
- model,
436
- thinkingLevel,
437
- tools,
438
- models,
439
- sessionStore,
440
- extensions: extensionsResult.registry,
441
- });
442
- const reloaded = session.reloadFromSession();
443
- if (reloaded > 0) {
444
- log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from session context`);
445
- }
446
- return { session, extensionSkills: extensionsResult.skills };
447
- }
448
535
  function createEmptyUsageTotals() {
449
536
  return {
450
537
  input: 0,
@@ -461,6 +548,13 @@ function createRunState() {
461
548
  queue: null,
462
549
  pendingTools: new Map(),
463
550
  toolProgress: new Map(),
551
+ subagentProgress: new Map(),
552
+ completedSubagentProgress: [],
553
+ subagentToolCalls: new Set(),
554
+ subagentProgressShown: false,
555
+ suppressResponseDeltas: false,
556
+ lastSubagentProgressAt: 0,
557
+ toolProgressTimer: undefined,
464
558
  totalUsage: createEmptyUsageTotals(),
465
559
  llmCallCount: 0,
466
560
  stopReason: "stop",
@@ -480,6 +574,15 @@ function resetRunState(runState, responder, sessionConversation, userName, sessi
480
574
  };
481
575
  runState.pendingTools.clear();
482
576
  runState.toolProgress.clear();
577
+ runState.subagentProgress.clear();
578
+ runState.completedSubagentProgress = [];
579
+ runState.subagentToolCalls.clear();
580
+ runState.subagentProgressShown = false;
581
+ runState.suppressResponseDeltas = false;
582
+ runState.lastSubagentProgressAt = 0;
583
+ if (runState.toolProgressTimer)
584
+ clearTimeout(runState.toolProgressTimer);
585
+ runState.toolProgressTimer = undefined;
483
586
  runState.totalUsage = createEmptyUsageTotals();
484
587
  runState.llmCallCount = 0;
485
588
  runState.stopReason = "stop";
@@ -515,53 +618,6 @@ function createRunQueue(responder, runState) {
515
618
  wait: () => queueChain,
516
619
  };
517
620
  }
518
- function formatTimestampedUserMessage(message) {
519
- const timestamp = formatLocalTimestamp(new Date());
520
- const threadContext = message.threadTs ? ` [in-thread:${message.threadTs}]` : "";
521
- return `[${timestamp}] [${message.userName || "unknown"}]${threadContext}: ${message.text}`;
522
- }
523
- function collectMessageAttachments(message, workspacePath, pathContext) {
524
- const imageAttachments = [];
525
- const nonImagePaths = [];
526
- for (const attachment of message.attachments || []) {
527
- const runtimePath = `${workspacePath}/${attachment.localPath}`;
528
- const hostPath = pathContext?.runtimeToHostPath?.(runtimePath) ?? runtimePath;
529
- const mimeType = getImageMimeType(attachment.localPath);
530
- if (mimeType && existsSync(hostPath)) {
531
- try {
532
- imageAttachments.push({
533
- type: "image",
534
- mimeType,
535
- data: readFileSync(hostPath).toString("base64"),
536
- });
537
- }
538
- catch {
539
- nonImagePaths.push(runtimePath);
540
- }
541
- }
542
- else {
543
- nonImagePaths.push(runtimePath);
544
- }
545
- }
546
- return { imageAttachments, nonImagePaths };
547
- }
548
- export function buildPromptPayload(message, workspacePath, pathContext) {
549
- let userMessage = formatTimestampedUserMessage(message);
550
- const { imageAttachments, nonImagePaths } = collectMessageAttachments(message, workspacePath, pathContext);
551
- if (nonImagePaths.length > 0) {
552
- userMessage += `\n\n<slack_attachments>\n${nonImagePaths.join("\n")}\n</slack_attachments>`;
553
- }
554
- return { userMessage, imageAttachments };
555
- }
556
- async function writePromptDebugContext(conversationDir, systemPrompt, session, userMessage, imageAttachmentCount) {
557
- const debugContext = {
558
- systemPrompt,
559
- messages: session.messages,
560
- newUserMessage: userMessage,
561
- imageAttachmentCount,
562
- };
563
- await writeFile(join(conversationDir, "last_prompt.jsonl"), JSON.stringify(debugContext, null, 2));
564
- }
565
621
  function getFinalAssistantText(session) {
566
622
  const lastAssistant = session.messages.findLast((message) => message.role === "assistant");
567
623
  return (lastAssistant?.content
@@ -569,21 +625,6 @@ function getFinalAssistantText(session) {
569
625
  .map((content) => content.text)
570
626
  .join("\n") || "");
571
627
  }
572
- export function appendTriggerAttribution(text, triggerAttribution, sessionLink) {
573
- if (!triggerAttribution)
574
- return text;
575
- const trimmed = text.trimEnd();
576
- const legacySuffix = `_Triggered by ${triggerAttribution}_`;
577
- const suffix = sessionLink
578
- ? `_Triggered by ${triggerAttribution} · session: ${sessionLink}_`
579
- : legacySuffix;
580
- if (trimmed.endsWith(suffix))
581
- return text;
582
- const body = trimmed.endsWith(legacySuffix)
583
- ? trimmed.slice(0, -legacySuffix.length).trimEnd()
584
- : trimmed;
585
- return `${body}\n\n${suffix}`;
586
- }
587
628
  function isEventTriggerAttribution(triggerAttribution) {
588
629
  return triggerAttribution?.startsWith("[event:") === true;
589
630
  }
@@ -593,22 +634,86 @@ function extractToolLabel(toolName, args) {
593
634
  return toolName;
594
635
  return label.trim() || toolName;
595
636
  }
637
+ /** Non-subagent tool activity lines; subagent calls render as the dashboard. */
596
638
  function formatToolProgress(runState) {
597
- const lines = Array.from(runState.toolProgress.values()).map((item) => {
639
+ const lines = Array.from(runState.toolProgress.entries()).flatMap(([toolCallId, item]) => {
640
+ if (runState.subagentToolCalls.has(toolCallId))
641
+ return [];
598
642
  const marker = item.status === "running" ? "•" : item.status === "error" ? "✗" : "✓";
599
- return `${marker} ${item.label}`;
643
+ return [`${marker} ${item.label}`];
600
644
  });
601
645
  return lines.join("\n");
602
646
  }
603
647
  function formatResponseWithToolProgress(text, runState) {
604
648
  const progress = formatToolProgress(runState);
605
- return progress ? `${progress}\n\n${text}` : text;
649
+ return [progress, text].filter(Boolean).join("\n\n");
606
650
  }
607
- async function replaceResponseWithToolProgress(responder, runState) {
651
+ /**
652
+ * The one dashboard render path. A responder that overrides
653
+ * `replaceSubagentProgress` converts the snapshot for a pipeline that is not
654
+ * response-source Markdown (Telegram HTML); every other platform receives the
655
+ * Markdown dashboard — "dashboard, blank line, answer" — through
656
+ * `replaceResponse`, the same conversion as any response.
657
+ */
658
+ async function replaceWithSubagentDashboard(responder, snapshot, finalText, options) {
659
+ if (responder.replaceSubagentProgress) {
660
+ await responder.replaceSubagentProgress(snapshot, finalText);
661
+ return;
662
+ }
663
+ const dashboard = renderSubagentDashboard(snapshot);
664
+ await responder.replaceResponse(finalText ? `${dashboard}\n\n${finalText}` : dashboard, options);
665
+ }
666
+ async function replaceResponseWithToolProgress(responder, runState, subagentProgress = mergeSubagentProgress([...runState.subagentProgress.values()])) {
667
+ if (subagentProgress) {
668
+ await replaceWithSubagentDashboard(responder, subagentProgress);
669
+ return;
670
+ }
608
671
  const progress = formatToolProgress(runState);
609
672
  if (progress)
610
673
  await responder.replaceResponse(progress);
611
674
  }
675
+ const TOOL_PROGRESS_DEBOUNCE_MS = 500;
676
+ const SUBAGENT_PROGRESS_THROTTLE_MS = 2000;
677
+ function subagentProgressDelay(runState) {
678
+ if (!runState.subagentProgressShown)
679
+ return 0;
680
+ return Math.max(0, runState.lastSubagentProgressAt + SUBAGENT_PROGRESS_THROTTLE_MS - Date.now());
681
+ }
682
+ function scheduleToolProgressUpdate(responder, runState) {
683
+ if (runState.toolProgressTimer)
684
+ return;
685
+ const subagentProgress = mergeSubagentProgress([...runState.subagentProgress.values()]);
686
+ const delay = subagentProgress ? subagentProgressDelay(runState) : TOOL_PROGRESS_DEBOUNCE_MS;
687
+ runState.toolProgressTimer = setTimeout(() => {
688
+ runState.toolProgressTimer = undefined;
689
+ const snapshot = mergeSubagentProgress([...runState.subagentProgress.values()]);
690
+ if (snapshot) {
691
+ runState.subagentProgressShown = true;
692
+ runState.lastSubagentProgressAt = Date.now();
693
+ }
694
+ runState.queue?.enqueue(() => replaceResponseWithToolProgress(responder, runState, snapshot), "tool progress update");
695
+ }, delay);
696
+ runState.toolProgressTimer.unref();
697
+ }
698
+ function flushToolProgressUpdate(responder, runState) {
699
+ if (runState.toolProgressTimer)
700
+ clearTimeout(runState.toolProgressTimer);
701
+ runState.toolProgressTimer = undefined;
702
+ const subagentProgress = mergeSubagentProgress([...runState.subagentProgress.values()]);
703
+ if (subagentProgress) {
704
+ runState.subagentProgressShown = true;
705
+ runState.lastSubagentProgressAt = Date.now();
706
+ }
707
+ runState.queue?.enqueue(() => replaceResponseWithToolProgress(responder, runState, subagentProgress), "tool progress update");
708
+ }
709
+ function extractSubagentProgress(partialResult) {
710
+ if (!partialResult || typeof partialResult !== "object")
711
+ return undefined;
712
+ const details = partialResult.details;
713
+ if (!details || typeof details !== "object")
714
+ return undefined;
715
+ return parseSubagentProgressSnapshot(details.progress);
716
+ }
612
717
  async function finalizeRunResponse(responder, session, runState, options) {
613
718
  if (runState.stopReason === "error" && runState.errorMessage) {
614
719
  if (!runState.reportedLlmError) {
@@ -673,7 +778,17 @@ async function finalizeRunResponse(responder, session, runState, options) {
673
778
  if (!finalText.trim())
674
779
  return;
675
780
  try {
676
- await responder.replaceResponse(appendTriggerAttribution(formatResponseWithToolProgress(finalText, runState), options?.triggerAttribution, options?.triggerSessionLink), { createOverflowLink: options?.createOverflowLink });
781
+ const finalResponse = appendTriggerAttribution(finalText, options?.triggerAttribution, options?.triggerSessionLink);
782
+ const finalDashboard = mergeSubagentProgress(runState.completedSubagentProgress);
783
+ if (finalDashboard) {
784
+ await replaceWithSubagentDashboard(responder, finalDashboard, finalResponse, {
785
+ createOverflowLink: options?.createOverflowLink,
786
+ });
787
+ return;
788
+ }
789
+ await responder.replaceResponse(formatResponseWithToolProgress(finalResponse, runState), {
790
+ createOverflowLink: options?.createOverflowLink,
791
+ });
677
792
  }
678
793
  catch (err) {
679
794
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -739,6 +854,225 @@ async function reportUsageSummary(ctx) {
739
854
  await waitForQueue();
740
855
  }
741
856
  }
857
+ function extractToolResultText(result) {
858
+ if (typeof result === "string") {
859
+ return result;
860
+ }
861
+ if (result &&
862
+ typeof result === "object" &&
863
+ "content" in result &&
864
+ Array.isArray(result.content)) {
865
+ const content = result.content;
866
+ const textParts = [];
867
+ for (const part of content) {
868
+ if (part.type === "text" && part.text) {
869
+ textParts.push(part.text);
870
+ }
871
+ }
872
+ if (textParts.length > 0) {
873
+ return textParts.join("\n");
874
+ }
875
+ }
876
+ return JSON.stringify(result);
877
+ }
878
+ function buildThreadSessionName(message) {
879
+ const text = message?.text?.trim();
880
+ if (!text)
881
+ return undefined;
882
+ const userLabel = message?.userName || message?.user || "unknown";
883
+ return `[${userLabel}]: ${text}`;
884
+ }
885
+ function createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspace) {
886
+ const executionResolver = vaultManager && sandboxConfig.type !== "host"
887
+ ? new ActorExecutionResolver(sandboxConfig, vaultManager, provisioner, workspace)
888
+ : undefined;
889
+ // activeExecutor is replaced at the start of each run() call when executionResolver
890
+ // is present, so the stable `executor` wrapper always delegates to the latest resolved value.
891
+ let activeExecutor = executionResolver !== undefined
892
+ ? createExecutor({ type: "host" })
893
+ : createExecutor(sandboxConfig);
894
+ const executor = {
895
+ exec(command, options) {
896
+ return activeExecutor.exec(command, options);
897
+ },
898
+ readFile(path, options) {
899
+ return activeExecutor.readFile(path, options);
900
+ },
901
+ readFileBase64(path, options) {
902
+ return activeExecutor.readFileBase64(path, options);
903
+ },
904
+ writeFile(path, content, options) {
905
+ return activeExecutor.writeFile(path, content, options);
906
+ },
907
+ getWorkspacePath(hostPath) {
908
+ return activeExecutor.getWorkspacePath(hostPath);
909
+ },
910
+ getSandboxConfig() {
911
+ return activeExecutor.getSandboxConfig();
912
+ },
913
+ getPathContext(hostWorkspaceRoot) {
914
+ return activeExecutor.getPathContext(hostWorkspaceRoot);
915
+ },
916
+ };
917
+ return {
918
+ executionResolver,
919
+ executor,
920
+ getPathContext: () => executor.getPathContext(workspace.root),
921
+ async resolveExecutorForRun(context) {
922
+ if (!executionResolver)
923
+ return;
924
+ activeExecutor = await executionResolver.resolve(context);
925
+ },
926
+ };
927
+ }
928
+ /**
929
+ * Extension host services over mikan's runtime infrastructure: schedules
930
+ * become event files under `<workspaceDir>/events` (picked up live by
931
+ * EventsWatcher), secrets come from `vaults/extensions/<slug>/env`, and
932
+ * notify posts through the platform bots when main.ts provides a notifier.
933
+ */
934
+ function buildExtensionHostServices(params) {
935
+ const { workspace, address, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformDmOpener, platformHistoryFetcher, platformUserLister, extensionScheduleEngine, runSubagentService, } = params;
936
+ const eventStore = HostEventStore.fromWorkspaceDir(workspace.root);
937
+ return {
938
+ stateDir: workspace.stateDir,
939
+ scheduleStore: {
940
+ write: async (filename, payload) => {
941
+ await eventStore.write(filename, payload);
942
+ },
943
+ delete: async (filename) => (await eventStore.delete(filename)).deleted,
944
+ list: async () => (await eventStore.list()).flatMap((entry) => entry.payload ? [{ filename: entry.filename, payload: entry.payload }] : []),
945
+ },
946
+ ...(platformNotifier ? { postMessage: platformNotifier } : {}),
947
+ ...(platformReactor ? { addReaction: platformReactor } : {}),
948
+ ...(platformUploader ? { uploadFile: platformUploader } : {}),
949
+ ...(platformDmOpener ? { openDirectConversation: platformDmOpener } : {}),
950
+ ...(platformHistoryFetcher ? { fetchHistory: platformHistoryFetcher } : {}),
951
+ ...(platformUserLister ? { listUsers: platformUserLister } : {}),
952
+ ...(extensionScheduleEngine
953
+ ? {
954
+ callbackScheduleStore: {
955
+ upsert: (slug, name, spec) => extensionScheduleEngine.upsert(address, slug, name, spec),
956
+ delete: (slug, name) => extensionScheduleEngine.delete(address, slug, name),
957
+ list: (slug) => extensionScheduleEngine.list(address, slug),
958
+ },
959
+ }
960
+ : {}),
961
+ ...(platformBlockKit
962
+ ? { postBlocks: platformBlockKit.postBlocks, updateBlocks: platformBlockKit.updateBlocks }
963
+ : {}),
964
+ ...(runSubagentService ? { runSubagent: runSubagentService } : {}),
965
+ ...(vaultManager
966
+ ? {
967
+ resolveSecrets: (slug) => vaultManager.resolve(`extensions/${slug}`)?.env ?? {},
968
+ }
969
+ : {}),
970
+ };
971
+ }
972
+ async function createConfiguredAgentSession(params) {
973
+ const { office, systemPrompt, model, thinkingLevel, tools, sessionStore, models, vaultManager, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformDmOpener, platformHistoryFetcher, platformUserLister, extensionScheduleEngine, } = params;
974
+ const { address } = office;
975
+ const conversationId = address.conversationId;
976
+ const workspaceDir = office.workspace.root;
977
+ // Host-only dirs under the state dir: extension code runs in the mikan
978
+ // process, so it must never load from workspace paths — those are mounted
979
+ // into sandbox containers and agent-writable (sandbox escape otherwise).
980
+ // resolveConversationPackages only ever returns state-dir paths (and host
981
+ // paths an administrator named explicitly in settings); it never reaches
982
+ // the network on this path, so a slow remote cannot delay a reply.
983
+ let session;
984
+ const loadedProfiles = loadSubagentProfiles(workspaceDir);
985
+ for (const diagnostic of loadedProfiles.diagnostics) {
986
+ log.logWarning(`[${conversationId}] Subagent profile ignored: ${diagnostic.path}`, diagnostic.message);
987
+ }
988
+ // Narrowed to the profiles whose tools actually exist once extensions have
989
+ // contributed theirs. Both subagent entry points read this at call time, so
990
+ // they always agree on which profiles are launchable.
991
+ let runnableSubagentProfiles = loadedProfiles.profiles;
992
+ const resolvedPackages = resolveConversationPackages({ office });
993
+ for (const error of resolvedPackages.errors) {
994
+ log.logWarning(`Package unavailable: ${error.source}`, error.message);
995
+ }
996
+ const extensionsResult = await loadExtensions({
997
+ dirs: resolvedPackages.extensionDirs,
998
+ roots: resolvedPackages.extensionRoots,
999
+ context: { address, conversationId, workspaceDir, model, thinkingLevel },
1000
+ services: buildExtensionHostServices({
1001
+ workspace: office.workspace,
1002
+ address,
1003
+ vaultManager,
1004
+ platformNotifier,
1005
+ platformReactor,
1006
+ platformUploader,
1007
+ platformBlockKit,
1008
+ platformDmOpener,
1009
+ platformHistoryFetcher,
1010
+ platformUserLister,
1011
+ extensionScheduleEngine,
1012
+ runSubagentService: (request, extensionTools) => {
1013
+ const activeParent = session?.isActiveRun ? session : undefined;
1014
+ return runSubagent({
1015
+ request,
1016
+ defaultModel: model,
1017
+ thinkingLevel,
1018
+ models,
1019
+ workspaceDir,
1020
+ availableTools: [...tools, ...extensionTools],
1021
+ profiles: runnableSubagentProfiles,
1022
+ slots: globalSubagentSlots,
1023
+ ...(activeParent
1024
+ ? {
1025
+ parentMessages: [...activeParent.messages],
1026
+ onUsage: activeParent.captureExternalUsageSink(),
1027
+ }
1028
+ : {}),
1029
+ });
1030
+ },
1031
+ }),
1032
+ });
1033
+ const contributedTools = extensionsResult.registry.getContributedTools();
1034
+ const subagentAvailableTools = [...tools, ...contributedTools];
1035
+ const availableToolNames = new Set(subagentAvailableTools.map((tool) => tool.name));
1036
+ runnableSubagentProfiles = new Map([...loadedProfiles.profiles].filter(([, profile]) => profile.tools.every((tool) => availableToolNames.has(tool))));
1037
+ for (const err of extensionsResult.errors) {
1038
+ log.logWarning(`[${conversationId}] Extension load error: ${err.path}`, err.error);
1039
+ }
1040
+ if (extensionsResult.extensions.length > 0) {
1041
+ log.logInfo(`[${conversationId}] Loaded ${extensionsResult.extensions.length} extension(s): ${extensionsResult.extensions.map((extension) => extension.name).join(", ")}`);
1042
+ }
1043
+ const subagentTool = createSubagentTool((request) => runSubagent({
1044
+ request,
1045
+ defaultModel: model,
1046
+ thinkingLevel,
1047
+ models,
1048
+ workspaceDir,
1049
+ availableTools: subagentAvailableTools,
1050
+ profiles: runnableSubagentProfiles,
1051
+ slots: globalSubagentSlots,
1052
+ parentMessages: [...session.messages],
1053
+ onUsage: session.captureExternalUsageSink(),
1054
+ }), runnableSubagentProfiles);
1055
+ session = new MikanAgentSession({
1056
+ systemPrompt,
1057
+ model,
1058
+ thinkingLevel,
1059
+ tools: [...tools, subagentTool],
1060
+ models,
1061
+ sessionStore,
1062
+ extensions: extensionsResult.registry,
1063
+ });
1064
+ const activeSession = session;
1065
+ const reloaded = activeSession.reloadFromSession();
1066
+ if (reloaded > 0) {
1067
+ log.logInfo(`[${conversationId}] Reloaded ${reloaded} messages from session context`);
1068
+ }
1069
+ return {
1070
+ session: activeSession,
1071
+ extensionSkills: extensionsResult.skills,
1072
+ extensionRegistry: extensionsResult.registry,
1073
+ disposeExtensions: extensionsResult.dispose,
1074
+ };
1075
+ }
742
1076
  function reloadSessionMessages(session, conversationId) {
743
1077
  const reloaded = session.reloadFromSession();
744
1078
  if (reloaded > 0) {
@@ -746,31 +1080,36 @@ function reloadSessionMessages(session, conversationId) {
746
1080
  }
747
1081
  }
748
1082
  async function prepareRunContext(params) {
749
- const { message, responder, platform, conversationId, conversationDir, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, } = params;
1083
+ const { message, responder, platform, office, sessionUuid, runState, executor, executionResolver, resolveExecutorForRun, getPathContext, session, setEventContext, setSandboxContext, setUploadFunction, setImageUploadFunction, setReactFunction, bindPlatformToolPacks, } = params;
1084
+ const conversationId = office.address.conversationId;
750
1085
  let pathContext = params.pathContext;
751
- const sessionConversation = message.sessionKey.split(":")[0];
752
- await mkdir(join(conversationDir, "scratch"), { recursive: true });
1086
+ const sessionConversation = conversationIdOf(message.sessionKey);
1087
+ await mkdir(join(office.dir, "scratch"), { recursive: true });
753
1088
  if (executionResolver) {
754
1089
  await resolveExecutorForRun({
755
- platform: platform.name,
1090
+ address: message.address,
756
1091
  userId: message.userId,
757
- conversationId,
1092
+ trustModel: platform.trustModel,
758
1093
  });
759
1094
  pathContext = getPathContext();
760
1095
  }
761
1096
  reloadSessionMessages(session, conversationId);
762
- const memory = await getMemory(conversationDir);
763
- const skills = mergeExtensionSkills(loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot), params.extensionSkills ?? []);
1097
+ const projection = resolveWorkspaceProjection(office);
1098
+ const memory = await getMemory(projection);
1099
+ const conversationSkillLoad = loadMikanSkills(office, pathContext.runtimeWorkspaceRoot, projection);
1100
+ const skills = mergeExtensionSkills(conversationSkillLoad.skills, params.extensionSkills ?? []);
764
1101
  const triggerAttribution = resolveTriggerAttribution(message);
765
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills);
1102
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, office, message.conversationKind, message.userId, memory, executor.getSandboxConfig(), platform, skills, projection, conversationSkillLoad.skippedSkillLinks);
766
1103
  session.agent.state.systemPrompt = systemPrompt;
767
1104
  // Cache diagnosis: a byte-stable system prompt is the precondition for
768
1105
  // provider prompt caching. If this hash changes between turns of one
769
1106
  // conversation, something turn-varying leaked into the prompt; if it is
770
1107
  // stable and cacheRead stays 0, the miss is provider-side (e.g. OpenRouter
771
- // routing the model across upstream hosts).
1108
+ // routing the model across upstream hosts). This is the base prompt mikan
1109
+ // builds; a `before_agent_start` extension may still rewrite it per turn
1110
+ // (logged separately by the runner).
772
1111
  const promptHash = createHash("sha256").update(systemPrompt).digest("hex").slice(0, 8);
773
- log.logInfo(`[${conversationId}] System prompt: ${systemPrompt.length} chars, sha ${promptHash}`);
1112
+ log.logInfo(`[${conversationId}] System prompt (base): ${systemPrompt.length} chars, sha ${promptHash}`);
774
1113
  setEventContext({
775
1114
  platform: platform.name,
776
1115
  conversationId,
@@ -779,18 +1118,33 @@ async function prepareRunContext(params) {
779
1118
  });
780
1119
  setSandboxContext({ conversationId, userId: message.userId });
781
1120
  setUploadFunction(async (filePath, title) => {
782
- const hostPath = translateAttachPathToHost(filePath, pathContext);
1121
+ const runtimePath = normalizeAttachRuntimePath(filePath, pathContext.runtimeWorkspaceRoot);
1122
+ await withStagedRuntimeFile(executor, runtimePath, (stagedPath) => responder.uploadFile(stagedPath, title));
1123
+ });
1124
+ // generate_image writes its file host-side, so it uploads by host path —
1125
+ // no staging through the sandbox, where the file may not be mounted.
1126
+ setImageUploadFunction(async (hostPath, title) => {
783
1127
  await responder.uploadFile(hostPath, title);
784
1128
  });
1129
+ // The react tool is available only when the responder supports reactions
1130
+ // (interactive turns on platforms with reaction support); otherwise unset
1131
+ // so the tool reports it is unavailable rather than silently no-op'ing.
1132
+ setReactFunction(responder.react ? async (emoji) => responder.react(emoji) : null);
1133
+ // Platform capability packs (e.g. GitHub PR/CI) enable themselves per run;
1134
+ // core does not branch on platform name here.
1135
+ bindPlatformToolPacks({
1136
+ conversationId,
1137
+ platformName: platform.name,
1138
+ threadTs: message.threadTs,
1139
+ });
785
1140
  resetRunState(runState, responder, sessionConversation, message.userName, sessionUuid, triggerAttribution);
786
1141
  const runQueue = createRunQueue(responder, runState);
787
1142
  runState.queue = runQueue.queue;
788
1143
  log.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);
789
1144
  log.logInfo(`Channels: ${platform.channels.length}, Users: ${platform.users.length}`);
790
- const { userMessage, imageAttachments } = buildPromptPayload(message, pathContext.runtimeWorkspaceRoot, pathContext);
1145
+ const { userMessage, imageAttachments } = await buildPromptPayload(message, pathContext.runtimeWorkspaceRoot, pathContext, (runtimePath) => executor.readFileBase64(runtimePath));
791
1146
  const turnInstructions = buildTurnInstructions(message.id.startsWith("event:"), triggerAttribution, platform.name);
792
1147
  const finalUserMessage = turnInstructions ? `${turnInstructions}\n\n${userMessage}` : userMessage;
793
- await writePromptDebugContext(conversationDir, systemPrompt, session, finalUserMessage, imageAttachments.length);
794
1148
  return {
795
1149
  sessionConversation,
796
1150
  runQueue,
@@ -800,6 +1154,62 @@ async function prepareRunContext(params) {
800
1154
  pathContext,
801
1155
  };
802
1156
  }
1157
+ /**
1158
+ * The dream run reads a transcript in which the agent held its full tool set,
1159
+ * but `sessionDreamTools` has since narrowed it. Left to infer its grant from
1160
+ * the transcript, the model calls a tool it no longer holds and burns one of
1161
+ * its five turns on the failure — so the grant is stated, and derived from the
1162
+ * tools actually handed to the run rather than restated by hand.
1163
+ */
1164
+ function sessionDreamPrompt(conversationMemoryPath, toolNames) {
1165
+ // read/edit/write are core tools, so an empty grant does not arise today;
1166
+ // the fallback only keeps this sentence coherent if that ever changes.
1167
+ const grant = toolNames.length === 0
1168
+ ? "You have no tools in this run and cannot change memory. Say so and stop."
1169
+ : `Your tools this run: ${toolNames.join(", ")}. Nothing else will execute, including tools used earlier in this transcript. ${conversationMemoryPath} is the only path you may write.`;
1170
+ return [
1171
+ "Before this conversation is reset or rotated, preserve only durable information worth carrying into future sessions of this same conversation.",
1172
+ `Review the existing transcript and update only the conversation-specific MEMORY.md at ${conversationMemoryPath}.`,
1173
+ grant,
1174
+ "Do not modify the workspace-level global MEMORY.md. Information from one DM or channel must not be promoted into memory shared with other conversations by this maintenance run.",
1175
+ "Preserve durable decisions, preferences, facts, and ongoing work specific to this conversation. Preserve the concrete values and details needed to resume the work; do not replace them with abstract categories, summaries of retention rules, or statements that merely say a kind of information is durable. An explicit user statement that a fact, preference, or decision should persist is strong evidence that its exact content is worth preserving.",
1176
+ "Deduplicate against existing conversation memory. Do not preserve transient discussion, tool noise, secrets, speculative details, or test scaffolding. If there is nothing new worth preserving, make no changes. Do no unrelated work.",
1177
+ ].join("\n");
1178
+ }
1179
+ const SESSION_DREAM_BUDGET = {
1180
+ maxDurationMs: 2 * 60 * 1000,
1181
+ maxLlmCalls: 10,
1182
+ };
1183
+ function sessionDreamTools(tools, conversationMemoryPath) {
1184
+ return tools
1185
+ .filter((tool) => tool.name === "read" || tool.name === "edit" || tool.name === "write")
1186
+ .map((tool) => {
1187
+ if (tool.name === "read")
1188
+ return tool;
1189
+ return Object.assign({}, tool, {
1190
+ execute: async (...args) => {
1191
+ const params = args[1];
1192
+ if (params.path !== conversationMemoryPath) {
1193
+ throw new Error(`Session Dream may only modify ${conversationMemoryPath}`);
1194
+ }
1195
+ return tool.execute(...args);
1196
+ },
1197
+ });
1198
+ });
1199
+ }
1200
+ async function noopResponderMethod() { }
1201
+ function createNoopResponder() {
1202
+ return {
1203
+ respond: noopResponderMethod,
1204
+ replaceResponse: noopResponderMethod,
1205
+ respondDiagnostic: noopResponderMethod,
1206
+ respondToolResult: noopResponderMethod,
1207
+ setTyping: noopResponderMethod,
1208
+ setWorking: noopResponderMethod,
1209
+ uploadFile: noopResponderMethod,
1210
+ deleteResponse: noopResponderMethod,
1211
+ };
1212
+ }
803
1213
  function formatAgentActorName(userName, fallback) {
804
1214
  return userName ? `DM:${userName}` : fallback;
805
1215
  }
@@ -837,7 +1247,12 @@ function attachSessionEventHandlers(params) {
837
1247
  label: extractToolLabel(event.toolName, event.args),
838
1248
  status: "running",
839
1249
  });
840
- queue.enqueue(() => replaceResponseWithToolProgress(responder, runState), "tool progress update");
1250
+ if (event.toolName === "subagent") {
1251
+ runState.subagentToolCalls.add(event.toolCallId);
1252
+ }
1253
+ else {
1254
+ queue.enqueue(() => replaceResponseWithToolProgress(responder, runState), "tool progress update");
1255
+ }
841
1256
  addLifecycleBreadcrumb("agent.tool.started", {
842
1257
  tool: event.toolName,
843
1258
  ...baseAttrs,
@@ -845,6 +1260,16 @@ function attachSessionEventHandlers(params) {
845
1260
  log.logToolStart(logCtx, event.toolName, label, event.args);
846
1261
  return;
847
1262
  }
1263
+ if (event.type === "tool_execution_update") {
1264
+ const subagentProgress = extractSubagentProgress(event.partialResult);
1265
+ if (subagentProgress) {
1266
+ runState.subagentProgress.set(event.toolCallId, subagentProgress);
1267
+ runState.subagentToolCalls.add(event.toolCallId);
1268
+ runState.suppressResponseDeltas = true;
1269
+ scheduleToolProgressUpdate(responder, runState);
1270
+ }
1271
+ return;
1272
+ }
848
1273
  if (event.type === "tool_execution_end") {
849
1274
  const resultStr = extractToolResultText(event.result);
850
1275
  const pending = pendingTools.get(event.toolCallId);
@@ -856,7 +1281,15 @@ function attachSessionEventHandlers(params) {
856
1281
  const progress = runState.toolProgress.get(event.toolCallId);
857
1282
  if (progress)
858
1283
  progress.status = event.isError ? "error" : "done";
859
- queue.enqueue(() => replaceResponseWithToolProgress(responder, runState), "tool progress update");
1284
+ const subagentProgress = runState.subagentProgress.get(event.toolCallId);
1285
+ if (subagentProgress) {
1286
+ runState.subagentProgress.set(event.toolCallId, settleSubagentProgress(subagentProgress, event.isError));
1287
+ }
1288
+ flushToolProgressUpdate(responder, runState);
1289
+ const completedProgress = runState.subagentProgress.get(event.toolCallId);
1290
+ if (completedProgress)
1291
+ runState.completedSubagentProgress.push(completedProgress);
1292
+ runState.subagentProgress.delete(event.toolCallId);
860
1293
  pendingTools.delete(event.toolCallId);
861
1294
  const durationMs = pending ? Date.now() - pending.startTime : 0;
862
1295
  Sentry.metrics.count("agent.tool.calls", 1, {
@@ -884,6 +1317,9 @@ function attachSessionEventHandlers(params) {
884
1317
  }
885
1318
  else {
886
1319
  log.logToolSuccess(logCtx, event.toolName, durationMs, resultStr);
1320
+ if (event.toolName === "slack_blockkit") {
1321
+ runState.finalResponseHandledByTool = true;
1322
+ }
887
1323
  }
888
1324
  return;
889
1325
  }
@@ -913,7 +1349,7 @@ function attachSessionEventHandlers(params) {
913
1349
  actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
914
1350
  event: { kind: "responseDelta", delta: assistantMessageEvent.delta },
915
1351
  });
916
- if (responder.appendResponseDelta) {
1352
+ if (responder.appendResponseDelta && !runState.suppressResponseDeltas) {
917
1353
  queue.enqueue(async () => {
918
1354
  await responder.appendResponseDelta?.(assistantMessageEvent.delta ?? "");
919
1355
  }, "response delta");
@@ -1010,6 +1446,12 @@ function attachSessionEventHandlers(params) {
1010
1446
  actorName: formatAgentActorName(logCtx.userName, logCtx.conversationId),
1011
1447
  event: { kind: "turnEnd" },
1012
1448
  });
1449
+ // A run that produced a subagent dashboard finalizes through it:
1450
+ // finalizeRunResponse renders "dashboard, blank line, answer", and a
1451
+ // finish here would first overwrite the dashboard with the bare
1452
+ // answer on every platform.
1453
+ if (runState.completedSubagentProgress.length > 0)
1454
+ return;
1013
1455
  if (responder.finishResponse) {
1014
1456
  queue.enqueue(async () => {
1015
1457
  await responder.finishResponse?.(finalText);
@@ -1069,27 +1511,6 @@ function attachSessionEventHandlers(params) {
1069
1511
  }
1070
1512
  });
1071
1513
  }
1072
- function extractToolResultText(result) {
1073
- if (typeof result === "string") {
1074
- return result;
1075
- }
1076
- if (result &&
1077
- typeof result === "object" &&
1078
- "content" in result &&
1079
- Array.isArray(result.content)) {
1080
- const content = result.content;
1081
- const textParts = [];
1082
- for (const part of content) {
1083
- if (part.type === "text" && part.text) {
1084
- textParts.push(part.text);
1085
- }
1086
- }
1087
- if (textParts.length > 0) {
1088
- return textParts.join("\n");
1089
- }
1090
- }
1091
- return JSON.stringify(result);
1092
- }
1093
1514
  /**
1094
1515
  * Create a new PiAgentWrapper for a channel.
1095
1516
  * Sets up the session and subscribes to events once.
@@ -1097,32 +1518,45 @@ function extractToolResultText(result) {
1097
1518
  * Runner caching is handled by the caller (channelStates in main.ts).
1098
1519
  * This is a stateless factory function.
1099
1520
  */
1100
- export async function createRunner(sandboxConfig, sessionKey, conversationId, conversationDir, workspaceDir, sessionScope, vaultManager, provisioner, sessionView, platformNotifier) {
1101
- const agentConfig = resolveConversationSettings(conversationDir);
1102
- const workspaceBase = join(conversationDir, "..");
1103
- const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, workspaceDir, workspaceBase);
1104
- let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceBase);
1105
- // Create tools (per-runner, with per-runner upload function setter)
1106
- const { tools, setUploadFunction, setEventContext, setSandboxContext } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, provisioner });
1107
- const modelRegistry = MikanModels.create();
1521
+ export async function createRunner(options) {
1522
+ const { sandboxConfig, sessionKey, office, sessionScope, vaultManager, provisioner, resourceController, sessionView, platformNotifier, platformReactor, platformUploader, platformBlockKit, platformDmOpener, platformHistoryFetcher, platformUserLister, extensionScheduleEngine, platformToolPackFactories, } = options;
1523
+ const conversationId = office.address.conversationId;
1524
+ const conversationDir = office.dir;
1525
+ const workspaceDir = office.workspace.root;
1526
+ const agentConfig = resolveConversationSettings(office);
1527
+ const projection = resolveWorkspaceProjection(office);
1528
+ assertSandboxSupportsWorkspacePolicy(sandboxConfig, projection.doorPolicy);
1529
+ const { executionResolver, executor, getPathContext, resolveExecutorForRun } = createRunnerExecutionContext(sandboxConfig, vaultManager, provisioner, office.workspace);
1530
+ let pathContext = getUnresolvedSandboxPathContext(sandboxConfig, workspaceDir);
1531
+ const modelRegistry = options.models ?? MikanModels.create();
1108
1532
  if (modelRegistry.getError()) {
1109
1533
  log.logWarning("models.json load error", modelRegistry.getError());
1110
1534
  }
1111
- const model = resolveConfiguredModel(modelRegistry, agentConfig.provider, agentConfig.model);
1535
+ const model = modelRegistry.resolve(agentConfig.provider, agentConfig.model);
1536
+ // Create tools (per-runner, with per-runner upload function setter)
1537
+ const { tools, setUploadFunction, setImageUploadFunction, setReactFunction, bindPlatformToolPacks, setEventContext, setSandboxContext, } = createMikanTools(executor, workspaceDir, { sandbox: sandboxConfig, resourceController: resourceController ?? provisioner }, platformToolPackFactories ?? [], {
1538
+ model,
1539
+ getApiKey: () => modelRegistry.getApiKeyForProvider(model.provider),
1540
+ // The conversation's own office dir: mounted into the sandbox, unlike
1541
+ // the workspace base — generated images must land somewhere the agent
1542
+ // (and a future run) can still reach.
1543
+ outputDir: conversationDir,
1544
+ });
1112
1545
  // Initial system prompt (will be updated each run with fresh memory/channels/users/skills)
1113
- const memory = await getMemory(conversationDir);
1114
- const skills = loadMikanSkills(conversationDir, pathContext.runtimeWorkspaceRoot);
1546
+ const memory = await getMemory(projection);
1547
+ const { skills, skippedSkillLinks } = loadMikanSkills(office, pathContext.runtimeWorkspaceRoot, projection);
1115
1548
  const emptyPlatform = {
1116
1549
  name: "chat",
1117
1550
  formattingGuide: "",
1118
1551
  channels: [],
1119
1552
  users: [],
1553
+ trustModel: "membership",
1120
1554
  };
1121
- const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, conversationId, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills);
1555
+ const systemPrompt = buildSystemPrompt(pathContext.runtimeWorkspaceRoot, office, "shared", undefined, memory, sandboxConfig, emptyPlatform, skills, projection, skippedSkillLinks);
1122
1556
  // Create session manager and settings manager. Top-level/private sessions
1123
1557
  // use the conversation's current pointer; scoped sessions use fixed files.
1124
1558
  // Platform-specific scope behavior is resolved before runner creation.
1125
- const isThread = sessionKey.includes(":");
1559
+ const isThread = isThreadSessionKey(sessionKey);
1126
1560
  const { contextFile, threadRootMessage } = sessionScope;
1127
1561
  const sessionManager = openManagedSession(contextFile, pathContext.runtimeWorkspaceRoot);
1128
1562
  const threadSessionName = buildThreadSessionName(threadRootMessage);
@@ -1130,10 +1564,9 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1130
1564
  sessionManager.appendSessionInfo(threadSessionName);
1131
1565
  }
1132
1566
  const sessionUuid = extractSessionUuid(contextFile);
1133
- const chatSessionManager = new AgentMemoryFileManager();
1134
- const { session, extensionSkills } = await createConfiguredAgentSession({
1135
- conversationId,
1136
- workspaceDir,
1567
+ const chatSessionManager = new ChatHistorySync();
1568
+ const { session, extensionSkills, extensionRegistry, disposeExtensions } = await createConfiguredAgentSession({
1569
+ office,
1137
1570
  systemPrompt,
1138
1571
  model,
1139
1572
  thinkingLevel: agentConfig.thinkingLevel,
@@ -1142,6 +1575,13 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1142
1575
  models: modelRegistry,
1143
1576
  vaultManager,
1144
1577
  platformNotifier,
1578
+ platformReactor,
1579
+ platformUploader,
1580
+ platformBlockKit,
1581
+ platformDmOpener,
1582
+ platformHistoryFetcher,
1583
+ platformUserLister,
1584
+ extensionScheduleEngine,
1145
1585
  });
1146
1586
  // Mutable per-run state - event handler references this
1147
1587
  const runState = createRunState();
@@ -1160,8 +1600,7 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1160
1600
  message,
1161
1601
  responder,
1162
1602
  platform,
1163
- conversationId,
1164
- conversationDir,
1603
+ office,
1165
1604
  sessionUuid,
1166
1605
  runState,
1167
1606
  executor,
@@ -1173,6 +1612,9 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1173
1612
  setEventContext,
1174
1613
  setSandboxContext,
1175
1614
  setUploadFunction,
1615
+ setImageUploadFunction,
1616
+ setReactFunction,
1617
+ bindPlatformToolPacks,
1176
1618
  pathContext,
1177
1619
  });
1178
1620
  pathContext = prepared.pathContext;
@@ -1201,10 +1643,39 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1201
1643
  // Autonomous (event/trigger) runs get an explicit resource ceiling since
1202
1644
  // no human is watching the loop; interactive turns stay human-gated.
1203
1645
  const isEventRun = message.id.startsWith("event:");
1204
- await session.prompt(prepared.userMessage, {
1646
+ // Event runs have no triggering platform message, so identity fields
1647
+ // stay unset and extensions must null-check them.
1648
+ const origin = isEventRun
1649
+ ? { kind: "event", platform: platform.name }
1650
+ : {
1651
+ kind: "interactive",
1652
+ platform: platform.name,
1653
+ messageTs: message.id,
1654
+ userId: message.userId,
1655
+ userName: message.userName,
1656
+ threadTs: message.threadTs,
1657
+ attachments: message.attachments,
1658
+ };
1659
+ const outcome = await session.prompt(prepared.userMessage, {
1660
+ origin,
1205
1661
  ...(prepared.imageAttachments.length > 0 ? { images: prepared.imageAttachments } : {}),
1206
1662
  ...(isEventRun ? { budget: DEFAULT_EVENT_BUDGET } : {}),
1207
1663
  });
1664
+ if (outcome?.blocked) {
1665
+ const text = outcome.reason
1666
+ ? `_Turn blocked by an extension: ${outcome.reason}_`
1667
+ : "_Turn blocked by an extension._";
1668
+ sendAgentEvent({
1669
+ sessionId: sessionUuid,
1670
+ actorName: formatAgentActorName(message.userName, prepared.sessionConversation),
1671
+ event: { kind: "diagnostic", text },
1672
+ });
1673
+ await responder.respondDiagnostic(text, { style: "muted" });
1674
+ runState.responder = null;
1675
+ runState.logCtx = null;
1676
+ runState.queue = null;
1677
+ return { stopReason: "blocked" };
1678
+ }
1208
1679
  // Wait for queued messages
1209
1680
  await prepared.runQueue.wait();
1210
1681
  const sessionViewTokenStore = sessionView?.tokenStore;
@@ -1247,9 +1718,89 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1247
1718
  runState.queue = null;
1248
1719
  return { stopReason: runState.stopReason, errorMessage: runState.errorMessage };
1249
1720
  },
1721
+ async dreamSessionMemory(message, platform) {
1722
+ const responder = createNoopResponder();
1723
+ const prepared = await prepareRunContext({
1724
+ message,
1725
+ responder,
1726
+ platform,
1727
+ office,
1728
+ sessionUuid,
1729
+ runState,
1730
+ executor,
1731
+ executionResolver,
1732
+ resolveExecutorForRun,
1733
+ getPathContext,
1734
+ session,
1735
+ extensionSkills,
1736
+ setEventContext,
1737
+ setSandboxContext,
1738
+ setUploadFunction,
1739
+ setImageUploadFunction,
1740
+ setReactFunction,
1741
+ bindPlatformToolPacks,
1742
+ pathContext,
1743
+ });
1744
+ pathContext = prepared.pathContext;
1745
+ try {
1746
+ const conversationMemoryPath = posix.join(prepared.pathContext.runtimeWorkspaceRoot, office.key, "MEMORY.md");
1747
+ const dreamTools = sessionDreamTools(session.agent.state.tools, conversationMemoryPath);
1748
+ const outcome = await session.prompt(sessionDreamPrompt(conversationMemoryPath, dreamTools.map((tool) => tool.name)), {
1749
+ origin: {
1750
+ kind: "interactive",
1751
+ platform: platform.name,
1752
+ messageTs: message.id,
1753
+ userId: message.userId,
1754
+ userName: message.userName,
1755
+ threadTs: message.threadTs,
1756
+ attachments: message.attachments,
1757
+ },
1758
+ budget: SESSION_DREAM_BUDGET,
1759
+ tools: dreamTools,
1760
+ });
1761
+ if (outcome?.blocked) {
1762
+ return { stopReason: "blocked", errorMessage: outcome.reason };
1763
+ }
1764
+ await prepared.runQueue.wait();
1765
+ // A budget abort carries its reason in the tally, not in errorMessage,
1766
+ // and that reason is the only thing that tells the user why memory was
1767
+ // not preserved.
1768
+ return {
1769
+ stopReason: runState.stopReason,
1770
+ errorMessage: runState.errorMessage ?? session.getLastRunStats().budgetExceededReason,
1771
+ };
1772
+ }
1773
+ finally {
1774
+ runState.responder = null;
1775
+ runState.logCtx = null;
1776
+ runState.queue = null;
1777
+ }
1778
+ },
1250
1779
  abort() {
1251
1780
  session.abort();
1252
1781
  },
1782
+ async tryExtensionCommand(message, responder) {
1783
+ const parsed = parseCommandInput(message.text);
1784
+ if (!parsed)
1785
+ return false;
1786
+ return extensionRegistry.dispatchCommand(parsed.name, {
1787
+ args: parsed.args,
1788
+ conversationId,
1789
+ userId: message.userId,
1790
+ userName: message.userName,
1791
+ threadTs: message.threadTs,
1792
+ respond: (text) => responder.respond(text),
1793
+ });
1794
+ },
1795
+ async tryExtensionAction(slug, action) {
1796
+ return extensionRegistry.dispatchAction(slug, action);
1797
+ },
1798
+ async tryExtensionScheduleCallback(slug, callback, event) {
1799
+ return extensionRegistry.dispatchScheduleCallback(slug, callback, event);
1800
+ },
1801
+ async dispose() {
1802
+ await disposeExtensions();
1803
+ },
1253
1804
  getCurrentStep() {
1254
1805
  const pending = runState.pendingTools;
1255
1806
  if (pending.size === 0)
@@ -1265,13 +1816,4 @@ export async function createRunner(sandboxConfig, sessionKey, conversationId, co
1265
1816
  },
1266
1817
  };
1267
1818
  }
1268
- export function translateRuntimePathToHost(runtimePath, pathContext) {
1269
- return pathContext.runtimeToHostPath?.(runtimePath) ?? runtimePath;
1270
- }
1271
- export function translateAttachPathToHost(filePath, pathContext) {
1272
- const runtimePath = posix.isAbsolute(filePath)
1273
- ? filePath
1274
- : posix.join(pathContext.runtimeWorkspaceRoot, filePath);
1275
- return translateRuntimePathToHost(runtimePath, pathContext);
1276
- }
1277
1819
  //# sourceMappingURL=agent.js.map