@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,387 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { once } from 'node:events';
3
+ import { createServer } from 'node:http';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import { mkdir, mkdtemp } from 'node:fs/promises';
7
+ import test from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { interactiveAskServiceForAgent } from '../asks/interactive-ask.service.js';
10
+ import { activityServiceForAgent } from '../activities/activity.service.js';
11
+ import { withAnimaHome } from '../anima-home.js';
12
+ import { WakeQueueService } from '../inbox/wake-queue.service.js';
13
+ import { buildCodeAgentDeliveryPrompt } from '../runtime/delivery-prompt.js';
14
+ import { AgentStore } from '../storage/schema/agent.store.js';
15
+ import { InteractiveAskStore } from '../storage/schema/interactive-ask.store.js';
16
+ import { SubscriptionStore } from '../storage/schema/subscription.store.js';
17
+ const cliPath = resolve('dist/server/cli/anima.js');
18
+ test('anima ask posts Block Kit buttons, stores the ask, and subscribes to typed replies', async () => {
19
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-interactive-ask-cli-'));
20
+ const posts = [];
21
+ const slackApi = await startSlackApiMock((method, body) => {
22
+ if (method === 'conversations.info') {
23
+ return {
24
+ channel: { id: 'C-product', is_channel: true, name: 'product', name_normalized: 'product' },
25
+ ok: true,
26
+ };
27
+ }
28
+ if (method !== 'chat.postMessage')
29
+ throw new Error(`unexpected method ${method}`);
30
+ posts.push(slackRequestBody(body));
31
+ return { channel: 'C-product', ok: true, ts: '1770000400.000123' };
32
+ });
33
+ try {
34
+ await withAnimaHome(stateDir, async () => {
35
+ await writeAgentConfig('scout', {
36
+ slack: { appToken: 'xapp-test', botToken: 'xoxb-test', teamId: 'T-demo' },
37
+ });
38
+ const run = await runNode([
39
+ cliPath,
40
+ 'ask',
41
+ '--channel',
42
+ 'C-product',
43
+ '--question',
44
+ 'Ship this change?',
45
+ '--option',
46
+ 'Approve',
47
+ '--option',
48
+ 'Hold',
49
+ ], {
50
+ env: {
51
+ ...process.env,
52
+ ANIMA_AGENT_ID: 'scout',
53
+ ANIMA_HOME: stateDir,
54
+ ANIMA_SLACK_API_URL: slackApi.url,
55
+ },
56
+ });
57
+ assert.equal(run.status, 0, run.stderr || run.stdout);
58
+ assert.match(run.stdout.trim(), /^asked successfully\. channel=#product, message_ts=1770000400\.000123, ask_id=ask_/);
59
+ assert.equal(posts.length, 1);
60
+ assert.equal(posts[0]?.['channel'], 'C-product');
61
+ assert.equal(posts[0]?.['text'], [
62
+ 'Ship this change?',
63
+ '',
64
+ 'Options:',
65
+ '1. Approve',
66
+ '2. Hold',
67
+ '',
68
+ 'None fit? Just reply in this thread.',
69
+ ].join('\n'));
70
+ const blocks = slackBlocks(posts[0] ?? {});
71
+ assert.equal(blocks[0]?.type, 'section');
72
+ assert.equal(blocks[1]?.type, 'actions');
73
+ const button = blocks[1]?.elements?.[0];
74
+ assert.ok(button?.action_id?.startsWith('anima.ask.answer'));
75
+ assert.deepEqual(JSON.parse(String(button?.value)), {
76
+ askId: run.stdout.match(/ask_id=([^.\s]+)/)?.[1],
77
+ optionId: 'option_1',
78
+ });
79
+ const asks = await new InteractiveAskStore('scout').list();
80
+ const ask = asks[0];
81
+ assert.ok(ask);
82
+ assert.equal(ask.question, 'Ship this change?');
83
+ assert.equal(ask.messageTs, '1770000400.000123');
84
+ assert.equal(ask.allowAnyone, true);
85
+ assert.deepEqual(ask.options.map((option) => option.label), ['Approve', 'Hold']);
86
+ const subscriptions = await new SubscriptionStore('scout').list();
87
+ assert.equal(subscriptions.some((subscription) => subscription.kind === 'thread'
88
+ && subscription.channelId === 'C-product'
89
+ && subscription.threadTs === '1770000400.000123'), true);
90
+ const completed = (await activityServiceForAgent('scout').readAll()).find((activity) => activity.type === 'external.effect.completed'
91
+ && activity.payload?.['effect'] === 'slack.ask.post');
92
+ assert.equal(completed?.payload?.['tool'], 'anima.ask');
93
+ assert.equal(completed?.payload?.['question'], 'Ship this change?');
94
+ });
95
+ }
96
+ finally {
97
+ await slackApi.close();
98
+ }
99
+ });
100
+ test('interactive ask answer enqueues one choice_response and renders a reply target', async () => {
101
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-interactive-ask-answer-'));
102
+ await withAnimaHome(stateDir, async () => {
103
+ await writeAgentConfig('scout', {
104
+ slack: { appToken: 'xapp-test', botToken: 'xoxb-test', teamId: 'T-demo' },
105
+ });
106
+ const ask = await savePendingAsk({
107
+ allowedUserIds: ['U-operator'],
108
+ messageTs: '1770000500.000001',
109
+ threadTs: undefined,
110
+ });
111
+ const updates = [];
112
+ const client = fakeSlackClient({ updates });
113
+ const askService = interactiveAskServiceForAgent('scout');
114
+ const answered = await askService.answerAsk({
115
+ askId: ask.askId,
116
+ client,
117
+ optionId: 'option_2',
118
+ userId: 'U-operator',
119
+ });
120
+ assert.equal(answered.outcome, 'answered');
121
+ assert.equal(answered.queued, true);
122
+ await askService.replaceAnsweredMessage({ ask: answered.ask, client });
123
+ assert.equal(updates.length, 1);
124
+ const duplicate = await askService.answerAsk({
125
+ askId: ask.askId,
126
+ client,
127
+ optionId: 'option_1',
128
+ userId: 'U-operator',
129
+ });
130
+ assert.equal(duplicate.outcome, 'already_answered');
131
+ const items = await new WakeQueueService('scout').list();
132
+ const choice = items.find((item) => item.kind === 'choice_response');
133
+ assert.ok(choice);
134
+ assert.equal(choice.id, `choice:scout:${ask.askId}`);
135
+ assert.equal(choice.kind === 'choice_response' ? choice.optionLabel : undefined, 'Hold');
136
+ assert.equal(choice.kind === 'choice_response' ? choice.threadTs : undefined, '1770000500.000001');
137
+ assert.equal(items.filter((item) => item.kind === 'choice_response').length, 1);
138
+ const prompt = buildCodeAgentDeliveryPrompt(choice);
139
+ assert.match(prompt, /^Choice response:/);
140
+ assert.match(prompt, /Alice \(@alice, <@U-operator>\) selected: Hold/);
141
+ assert.match(prompt, /Use `anima message send --channel C-product --thread-ts 1770000500\.000001`/);
142
+ });
143
+ });
144
+ test('interactive ask rejects non-addressee clicks without waking the agent', async () => {
145
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-interactive-ask-forbidden-'));
146
+ await withAnimaHome(stateDir, async () => {
147
+ await writeAgentConfig('scout', {
148
+ slack: { appToken: 'xapp-test', botToken: 'xoxb-test', teamId: 'T-demo' },
149
+ });
150
+ const ask = await savePendingAsk({ allowedUserIds: ['U-operator'] });
151
+ const ephemerals = [];
152
+ const client = fakeSlackClient({ ephemerals });
153
+ const askService = interactiveAskServiceForAgent('scout');
154
+ const result = await askService.answerAsk({
155
+ askId: ask.askId,
156
+ client,
157
+ optionId: 'option_1',
158
+ userId: 'U-other',
159
+ });
160
+ assert.equal(result.outcome, 'forbidden');
161
+ await askService.notifyForbiddenClick({ ask: result.ask, client, userId: 'U-other' });
162
+ assert.equal((await new WakeQueueService('scout').list()).length, 0);
163
+ assert.equal(ephemerals.length, 1);
164
+ const storedAsk = await askService.getAsk(ask.askId);
165
+ assert.equal(storedAsk?.status, 'pending');
166
+ assert.match(storedAsk?.lastInteractionAt ?? '', /^\d{4}-/);
167
+ });
168
+ });
169
+ test('interactive ask retention prunes old answered asks but keeps pending asks', async () => {
170
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-interactive-ask-retention-'));
171
+ await withAnimaHome(stateDir, async () => {
172
+ const store = new InteractiveAskStore('scout');
173
+ await store.create(askRecord({
174
+ answeredAt: '2000-01-01T00:00:00.000Z',
175
+ answeredBy: { slackUserId: 'U-operator' },
176
+ askId: 'ask-old-answered',
177
+ chosenOptionId: 'option_1',
178
+ createdAt: '2000-01-01T00:00:00.000Z',
179
+ status: 'answered',
180
+ }));
181
+ await store.create(askRecord({
182
+ askId: 'ask-old-pending',
183
+ createdAt: '2000-01-01T00:00:00.000Z',
184
+ status: 'pending',
185
+ }));
186
+ await interactiveAskServiceForAgent('scout').saveAsk(askRecord({ askId: 'ask-new-pending' }));
187
+ assert.deepEqual((await store.list()).map((ask) => ask.askId).sort(), ['ask-new-pending', 'ask-old-pending']);
188
+ });
189
+ });
190
+ test('anima ask rejects bot users as explicit answer targets', async () => {
191
+ const stateDir = await mkdtemp(join(tmpdir(), 'anima-interactive-ask-bot-target-'));
192
+ const posts = [];
193
+ const slackApi = await startSlackApiMock((method, body) => {
194
+ if (method === 'conversations.info') {
195
+ return {
196
+ channel: { id: 'C-product', is_channel: true, name: 'product', name_normalized: 'product' },
197
+ ok: true,
198
+ };
199
+ }
200
+ if (method === 'users.list') {
201
+ return {
202
+ members: [{ id: 'UBOT', is_bot: true, name: 'buildbot' }],
203
+ ok: true,
204
+ };
205
+ }
206
+ if (method === 'chat.postMessage') {
207
+ posts.push(slackRequestBody(body));
208
+ return { channel: 'C-product', ok: true, ts: '1770000500.000002' };
209
+ }
210
+ throw new Error(`unexpected method ${method}`);
211
+ });
212
+ try {
213
+ await withAnimaHome(stateDir, async () => {
214
+ await writeAgentConfig('scout', {
215
+ slack: { appToken: 'xapp-test', botToken: 'xoxb-test', teamId: 'T-demo' },
216
+ });
217
+ const run = await runNode([
218
+ cliPath,
219
+ 'ask',
220
+ '--channel',
221
+ 'C-product',
222
+ '--question',
223
+ 'Ship this change?',
224
+ '--option',
225
+ 'Approve',
226
+ '--option',
227
+ 'Hold',
228
+ '--to',
229
+ '@buildbot',
230
+ ], {
231
+ env: {
232
+ ...process.env,
233
+ ANIMA_AGENT_ID: 'scout',
234
+ ANIMA_HOME: stateDir,
235
+ ANIMA_SLACK_API_URL: slackApi.url,
236
+ },
237
+ });
238
+ assert.notEqual(run.status, 0);
239
+ assert.match(run.stderr, /human Slack users, not bots/);
240
+ assert.equal(posts.length, 0);
241
+ });
242
+ }
243
+ finally {
244
+ await slackApi.close();
245
+ }
246
+ });
247
+ async function savePendingAsk(overrides = {}) {
248
+ const ask = askRecord(overrides);
249
+ await interactiveAskServiceForAgent('scout').saveAsk(ask);
250
+ return ask;
251
+ }
252
+ function askRecord(overrides = {}) {
253
+ const now = new Date().toISOString();
254
+ return {
255
+ agentId: 'scout',
256
+ askId: `ask_${Math.random().toString(36).slice(2)}`,
257
+ channelId: 'C-product',
258
+ channelName: '#product',
259
+ createdAt: now,
260
+ messageTs: '1770000500.000001',
261
+ options: [
262
+ { optionId: 'option_1', label: 'Approve' },
263
+ { optionId: 'option_2', label: 'Hold' },
264
+ ],
265
+ question: 'Ship this change?',
266
+ status: 'pending',
267
+ teamId: 'T-demo',
268
+ ...overrides,
269
+ };
270
+ }
271
+ async function writeAgentConfig(agentId, overrides = {}) {
272
+ const homePath = join(process.cwd(), '.tmp-home', agentId);
273
+ await mkdir(homePath, { recursive: true });
274
+ await new AgentStore(agentId).write({
275
+ createdAt: '2026-05-26T00:00:00.000Z',
276
+ enabled: true,
277
+ homePath,
278
+ id: agentId,
279
+ ...(overrides.operator ? { operator: overrides.operator } : {}),
280
+ profile: { displayName: 'Scout', role: 'Engineering agent' },
281
+ provider: { kind: 'codex-cli', model: 'gpt-5.3-codex' },
282
+ slack: {
283
+ appToken: overrides.slack?.appToken ?? '',
284
+ botToken: overrides.slack?.botToken ?? '',
285
+ connected: Boolean(overrides.slack?.appToken && overrides.slack?.botToken),
286
+ manifestVersion: 0,
287
+ teamId: overrides.slack?.teamId ?? '',
288
+ workspaceIconUrl: '',
289
+ workspaceName: '',
290
+ },
291
+ });
292
+ }
293
+ function fakeSlackClient(output = {}) {
294
+ return {
295
+ chat: {
296
+ postEphemeral: async (body) => {
297
+ output.ephemerals?.push(body);
298
+ return { ok: true };
299
+ },
300
+ update: async (body) => {
301
+ output.updates?.push(body);
302
+ return { ok: true };
303
+ },
304
+ },
305
+ users: {
306
+ info: async ({ user }) => ({
307
+ ok: true,
308
+ user: user === 'U-operator'
309
+ ? {
310
+ id: 'U-operator',
311
+ name: 'alice',
312
+ profile: { display_name: 'Alice' },
313
+ }
314
+ : { id: user, name: 'other' },
315
+ }),
316
+ },
317
+ };
318
+ }
319
+ async function runNode(args, opts = {}) {
320
+ const child = spawn(process.execPath, args, {
321
+ env: opts.env,
322
+ stdio: ['pipe', 'pipe', 'pipe'],
323
+ });
324
+ let stdout = '';
325
+ let stderr = '';
326
+ child.stdout.setEncoding('utf8');
327
+ child.stderr.setEncoding('utf8');
328
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
329
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
330
+ if (opts.input)
331
+ child.stdin.end(opts.input);
332
+ else
333
+ child.stdin.end();
334
+ const [code] = await once(child, 'close');
335
+ return { stdout, stderr, status: code };
336
+ }
337
+ async function startSlackApiMock(handler) {
338
+ const server = createServer(async (request, response) => {
339
+ try {
340
+ const body = await readBody(request);
341
+ const method = request.url?.split('/').pop() ?? '';
342
+ const payload = handler(method, body, request);
343
+ response.setHeader('content-type', 'application/json');
344
+ response.end(JSON.stringify(payload));
345
+ }
346
+ catch (error) {
347
+ response.statusCode = 500;
348
+ response.setHeader('content-type', 'application/json');
349
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error), ok: false }));
350
+ }
351
+ });
352
+ server.listen(0, '127.0.0.1');
353
+ await once(server, 'listening');
354
+ const address = server.address();
355
+ if (!address || typeof address === 'string')
356
+ throw new Error('Expected TCP address');
357
+ return {
358
+ close: async () => {
359
+ server.close();
360
+ await once(server, 'close');
361
+ },
362
+ url: `http://127.0.0.1:${address.port}/api`,
363
+ };
364
+ }
365
+ function slackRequestBody(body) {
366
+ try {
367
+ return JSON.parse(body);
368
+ }
369
+ catch {
370
+ return Object.fromEntries(new URLSearchParams(body));
371
+ }
372
+ }
373
+ function slackBlocks(body) {
374
+ if (Array.isArray(body['blocks'])) {
375
+ return body['blocks'];
376
+ }
377
+ assert.equal(typeof body['blocks'], 'string');
378
+ return JSON.parse(body['blocks']);
379
+ }
380
+ async function readBody(request) {
381
+ let body = '';
382
+ request.setEncoding('utf8');
383
+ for await (const chunk of request) {
384
+ body += chunk;
385
+ }
386
+ return body;
387
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kb.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kb.test.d.ts","sourceRoot":"","sources":["../../../server/tests/kb.test.ts"],"names":[],"mappings":""}