@meetquinn/animactl 0.1.2 → 0.1.3

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 (289) hide show
  1. package/README.md +11 -6
  2. package/dist/server/{runtime/activity-payload.d.ts → activities/format.d.ts} +4 -1
  3. package/dist/server/activities/format.d.ts.map +1 -0
  4. package/dist/server/{runtime/activity-payload.js → activities/format.js} +16 -1
  5. package/dist/server/agents/agent-skills.d.ts +7 -3
  6. package/dist/server/agents/agent-skills.d.ts.map +1 -1
  7. package/dist/server/agents/agent-skills.js +243 -38
  8. package/dist/server/agents/agent-slack.service.d.ts +15 -0
  9. package/dist/server/agents/agent-slack.service.d.ts.map +1 -1
  10. package/dist/server/agents/agent-slack.service.js +43 -6
  11. package/dist/server/agents/default-skills.d.ts +9 -0
  12. package/dist/server/agents/default-skills.d.ts.map +1 -0
  13. package/dist/server/agents/default-skills.js +52 -0
  14. package/dist/server/cli/runtime-cli.d.ts +7 -1
  15. package/dist/server/cli/runtime-cli.d.ts.map +1 -1
  16. package/dist/server/cli/runtime-cli.js +107 -3
  17. package/dist/server/cli/services-cli.d.ts.map +1 -1
  18. package/dist/server/cli/services-cli.js +39 -0
  19. package/dist/server/inbox/slack-subscriber.d.ts +2 -0
  20. package/dist/server/inbox/slack-subscriber.d.ts.map +1 -1
  21. package/dist/server/inbox/slack-subscriber.js +28 -2
  22. package/dist/server/inbox/slack-subscription.service.d.ts.map +1 -1
  23. package/dist/server/inbox/slack-subscription.service.js +13 -1
  24. package/dist/server/inbox/wake-queue.service.d.ts +3 -1
  25. package/dist/server/inbox/wake-queue.service.d.ts.map +1 -1
  26. package/dist/server/inbox/wake-queue.service.js +5 -3
  27. package/dist/server/provider-usage/http.d.ts +1 -0
  28. package/dist/server/provider-usage/http.d.ts.map +1 -1
  29. package/dist/server/provider-usage/http.js +23 -1
  30. package/dist/server/provider-usage/provider-usage.service.d.ts +1 -4
  31. package/dist/server/provider-usage/provider-usage.service.d.ts.map +1 -1
  32. package/dist/server/provider-usage/provider-usage.service.js +1 -18
  33. package/dist/server/provider-usage/providers/claude.d.ts +1 -1
  34. package/dist/server/provider-usage/providers/claude.d.ts.map +1 -1
  35. package/dist/server/provider-usage/providers/claude.js +1 -1
  36. package/dist/server/provider-usage/providers/codex.d.ts +1 -1
  37. package/dist/server/provider-usage/providers/codex.d.ts.map +1 -1
  38. package/dist/server/provider-usage/providers/codex.js +1 -1
  39. package/dist/server/provider-usage/providers/kimi.d.ts +1 -1
  40. package/dist/server/provider-usage/providers/kimi.d.ts.map +1 -1
  41. package/dist/server/provider-usage/providers/kimi.js +4 -4
  42. package/dist/server/provider-usage/result.d.ts +5 -0
  43. package/dist/server/provider-usage/result.d.ts.map +1 -0
  44. package/dist/server/provider-usage/result.js +18 -0
  45. package/dist/server/providers/active-runtime.d.ts +1 -1
  46. package/dist/server/providers/active-runtime.d.ts.map +1 -1
  47. package/dist/server/providers/claude-events.d.ts +1 -1
  48. package/dist/server/providers/claude-events.d.ts.map +1 -1
  49. package/dist/server/providers/claude-events.js +2 -2
  50. package/dist/server/providers/claude.d.ts +1 -1
  51. package/dist/server/providers/claude.d.ts.map +1 -1
  52. package/dist/server/providers/claude.js +2 -2
  53. package/dist/server/providers/codex-app-server.d.ts +1 -1
  54. package/dist/server/providers/codex-app-server.d.ts.map +1 -1
  55. package/dist/server/providers/codex-app-server.js +1 -1
  56. package/dist/server/providers/codex-events.js +1 -1
  57. package/dist/server/providers/codex.d.ts +1 -1
  58. package/dist/server/providers/codex.d.ts.map +1 -1
  59. package/dist/server/providers/codex.js +2 -2
  60. package/dist/server/{runtime/provider-contract.d.ts → providers/contract.d.ts} +1 -1
  61. package/dist/server/providers/contract.d.ts.map +1 -0
  62. package/dist/server/providers/factory.d.ts +1 -1
  63. package/dist/server/providers/factory.d.ts.map +1 -1
  64. package/dist/server/providers/kimi-events.d.ts +1 -1
  65. package/dist/server/providers/kimi-events.d.ts.map +1 -1
  66. package/dist/server/providers/kimi-events.js +1 -1
  67. package/dist/server/providers/kimi.d.ts +1 -1
  68. package/dist/server/providers/kimi.d.ts.map +1 -1
  69. package/dist/server/providers/kimi.js +3 -2
  70. package/dist/server/providers/reasoning-events.js +1 -1
  71. package/dist/server/runtime/active-run-control.d.ts +19 -0
  72. package/dist/server/runtime/active-run-control.d.ts.map +1 -0
  73. package/dist/server/runtime/active-run-control.js +89 -0
  74. package/dist/server/runtime/activity.js +1 -1
  75. package/dist/server/runtime/agent-runner.d.ts +1 -1
  76. package/dist/server/runtime/agent-runner.d.ts.map +1 -1
  77. package/dist/server/runtime/delivery-prompt.d.ts +6 -0
  78. package/dist/server/runtime/delivery-prompt.d.ts.map +1 -1
  79. package/dist/server/runtime/delivery-prompt.js +53 -34
  80. package/dist/server/runtime/followup-appender.d.ts +20 -0
  81. package/dist/server/runtime/followup-appender.d.ts.map +1 -0
  82. package/dist/server/runtime/followup-appender.js +128 -0
  83. package/dist/server/runtime/host.d.ts +2 -0
  84. package/dist/server/runtime/host.d.ts.map +1 -1
  85. package/dist/server/runtime/host.js +8 -0
  86. package/dist/server/runtime/provider-runner.d.ts +16 -0
  87. package/dist/server/runtime/provider-runner.d.ts.map +1 -0
  88. package/dist/server/runtime/provider-runner.js +86 -0
  89. package/dist/server/runtime/runtime-bridge.d.ts +1 -1
  90. package/dist/server/runtime/runtime-bridge.d.ts.map +1 -1
  91. package/dist/server/runtime/runtime-session.service.d.ts +1 -1
  92. package/dist/server/runtime/runtime-session.service.d.ts.map +1 -1
  93. package/dist/server/runtime/runtime-session.service.js +1 -1
  94. package/dist/server/runtime/runtime-worker.d.ts +2 -10
  95. package/dist/server/runtime/runtime-worker.d.ts.map +1 -1
  96. package/dist/server/runtime/runtime-worker.js +60 -286
  97. package/dist/server/runtime-management/managed-runtime.d.ts.map +1 -0
  98. package/dist/server/runtime-management/runtime-release.d.ts.map +1 -0
  99. package/dist/server/{runtime → runtime-management}/runtime-upgrade.d.ts +1 -0
  100. package/dist/server/runtime-management/runtime-upgrade.d.ts.map +1 -0
  101. package/dist/server/{runtime → runtime-management}/runtime-upgrade.js +14 -0
  102. package/dist/server/services/system.service.d.ts +41 -0
  103. package/dist/server/services/system.service.d.ts.map +1 -0
  104. package/dist/server/services/system.service.js +154 -0
  105. package/dist/server/settings/settings.service.d.ts +2 -1
  106. package/dist/server/settings/settings.service.d.ts.map +1 -1
  107. package/dist/server/settings/settings.service.js +9 -1
  108. package/dist/server/slack/shortcuts.d.ts +0 -3
  109. package/dist/server/slack/shortcuts.d.ts.map +1 -1
  110. package/dist/server/slack/shortcuts.js +0 -3
  111. package/dist/server/slack-interactions/shortcut-ids.d.ts +4 -0
  112. package/dist/server/slack-interactions/shortcut-ids.d.ts.map +1 -0
  113. package/dist/server/slack-interactions/shortcut-ids.js +3 -0
  114. package/dist/server/slack-interactions/shortcut-views.d.ts +65 -0
  115. package/dist/server/slack-interactions/shortcut-views.d.ts.map +1 -0
  116. package/dist/server/slack-interactions/shortcut-views.js +211 -0
  117. package/dist/server/{slack → slack-interactions}/shortcut.service.d.ts +2 -47
  118. package/dist/server/slack-interactions/shortcut.service.d.ts.map +1 -0
  119. package/dist/server/slack-interactions/shortcut.service.js +227 -0
  120. package/dist/server/storage/jsonl-log.d.ts +9 -0
  121. package/dist/server/storage/jsonl-log.d.ts.map +1 -1
  122. package/dist/server/storage/jsonl-log.js +36 -1
  123. package/dist/server/storage/schema/server.store.d.ts +5 -0
  124. package/dist/server/storage/schema/server.store.d.ts.map +1 -1
  125. package/dist/server/storage/schema/server.store.js +2 -1
  126. package/dist/server/storage/schema/wake-queue.store.d.ts +15 -1
  127. package/dist/server/storage/schema/wake-queue.store.d.ts.map +1 -1
  128. package/dist/server/storage/schema/wake-queue.store.js +5 -3
  129. package/dist/server/tests/agent-config.test.d.ts +2 -0
  130. package/dist/server/tests/agent-config.test.d.ts.map +1 -0
  131. package/dist/server/tests/agent-config.test.js +229 -0
  132. package/dist/server/tests/agent-runtime.test.d.ts +2 -0
  133. package/dist/server/tests/agent-runtime.test.d.ts.map +1 -0
  134. package/dist/server/tests/agent-runtime.test.js +1110 -0
  135. package/dist/server/tests/agent-seed-memory.test.d.ts +2 -0
  136. package/dist/server/tests/agent-seed-memory.test.d.ts.map +1 -0
  137. package/dist/server/tests/agent-seed-memory.test.js +16 -0
  138. package/dist/server/tests/agent-skills.test.d.ts +2 -0
  139. package/dist/server/tests/agent-skills.test.d.ts.map +1 -0
  140. package/dist/server/tests/agent-skills.test.js +140 -0
  141. package/dist/server/tests/anima-home.d.ts +2 -0
  142. package/dist/server/tests/anima-home.d.ts.map +1 -0
  143. package/dist/server/tests/anima-home.js +4 -0
  144. package/dist/server/tests/cli-file.test.d.ts +2 -0
  145. package/dist/server/tests/cli-file.test.d.ts.map +1 -0
  146. package/dist/server/tests/cli-file.test.js +596 -0
  147. package/dist/server/tests/cli-message.test.d.ts +2 -0
  148. package/dist/server/tests/cli-message.test.d.ts.map +1 -0
  149. package/dist/server/tests/cli-message.test.js +1196 -0
  150. package/dist/server/tests/client-error-routes.test.d.ts +2 -0
  151. package/dist/server/tests/client-error-routes.test.d.ts.map +1 -0
  152. package/dist/server/tests/client-error-routes.test.js +163 -0
  153. package/dist/server/tests/config.test.d.ts +2 -0
  154. package/dist/server/tests/config.test.d.ts.map +1 -0
  155. package/dist/server/tests/config.test.js +497 -0
  156. package/dist/server/tests/default-skills.test.d.ts +2 -0
  157. package/dist/server/tests/default-skills.test.d.ts.map +1 -0
  158. package/dist/server/tests/default-skills.test.js +57 -0
  159. package/dist/server/tests/helpers/inbox.d.ts +10 -0
  160. package/dist/server/tests/helpers/inbox.d.ts.map +1 -0
  161. package/dist/server/tests/helpers/inbox.js +21 -0
  162. package/dist/server/tests/helpers/slack.d.ts +11 -0
  163. package/dist/server/tests/helpers/slack.d.ts.map +1 -0
  164. package/dist/server/tests/helpers/slack.js +17 -0
  165. package/dist/server/tests/helpers/state.d.ts +19 -0
  166. package/dist/server/tests/helpers/state.d.ts.map +1 -0
  167. package/dist/server/tests/helpers/state.js +69 -0
  168. package/dist/server/tests/inbox-slack-events.test.d.ts +2 -0
  169. package/dist/server/tests/inbox-slack-events.test.d.ts.map +1 -0
  170. package/dist/server/tests/inbox-slack-events.test.js +271 -0
  171. package/dist/server/tests/inbox.test.d.ts +2 -0
  172. package/dist/server/tests/inbox.test.d.ts.map +1 -0
  173. package/dist/server/tests/inbox.test.js +144 -0
  174. package/dist/server/tests/interactive-ask.test.d.ts +2 -0
  175. package/dist/server/tests/interactive-ask.test.d.ts.map +1 -0
  176. package/dist/server/tests/interactive-ask.test.js +387 -0
  177. package/dist/server/tests/kb.test.d.ts +2 -0
  178. package/dist/server/tests/kb.test.d.ts.map +1 -0
  179. package/dist/server/tests/kb.test.js +371 -0
  180. package/dist/server/tests/message.service.test.d.ts +2 -0
  181. package/dist/server/tests/message.service.test.d.ts.map +1 -0
  182. package/dist/server/tests/message.service.test.js +151 -0
  183. package/dist/server/tests/messages.test.d.ts +2 -0
  184. package/dist/server/tests/messages.test.d.ts.map +1 -0
  185. package/dist/server/tests/messages.test.js +12 -0
  186. package/dist/server/tests/prompt-attachments.test.d.ts +2 -0
  187. package/dist/server/tests/prompt-attachments.test.d.ts.map +1 -0
  188. package/dist/server/tests/prompt-attachments.test.js +274 -0
  189. package/dist/server/tests/provider-usage.test.d.ts +2 -0
  190. package/dist/server/tests/provider-usage.test.d.ts.map +1 -0
  191. package/dist/server/tests/provider-usage.test.js +97 -0
  192. package/dist/server/tests/reminders.test.d.ts +2 -0
  193. package/dist/server/tests/reminders.test.d.ts.map +1 -0
  194. package/dist/server/tests/reminders.test.js +383 -0
  195. package/dist/server/tests/runtime-cli.test.d.ts +2 -0
  196. package/dist/server/tests/runtime-cli.test.d.ts.map +1 -0
  197. package/dist/server/tests/runtime-cli.test.js +21 -0
  198. package/dist/server/tests/runtime-upgrade.test.d.ts +2 -0
  199. package/dist/server/tests/runtime-upgrade.test.d.ts.map +1 -0
  200. package/dist/server/tests/runtime-upgrade.test.js +307 -0
  201. package/dist/server/tests/runtime-worker.test.d.ts +2 -0
  202. package/dist/server/tests/runtime-worker.test.d.ts.map +1 -0
  203. package/dist/server/tests/runtime-worker.test.js +1021 -0
  204. package/dist/server/tests/runtime.test.d.ts +2 -0
  205. package/dist/server/tests/runtime.test.d.ts.map +1 -0
  206. package/dist/server/tests/runtime.test.js +354 -0
  207. package/dist/server/tests/services.test.d.ts +2 -0
  208. package/dist/server/tests/services.test.d.ts.map +1 -0
  209. package/dist/server/tests/services.test.js +636 -0
  210. package/dist/server/tests/slack-files.test.d.ts +2 -0
  211. package/dist/server/tests/slack-files.test.d.ts.map +1 -0
  212. package/dist/server/tests/slack-files.test.js +118 -0
  213. package/dist/server/tests/slack-shortcuts.test.d.ts +2 -0
  214. package/dist/server/tests/slack-shortcuts.test.d.ts.map +1 -0
  215. package/dist/server/tests/slack-shortcuts.test.js +310 -0
  216. package/dist/server/tests/slack.test.d.ts +2 -0
  217. package/dist/server/tests/slack.test.d.ts.map +1 -0
  218. package/dist/server/tests/slack.test.js +213 -0
  219. package/dist/server/tests/state-cache.test.d.ts +2 -0
  220. package/dist/server/tests/state-cache.test.d.ts.map +1 -0
  221. package/dist/server/tests/state-cache.test.js +184 -0
  222. package/dist/server/tests/subscriptions.test.d.ts +2 -0
  223. package/dist/server/tests/subscriptions.test.d.ts.map +1 -0
  224. package/dist/server/tests/subscriptions.test.js +292 -0
  225. package/dist/server/tests/system-service.test.d.ts +2 -0
  226. package/dist/server/tests/system-service.test.d.ts.map +1 -0
  227. package/dist/server/tests/system-service.test.js +20 -0
  228. package/dist/server/tests/url-routes.test.d.ts +2 -0
  229. package/dist/server/tests/url-routes.test.d.ts.map +1 -0
  230. package/dist/server/tests/url-routes.test.js +217 -0
  231. package/dist/server/tests/web-api.test.d.ts +2 -0
  232. package/dist/server/tests/web-api.test.d.ts.map +1 -0
  233. package/dist/server/tests/web-api.test.js +1759 -0
  234. package/dist/server/web/app.d.ts.map +1 -1
  235. package/dist/server/web/app.js +2 -0
  236. package/dist/server/web/client-error-routes.d.ts +3 -0
  237. package/dist/server/web/client-error-routes.d.ts.map +1 -0
  238. package/dist/server/web/client-error-routes.js +101 -0
  239. package/dist/server/web/system-routes.d.ts.map +1 -1
  240. package/dist/server/web/system-routes.js +21 -131
  241. package/dist/shared/agent-config.d.ts +8 -18
  242. package/dist/shared/agent-config.d.ts.map +1 -1
  243. package/dist/shared/agent-config.js +3 -4
  244. package/dist/shared/inbox.d.ts +27 -0
  245. package/dist/shared/inbox.d.ts.map +1 -1
  246. package/dist/shared/inbox.js +1 -0
  247. package/dist/shared/server-info.d.ts +6 -5
  248. package/dist/shared/server-info.d.ts.map +1 -1
  249. package/dist/shared/server-info.js +3 -1
  250. package/dist/shared/server-settings.d.ts +6 -0
  251. package/dist/shared/server-settings.d.ts.map +1 -1
  252. package/dist/shared/server-settings.js +1 -0
  253. package/dist/shared/skills.d.ts +24 -3
  254. package/dist/shared/skills.d.ts.map +1 -1
  255. package/dist/web/assets/activity-DHjip8tY.js +7 -0
  256. package/dist/web/assets/clipboard-D2NSC52v.js +1 -0
  257. package/dist/web/assets/{external-link-Dt0qTpmV.js → external-link-Bhr1WdRS.js} +1 -1
  258. package/dist/web/assets/{file-text-Dd9ys6py.js → file-text-CYMA_SMA.js} +1 -1
  259. package/dist/web/assets/{index-CzAQnbuO.js → index-CVpew409.js} +5 -5
  260. package/dist/web/assets/index-mh0uJTkY.css +2 -0
  261. package/dist/web/assets/kb-DDVpYkT9.js +35 -0
  262. package/dist/web/assets/profile-CBy6GNzp.js +1 -0
  263. package/dist/web/assets/reminders-Bbwb9QNT.js +1 -0
  264. package/dist/web/favicon.svg +1 -1
  265. package/dist/web/index.html +2 -2
  266. package/package.json +1 -1
  267. package/templates/runtime-standing-prompt.md +16 -2
  268. package/templates/skills/find-skills/SKILL.md +133 -0
  269. package/dist/server/runtime/activity-payload.d.ts.map +0 -1
  270. package/dist/server/runtime/activity-text.d.ts +0 -4
  271. package/dist/server/runtime/activity-text.d.ts.map +0 -1
  272. package/dist/server/runtime/activity-text.js +0 -16
  273. package/dist/server/runtime/managed-runtime.d.ts.map +0 -1
  274. package/dist/server/runtime/provider-contract.d.ts.map +0 -1
  275. package/dist/server/runtime/runtime-release.d.ts.map +0 -1
  276. package/dist/server/runtime/runtime-upgrade.d.ts.map +0 -1
  277. package/dist/server/slack/shortcut.service.d.ts.map +0 -1
  278. package/dist/server/slack/shortcut.service.js +0 -493
  279. package/dist/web/assets/activity-BP0C81IV.js +0 -7
  280. package/dist/web/assets/globe-DmDZSgDP.js +0 -1
  281. package/dist/web/assets/index-DKecrvN0.css +0 -2
  282. package/dist/web/assets/kb-3wFeP2Jt.js +0 -32
  283. package/dist/web/assets/profile-D7wPZXW_.js +0 -1
  284. package/dist/web/assets/reminders-DOhroVwo.js +0 -1
  285. /package/dist/server/{runtime/provider-contract.js → providers/contract.js} +0 -0
  286. /package/dist/server/{runtime → runtime-management}/managed-runtime.d.ts +0 -0
  287. /package/dist/server/{runtime → runtime-management}/managed-runtime.js +0 -0
  288. /package/dist/server/{runtime → runtime-management}/runtime-release.d.ts +0 -0
  289. /package/dist/server/{runtime → runtime-management}/runtime-release.js +0 -0
@@ -0,0 +1,371 @@
1
+ import { once } from 'node:events';
2
+ import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+ import assert from 'node:assert/strict';
7
+ import { createWebServer } from '../web/app.js';
8
+ import { defaultKbRegistryService } from '../kb/kb.service.js';
9
+ import { withAnimaHome } from './anima-home.js';
10
+ // Builds a temp ANIMA_HOME with a single kb root. The root `.gitignore`,
11
+ // when present, is the exposure boundary; otherwise visible files are read from
12
+ // disk. We add ordinary files, ignored files, a `.git/` metadata directory, and
13
+ // symlinks — covering every boundary branch.
14
+ async function setupKb(prefix) {
15
+ const homeDir = await mkdtemp(join(tmpdir(), `${prefix}-home-`));
16
+ const repoDir = await mkdtemp(join(tmpdir(), `${prefix}-repo-`));
17
+ await mkdir(join(repoDir, 'docs'), { recursive: true });
18
+ await writeFile(join(repoDir, 'README.md'), '# Title\n\nSome **markdown** body.\n', 'utf8');
19
+ await writeFile(join(repoDir, 'docs', 'data.json'), '{"key":"value","n":1}\n', 'utf8');
20
+ await writeFile(join(repoDir, 'docs', 'report.html'), '<!doctype html><html><body><h1>Report</h1><script>console.log("interactive")</script></body></html>\n', 'utf8');
21
+ await writeFile(join(repoDir, 'docs', 'app.ts'), 'export const answer = 42;\n', 'utf8');
22
+ await writeFile(join(repoDir, '.gitignore'), 'secret.txt\nignored/\n*.env\n!keep.env\n', 'utf8');
23
+ // Ignored paths — must never surface.
24
+ await writeFile(join(repoDir, 'secret.txt'), 'TOPSECRET-TOKEN\n', 'utf8');
25
+ await mkdir(join(repoDir, 'ignored'), { recursive: true });
26
+ await writeFile(join(repoDir, 'ignored', 'hidden.txt'), 'hidden\n', 'utf8');
27
+ await writeFile(join(repoDir, 'fake.env'), 'FAKE_ENV_SECRET\n', 'utf8');
28
+ await writeFile(join(repoDir, 'keep.env'), 'allowed by negation\n', 'utf8');
29
+ // Untracked/uncommitted by git terms: present on disk and visible because it
30
+ // is not ignored.
31
+ await writeFile(join(repoDir, 'untracked.txt'), 'not added to git\n', 'utf8');
32
+ await writeFile(join(repoDir, 'docs', 'untracked-target.md'), 'untracked target\n', 'utf8');
33
+ await mkdir(join(repoDir, '.git'), { recursive: true });
34
+ await writeFile(join(repoDir, '.git', 'config'), '[core]\n', 'utf8');
35
+ // Symlink pointing at a visible file; allowed only because the resolved target
36
+ // remains under the root and is itself visible.
37
+ await symlink('README.md', join(repoDir, 'link.md'));
38
+ await symlink('docs/untracked-target.md', join(repoDir, 'untracked-link.md'));
39
+ await symlink('/etc/passwd', join(repoDir, 'escape-link.md'));
40
+ await writeFile(join(homeDir, 'config.json'), `${JSON.stringify({}, null, 2)}\n`, 'utf8');
41
+ await writeTestKbConfig(homeDir, { id: 'test', label: 'Test', path: repoDir });
42
+ return { homeDir, repoDir };
43
+ }
44
+ async function writeTestKbConfig(homeDir, kb) {
45
+ const dir = join(homeDir, 'kbs', kb.id);
46
+ await mkdir(dir, { recursive: true });
47
+ await writeFile(join(dir, 'config.json'), `${JSON.stringify({ label: kb.label, path: kb.path }, null, 2)}\n`, 'utf8');
48
+ }
49
+ async function withServer(homeDir, body) {
50
+ await withAnimaHome(homeDir, async () => {
51
+ defaultKbRegistryService.clearCaches();
52
+ const server = await createWebServer();
53
+ try {
54
+ server.listen(0, '127.0.0.1');
55
+ await once(server, 'listening');
56
+ const address = server.address();
57
+ if (!address || typeof address === 'string')
58
+ throw new Error('Expected TCP address');
59
+ await body(`http://127.0.0.1:${address.port}`);
60
+ }
61
+ finally {
62
+ server.close();
63
+ defaultKbRegistryService.clearCaches();
64
+ }
65
+ });
66
+ }
67
+ test('kb roots endpoint lists configured roots without paths', async () => {
68
+ const { homeDir, repoDir } = await setupKb('anima-kb-roots');
69
+ try {
70
+ await withServer(homeDir, async (base) => {
71
+ const res = await fetch(`${base}/api/kbs`);
72
+ assert.equal(res.status, 200);
73
+ const body = (await res.json());
74
+ assert.deepEqual(body.kbs, [{ id: 'test', label: 'Test' }]);
75
+ assert.equal(body.kbs[0] && 'path' in body.kbs[0], false, 'absolute path must not leak');
76
+ });
77
+ }
78
+ finally {
79
+ await rm(homeDir, { force: true, recursive: true });
80
+ await rm(repoDir, { force: true, recursive: true });
81
+ }
82
+ });
83
+ test('kb roots can be added and removed without restarting the web app', async () => {
84
+ const { homeDir, repoDir } = await setupKb('anima-kb-mutate');
85
+ const secondRepo = await mkdtemp(join(tmpdir(), 'anima-kb-mutate-second-'));
86
+ try {
87
+ await writeFile(join(secondRepo, 'SECOND.md'), '# Second\n', 'utf8');
88
+ await mkdir(join(secondRepo, '.git'), { recursive: true });
89
+ await writeFile(join(secondRepo, '.git', 'config'), '[core]\n', 'utf8');
90
+ await withServer(homeDir, async (base) => {
91
+ const add = await fetch(`${base}/api/kbs`, {
92
+ body: JSON.stringify({ id: 'second', label: 'Second', path: secondRepo }),
93
+ headers: { 'content-type': 'application/json' },
94
+ method: 'POST',
95
+ });
96
+ assert.equal(add.status, 200);
97
+ assert.deepEqual(await add.json(), {
98
+ kbs: [
99
+ { id: 'second', label: 'Second' },
100
+ { id: 'test', label: 'Test' },
101
+ ],
102
+ });
103
+ // The mutation clears the 5 s root cache, so the new root is browsable
104
+ // immediately without a serve bounce.
105
+ const tree = await fetch(`${base}/api/kbs/second/tree`);
106
+ assert.equal(tree.status, 200);
107
+ const treeBody = (await tree.json());
108
+ assert.deepEqual(treeBody.nodes.map((node) => node.name), ['SECOND.md']);
109
+ const duplicate = await fetch(`${base}/api/kbs`, {
110
+ body: JSON.stringify({ id: 'second', label: 'Duplicate', path: secondRepo }),
111
+ headers: { 'content-type': 'application/json' },
112
+ method: 'POST',
113
+ });
114
+ assert.equal(duplicate.status, 409);
115
+ const rename = await fetch(`${base}/api/kbs/second/rename`, {
116
+ body: JSON.stringify({ label: 'Second Renamed' }),
117
+ headers: { 'content-type': 'application/json' },
118
+ method: 'POST',
119
+ });
120
+ assert.equal(rename.status, 200);
121
+ assert.deepEqual(await rename.json(), {
122
+ kbs: [
123
+ { id: 'second', label: 'Second Renamed' },
124
+ { id: 'test', label: 'Test' },
125
+ ],
126
+ });
127
+ const badRename = await fetch(`${base}/api/kbs/second/rename`, {
128
+ body: JSON.stringify({ label: ' ' }),
129
+ headers: { 'content-type': 'application/json' },
130
+ method: 'POST',
131
+ });
132
+ assert.equal(badRename.status, 400);
133
+ const missing = await fetch(`${base}/api/kbs`, {
134
+ body: JSON.stringify({ id: 'missing', label: 'Missing', path: join(secondRepo, 'missing') }),
135
+ headers: { 'content-type': 'application/json' },
136
+ method: 'POST',
137
+ });
138
+ assert.equal(missing.status, 400);
139
+ const remove = await fetch(`${base}/api/kbs/second`, { method: 'DELETE' });
140
+ assert.equal(remove.status, 200);
141
+ assert.deepEqual(await remove.json(), { kbs: [{ id: 'test', label: 'Test' }] });
142
+ const removedTree = await fetch(`${base}/api/kbs/second/tree`);
143
+ assert.equal(removedTree.status, 404);
144
+ const config = JSON.parse(await readFile(join(homeDir, 'kbs', 'test', 'config.json'), 'utf8'));
145
+ assert.deepEqual(config, { label: 'Test', path: repoDir });
146
+ });
147
+ }
148
+ finally {
149
+ await rm(homeDir, { force: true, recursive: true });
150
+ await rm(repoDir, { force: true, recursive: true });
151
+ await rm(secondRepo, { force: true, recursive: true });
152
+ }
153
+ });
154
+ test('kb directory browser is home-bound and returns directories only', async () => {
155
+ const { homeDir, repoDir } = await setupKb('anima-kb-browse');
156
+ const browseRoot = await mkdtemp(join(homedir(), 'anima-kb-browse-'));
157
+ try {
158
+ await mkdir(join(browseRoot, 'visible-dir'));
159
+ await mkdir(join(browseRoot, '.hidden-dir'));
160
+ await writeFile(join(browseRoot, 'file.txt'), 'not a dir\n', 'utf8');
161
+ await withServer(homeDir, async (base) => {
162
+ const rootRes = await fetch(`${base}/api/filesystem/browse?path=${encodeURIComponent(browseRoot)}`);
163
+ assert.equal(rootRes.status, 200);
164
+ const body = (await rootRes.json());
165
+ assert.equal(body.path, browseRoot);
166
+ assert.deepEqual(body.entries, [{ name: 'visible-dir', path: join(browseRoot, 'visible-dir') }]);
167
+ const homeRes = await fetch(`${base}/api/filesystem/browse`);
168
+ assert.equal(homeRes.status, 200, 'omitted path defaults to the server home directory');
169
+ const outside = await fetch(`${base}/api/filesystem/browse?path=${encodeURIComponent(tmpdir())}`);
170
+ assert.equal(outside.status, 400, 'directory browser cannot escape $HOME');
171
+ });
172
+ }
173
+ finally {
174
+ await rm(homeDir, { force: true, recursive: true });
175
+ await rm(repoDir, { force: true, recursive: true });
176
+ await rm(browseRoot, { force: true, recursive: true });
177
+ }
178
+ });
179
+ test('kb tree exposes non-ignored files in a nested shape', async () => {
180
+ const { homeDir, repoDir } = await setupKb('anima-kb-tree');
181
+ try {
182
+ await withServer(homeDir, async (base) => {
183
+ const res = await fetch(`${base}/api/kbs/test/tree`);
184
+ assert.equal(res.status, 200);
185
+ const body = (await res.json());
186
+ assert.equal(body.kb.id, 'test');
187
+ const topNames = body.nodes.map((n) => n.name);
188
+ assert.ok(topNames.includes('docs'), 'docs dir present');
189
+ assert.ok(topNames.includes('README.md'), 'README present');
190
+ assert.ok(topNames.includes('.gitignore'), '.gitignore is itself content');
191
+ assert.ok(topNames.includes('untracked.txt'), 'untracked/uncommitted file present');
192
+ assert.ok(topNames.includes('keep.env'), 'negated gitignore pattern is honored');
193
+ assert.ok(!topNames.includes('secret.txt'), 'ignored secret absent');
194
+ assert.ok(!topNames.includes('ignored'), 'ignored directory absent');
195
+ assert.ok(!topNames.includes('fake.env'), 'glob-ignored env file absent');
196
+ assert.ok(!topNames.includes('.git'), '.git metadata absent');
197
+ const docs = body.nodes.find((n) => n.name === 'docs');
198
+ assert.equal(docs?.type, 'dir');
199
+ const docChildren = (docs?.children ?? []).map((c) => c.name);
200
+ assert.deepEqual([...docChildren].sort(), ['app.ts', 'data.json', 'report.html', 'untracked-target.md']);
201
+ });
202
+ }
203
+ finally {
204
+ await rm(homeDir, { force: true, recursive: true });
205
+ await rm(repoDir, { force: true, recursive: true });
206
+ }
207
+ });
208
+ test('kb without root gitignore exposes all files except git metadata', async () => {
209
+ const homeDir = await mkdtemp(join(tmpdir(), 'anima-kb-nogitignore-home-'));
210
+ const rootDir = await mkdtemp(join(tmpdir(), 'anima-kb-nogitignore-root-'));
211
+ try {
212
+ await writeFile(join(rootDir, 'notes.md'), '# Notes\n', 'utf8');
213
+ await writeFile(join(rootDir, '.env'), 'VISIBLE_WITHOUT_GITIGNORE=1\n', 'utf8');
214
+ await mkdir(join(rootDir, 'nested'), { recursive: true });
215
+ await writeFile(join(rootDir, 'nested', 'data.txt'), 'nested\n', 'utf8');
216
+ await mkdir(join(rootDir, '.git'), { recursive: true });
217
+ await writeFile(join(rootDir, '.git', 'config'), '[core]\n', 'utf8');
218
+ await writeFile(join(homeDir, 'config.json'), `${JSON.stringify({}, null, 2)}\n`, 'utf8');
219
+ await writeTestKbConfig(homeDir, { id: 'plain', label: 'Plain', path: rootDir });
220
+ await withServer(homeDir, async (base) => {
221
+ const res = await fetch(`${base}/api/kbs/plain/tree`);
222
+ assert.equal(res.status, 200);
223
+ const body = (await res.json());
224
+ const topNames = body.nodes.map((node) => node.name);
225
+ assert.ok(topNames.includes('notes.md'));
226
+ assert.ok(topNames.includes('.env'), 'without .gitignore, dot/env files are content');
227
+ assert.ok(topNames.includes('nested'));
228
+ assert.ok(!topNames.includes('.git'), '.git metadata is never content');
229
+ const envRes = await fetch(`${base}/api/kbs/plain/file?path=.env`);
230
+ assert.equal(envRes.status, 200);
231
+ assert.match(await envRes.text(), /VISIBLE_WITHOUT_GITIGNORE/);
232
+ });
233
+ }
234
+ finally {
235
+ await rm(homeDir, { force: true, recursive: true });
236
+ await rm(rootDir, { force: true, recursive: true });
237
+ }
238
+ });
239
+ test('kb file endpoint classifies and inlines tracked text', async () => {
240
+ const { homeDir, repoDir } = await setupKb('anima-kb-file');
241
+ try {
242
+ await withServer(homeDir, async (base) => {
243
+ const md = await fetch(`${base}/api/kbs/test/file?path=README.md`);
244
+ assert.equal(md.status, 200);
245
+ const mdBody = (await md.json());
246
+ assert.equal(mdBody.kind, 'markdown');
247
+ assert.equal(mdBody.name, 'README.md');
248
+ assert.match(mdBody.content ?? '', /Some \*\*markdown\*\*/);
249
+ const jsonRes = await fetch(`${base}/api/kbs/test/file?path=docs/data.json`);
250
+ const jsonBody = (await jsonRes.json());
251
+ assert.equal(jsonBody.kind, 'json');
252
+ assert.match(jsonBody.content ?? '', /"key":"value"/);
253
+ const codeRes = await fetch(`${base}/api/kbs/test/file?path=docs/app.ts`);
254
+ const codeBody = (await codeRes.json());
255
+ assert.equal(codeBody.kind, 'code');
256
+ assert.equal(codeBody.language, 'typescript');
257
+ // HTML is served via the raw route, so the file API does not inline bytes.
258
+ const htmlRes = await fetch(`${base}/api/kbs/test/file?path=docs/report.html`);
259
+ const htmlBody = (await htmlRes.json());
260
+ assert.equal(htmlBody.kind, 'html');
261
+ assert.equal(htmlBody.content, undefined);
262
+ });
263
+ }
264
+ finally {
265
+ await rm(homeDir, { force: true, recursive: true });
266
+ await rm(repoDir, { force: true, recursive: true });
267
+ }
268
+ });
269
+ test('kb download endpoint serves file with attachment headers', async () => {
270
+ const { homeDir, repoDir } = await setupKb('anima-kb-discoverability');
271
+ try {
272
+ await withServer(homeDir, async (base) => {
273
+ const download = await fetch(`${base}/api/kbs/test/download?path=${encodeURIComponent('README.md')}`);
274
+ assert.equal(download.status, 200);
275
+ assert.match(download.headers.get('content-disposition') ?? '', /attachment/);
276
+ assert.match(download.headers.get('content-disposition') ?? '', /README\.md/);
277
+ assert.match(await download.text(), /Some \*\*markdown\*\* body/);
278
+ });
279
+ }
280
+ finally {
281
+ await rm(homeDir, { force: true, recursive: true });
282
+ await rm(repoDir, { force: true, recursive: true });
283
+ }
284
+ });
285
+ test('kb boundary refuses ignored, traversal, and unsafe symlink paths', async () => {
286
+ const { homeDir, repoDir } = await setupKb('anima-kb-boundary');
287
+ try {
288
+ await withServer(homeDir, async (base) => {
289
+ const untracked = await fetch(`${base}/api/kbs/test/file?path=untracked.txt`);
290
+ assert.equal(untracked.status, 200, 'untracked/uncommitted file is visible when not ignored');
291
+ const secret = await fetch(`${base}/api/kbs/test/file?path=secret.txt`);
292
+ assert.equal(secret.status, 404, 'ignored secret never serves');
293
+ const ignoredGlob = await fetch(`${base}/api/kbs/test/file?path=fake.env`);
294
+ assert.equal(ignoredGlob.status, 404, 'glob-ignored env file never serves');
295
+ const traversal = await fetch(`${base}/api/kbs/test/file?path=${encodeURIComponent('../../etc/passwd')}`);
296
+ assert.equal(traversal.status, 400, 'path traversal rejected pre-allowlist');
297
+ const symlinkRes = await fetch(`${base}/api/kbs/test/file?path=link.md`);
298
+ assert.equal(symlinkRes.status, 200, 'tracked same-root symlink to tracked target is allowed');
299
+ const symlinkBody = await symlinkRes.json();
300
+ assert.match(symlinkBody.content ?? '', /Some \*\*markdown\*\* body/);
301
+ const untrackedSymlink = await fetch(`${base}/api/kbs/test/file?path=untracked-link.md`);
302
+ assert.equal(untrackedSymlink.status, 200, 'symlink target may be untracked when it is visible');
303
+ const escapeSymlink = await fetch(`${base}/api/kbs/test/file?path=escape-link.md`);
304
+ assert.equal(escapeSymlink.status, 404, 'symlink target must stay under the same root');
305
+ const unknownRoot = await fetch(`${base}/api/kbs/nope/tree`);
306
+ assert.equal(unknownRoot.status, 404, 'unknown kb root');
307
+ });
308
+ }
309
+ finally {
310
+ await rm(homeDir, { force: true, recursive: true });
311
+ await rm(repoDir, { force: true, recursive: true });
312
+ }
313
+ });
314
+ test('kb raw route serves tracked bytes with hardened headers', async () => {
315
+ const { homeDir, repoDir } = await setupKb('anima-kb-raw');
316
+ try {
317
+ await withServer(homeDir, async (base) => {
318
+ const res = await fetch(`${base}/kb/raw/test/docs/report.html`);
319
+ assert.equal(res.status, 200);
320
+ assert.equal(res.headers.get('content-type'), 'text/html; charset=utf-8');
321
+ assert.equal(res.headers.get('x-content-type-options'), 'nosniff');
322
+ assert.equal(res.headers.get('cache-control'), 'no-store');
323
+ const csp = res.headers.get('content-security-policy') ?? '';
324
+ assert.match(csp, /script-src/, 'CSP present');
325
+ assert.doesNotMatch(csp, /script-src 'none'/, 'report scripts must run (sandboxed)');
326
+ assert.match(csp, /object-src 'none'/);
327
+ const html = await res.text();
328
+ assert.match(html, /<script>console\.log/);
329
+ });
330
+ }
331
+ finally {
332
+ await rm(homeDir, { force: true, recursive: true });
333
+ await rm(repoDir, { force: true, recursive: true });
334
+ }
335
+ });
336
+ test('malformed kbs config surfaces a visible error, not an empty surface', async () => {
337
+ // milo review: a config validation failure must not silently degrade to
338
+ // `200 {kbs:[]}` — that would hide the exact config boundary we want loud.
339
+ const homeDir = await mkdtemp(join(tmpdir(), 'anima-kb-badconfig-home-'));
340
+ try {
341
+ await writeFile(join(homeDir, 'config.json'), `${JSON.stringify({}, null, 2)}\n`, 'utf8');
342
+ await mkdir(join(homeDir, 'kbs', 'bad'), { recursive: true });
343
+ await writeFile(join(homeDir, 'kbs', 'bad', 'config.json'), `${JSON.stringify({ label: '', path: '' }, null, 2)}\n`, 'utf8');
344
+ await withServer(homeDir, async (base) => {
345
+ const res = await fetch(`${base}/api/kbs`);
346
+ assert.equal(res.status, 500, 'malformed config must surface, not empty out');
347
+ const body = (await res.json());
348
+ assert.match(body.error ?? '', /label|path/);
349
+ });
350
+ }
351
+ finally {
352
+ await rm(homeDir, { force: true, recursive: true });
353
+ }
354
+ });
355
+ test('kb raw route 404s on a miss instead of falling through to the SPA', async () => {
356
+ const { homeDir, repoDir } = await setupKb('anima-kb-raw-miss');
357
+ try {
358
+ await withServer(homeDir, async (base) => {
359
+ const miss = await fetch(`${base}/kb/raw/test/missing.txt`);
360
+ assert.equal(miss.status, 404);
361
+ assert.doesNotMatch(miss.headers.get('content-type') ?? '', /text\/html/);
362
+ // Malformed (no <id>/<path> separator) is a bad request.
363
+ const malformed = await fetch(`${base}/kb/raw/test`);
364
+ assert.equal(malformed.status, 400);
365
+ });
366
+ }
367
+ finally {
368
+ await rm(homeDir, { force: true, recursive: true });
369
+ await rm(repoDir, { force: true, recursive: true });
370
+ }
371
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=message.service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message.service.test.d.ts","sourceRoot":"","sources":["../../../server/tests/message.service.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,151 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { activityServiceForAgent } from '../activities/activity.service.js';
7
+ import { WakeQueueService } from '../inbox/wake-queue.service.js';
8
+ import { messageServiceForAgent } from '../messages/message.service.js';
9
+ import { MessageStore } from '../storage/schema/message.store.js';
10
+ import { withToolActivity } from '../tools/tool-context.js';
11
+ import { withAnimaHome } from './anima-home.js';
12
+ import { makeSlackEvent } from './helpers/slack.js';
13
+ test('message service projects legacy inbox and activity records into one ledger', async () => {
14
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-message-service-test-'));
15
+ try {
16
+ await withAnimaHome(stateDir, async () => {
17
+ await new WakeQueueService('scout').enqueue(makeSlackEvent({
18
+ actor: { handle: 'alice' },
19
+ channelId: 'C-product',
20
+ channelName: 'product',
21
+ eventId: 'evt-message-ledger-in',
22
+ teamId: 'T-demo',
23
+ text: 'Can you check the launch note?',
24
+ timestamp: '2026-05-11T00:00:00.000Z',
25
+ ts: '1770000100.000001',
26
+ userId: 'U-alice',
27
+ }));
28
+ await activityServiceForAgent('scout').record({
29
+ createdAt: '2026-05-11T00:01:00.000Z',
30
+ payload: {
31
+ channel: 'C-product',
32
+ channelName: 'product',
33
+ effect: 'slack.message.send',
34
+ status: 'completed',
35
+ text: 'Looks good.',
36
+ ts: '1770000101.000001',
37
+ },
38
+ type: 'external.effect.completed',
39
+ });
40
+ const page = await messageServiceForAgent('scout').list({ limit: 10 });
41
+ assert.deepEqual(page.entries.map((entry) => entry.direction), ['out', 'in']);
42
+ assert.equal(page.entries[0]?.text, 'Looks good.');
43
+ assert.equal(page.entries[1]?.actor, '@alice');
44
+ const repeated = await messageServiceForAgent('scout').list({ limit: 10 });
45
+ assert.equal(repeated.entries.length, 2);
46
+ assert.equal((await new MessageStore('scout').readAll()).length, 2);
47
+ });
48
+ }
49
+ finally {
50
+ await rm(stateDir, { force: true, recursive: true });
51
+ }
52
+ });
53
+ test('message service reads newest matching page without requiring a full ledger sort', async () => {
54
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-message-page-test-'));
55
+ try {
56
+ await withAnimaHome(stateDir, async () => {
57
+ await new MessageStore('scout').appendManyIfAbsent([
58
+ testMessage({ messageId: 'old-in', timestamp: '2026-05-11T00:00:00.000Z', direction: 'in' }),
59
+ testMessage({ messageId: 'mid-out', timestamp: '2026-05-11T00:01:00.000Z', direction: 'out' }),
60
+ testMessage({ messageId: 'new-in', timestamp: '2026-05-11T00:02:00.000Z', direction: 'in' }),
61
+ ]);
62
+ await new MessageStore('scout').markLegacyBackfilled();
63
+ const firstPage = await messageServiceForAgent('scout').list({ limit: 2 });
64
+ assert.deepEqual(firstPage.entries.map((entry) => entry.messageId), ['new-in', 'mid-out']);
65
+ assert.equal(firstPage.nextCursor, '2026-05-11T00:01:00.000Z');
66
+ const inboxPage = await messageServiceForAgent('scout').list({ direction: 'in', limit: 2 });
67
+ assert.deepEqual(inboxPage.entries.map((entry) => entry.messageId), ['new-in', 'old-in']);
68
+ assert.equal(inboxPage.nextCursor, null);
69
+ });
70
+ }
71
+ finally {
72
+ await rm(stateDir, { force: true, recursive: true });
73
+ }
74
+ });
75
+ test('wake queue enqueue writes inbound messages without duplicate ledger rows', async () => {
76
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-message-inbox-write-test-'));
77
+ try {
78
+ await withAnimaHome(stateDir, async () => {
79
+ const event = makeSlackEvent({
80
+ channelId: 'D-alice',
81
+ eventId: 'evt-message-ledger-dedupe',
82
+ teamId: 'T-demo',
83
+ text: 'hello',
84
+ timestamp: '2026-05-11T00:00:00.000Z',
85
+ ts: '1770000100.000001',
86
+ userId: 'U-alice',
87
+ });
88
+ const queue = new WakeQueueService('scout');
89
+ await queue.enqueue(event);
90
+ await queue.enqueue(event);
91
+ const messages = await new MessageStore('scout').readAll();
92
+ assert.equal(messages.length, 1);
93
+ assert.equal(messages[0]?.messageId, `msg_inbox:${event.id}`);
94
+ assert.equal(messages[0]?.direction, 'in');
95
+ });
96
+ }
97
+ finally {
98
+ await rm(stateDir, { force: true, recursive: true });
99
+ }
100
+ });
101
+ test('tool activity does not fail successful effects when message ledger write fails', async () => {
102
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-message-outbox-failure-test-'));
103
+ try {
104
+ await withAnimaHome(stateDir, async () => {
105
+ await mkdir(join(stateDir, 'agents/scout'), { recursive: true });
106
+ await writeFile(join(stateDir, 'agents/scout/messages.jsonl'), '{not-json}\n', 'utf8');
107
+ const result = await withMutedWarnings(() => withToolActivity({
108
+ audit: { agentId: 'scout' },
109
+ basePayload: { tool: 'anima.message.send' },
110
+ effectType: 'slack.message.send',
111
+ op: async () => ({
112
+ completedPayload: {
113
+ channel: 'C-product',
114
+ text: 'Sent successfully.',
115
+ ts: '1770000102.000001',
116
+ },
117
+ result: 'ok',
118
+ }),
119
+ }));
120
+ assert.equal(result, 'ok');
121
+ const activities = await activityServiceForAgent('scout').readAll();
122
+ assert.deepEqual(activities.map((activity) => activity.type), [
123
+ 'external.effect.started',
124
+ 'external.effect.completed',
125
+ ]);
126
+ });
127
+ }
128
+ finally {
129
+ await rm(stateDir, { force: true, recursive: true });
130
+ }
131
+ });
132
+ async function withMutedWarnings(op) {
133
+ const original = console.warn;
134
+ console.warn = () => undefined;
135
+ try {
136
+ return await op();
137
+ }
138
+ finally {
139
+ console.warn = original;
140
+ }
141
+ }
142
+ function testMessage(input) {
143
+ return {
144
+ direction: input.direction,
145
+ kind: 'message',
146
+ messageId: input.messageId,
147
+ source: { id: input.messageId, kind: 'activity' },
148
+ text: input.messageId,
149
+ timestamp: input.timestamp,
150
+ };
151
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=messages.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.test.d.ts","sourceRoot":"","sources":["../../../server/tests/messages.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { slackMessageContentForText } from '../tools/slack-message-format.js';
4
+ test('Slack markdown block content enforces body and fallback limits', () => {
5
+ const content = slackMessageContentForText(`Report\n${'é'.repeat(2_000)}`);
6
+ assert.equal(content.format, 'markdown');
7
+ assert.equal(content.blockCount, 1);
8
+ assert.deepEqual(content.blocks, [{ type: 'markdown', text: `Report\n${'é'.repeat(2_000)}` }]);
9
+ assert.ok(Buffer.byteLength(content.text, 'utf8') <= 3500);
10
+ assert.ok(content.text.endsWith('…'));
11
+ assert.throws(() => slackMessageContentForText('X'.repeat(12_001)), /message is too long for Slack markdown block: 12001 characters, Slack allows 12000; send a file instead/);
12
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prompt-attachments.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-attachments.test.d.ts","sourceRoot":"","sources":["../../../server/tests/prompt-attachments.test.ts"],"names":[],"mappings":""}