@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,636 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { once } from 'node:events';
3
+ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import test from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { cleanServiceEnv } from '../services/env.js';
9
+ const animactl = resolve('dist/server/cli/animactl.js');
10
+ test('cleanServiceEnv strips runtime item context before spawning services', () => {
11
+ const env = cleanServiceEnv({
12
+ ANIMA_AGENT_ID: 'milo',
13
+ ANIMA_HOME: '/tmp/source-home',
14
+ ANIMA_INBOX_ITEM_ID: 'item_123',
15
+ ANIMA_RUNTIME_HOME: '/tmp/source-home',
16
+ ANIMA_SLACK_BOT_TOKEN: 'xoxb-secret',
17
+ PATH: '/usr/bin',
18
+ SAFE_VALUE: 'kept',
19
+ SLACK_BOT_TOKEN: 'xoxb-secret',
20
+ });
21
+ assert.equal(env.PATH, '/usr/bin');
22
+ assert.equal(env.SAFE_VALUE, 'kept');
23
+ assert.equal(env.ANIMA_AGENT_ID, undefined);
24
+ assert.equal(env.ANIMA_HOME, undefined);
25
+ assert.equal(env.ANIMA_INBOX_ITEM_ID, undefined);
26
+ assert.equal(env.ANIMA_RUNTIME_HOME, undefined);
27
+ assert.equal(env.ANIMA_SLACK_BOT_TOKEN, undefined);
28
+ assert.equal(env.SLACK_BOT_TOKEN, undefined);
29
+ });
30
+ test('services status reports stopped agent and web with web URL', async () => {
31
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-status-'));
32
+ try {
33
+ const configDir = join(tempDir, '.anima');
34
+ await writeMinimalConfig(configDir, { dashboardPort: 4188 });
35
+ const status = await runAnimactl(['services', 'status'], { env: { ANIMA_HOME: configDir } });
36
+ assert.equal(status.status, 0, status.stderr || status.stdout);
37
+ assert.match(status.stdout, /agent \| stopped \| log .*\/agent\.log/);
38
+ assert.match(status.stdout, /web \| stopped \| http:\/\/127\.0\.0\.1:4188 \| log .*\/web\.log/);
39
+ assert.match(status.stdout, /\nDashboard: http:\/\/127\.0\.0\.1:4188/);
40
+ }
41
+ finally {
42
+ await rm(tempDir, { force: true, recursive: true });
43
+ }
44
+ });
45
+ test('services status uses default web port when config omits dashboardPort', async () => {
46
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-status-default-'));
47
+ try {
48
+ const configDir = join(tempDir, '.anima');
49
+ await writeMinimalConfig(configDir, {});
50
+ const status = await runAnimactl(['services', 'status'], { env: { ANIMA_HOME: configDir } });
51
+ assert.equal(status.status, 0, status.stderr || status.stdout);
52
+ assert.match(status.stdout, /web \| stopped \| http:\/\/127\.0\.0\.1:4174/);
53
+ assert.match(status.stdout, /\nDashboard: http:\/\/127\.0\.0\.1:4174/);
54
+ }
55
+ finally {
56
+ await rm(tempDir, { force: true, recursive: true });
57
+ }
58
+ });
59
+ test('services status ignores a pid file for an unrelated process', async () => {
60
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-running-'));
61
+ try {
62
+ const configDir = join(tempDir, '.anima');
63
+ await writeMinimalConfig(configDir, {});
64
+ await mkdir(join(configDir, 'run'), { recursive: true });
65
+ await writeFile(join(configDir, 'run', 'agent.pid'), `${process.pid}\n`, 'utf8');
66
+ const status = await runAnimactl(['services', 'status'], { env: { ANIMA_HOME: configDir } });
67
+ assert.equal(status.status, 0, status.stderr || status.stdout);
68
+ assert.match(status.stdout, /agent \| stopped/);
69
+ }
70
+ finally {
71
+ await rm(tempDir, { force: true, recursive: true });
72
+ }
73
+ });
74
+ test('server stays alive and logs invalid agents when none can start yet', async () => {
75
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-server-empty-'));
76
+ try {
77
+ const configDir = join(tempDir, '.anima');
78
+ await writeConfig(configDir, {
79
+ agents: [{ id: 'anima', homePath: join(tempDir, 'missing-home') }],
80
+ });
81
+ const server = await runAnimactlUntil(['server'], {
82
+ env: { ANIMA_HOME: configDir },
83
+ until: ({ stderr }) => /Agent anima failed to start/.test(stderr),
84
+ });
85
+ assert.match(server.stderr, /Agent anima failed to start: Agent anima: homePath must be an existing directory/);
86
+ assert.doesNotMatch(server.stderr, /No agents started/);
87
+ }
88
+ finally {
89
+ await rm(tempDir, { force: true, recursive: true });
90
+ }
91
+ });
92
+ test('server skips tokenless local agents and stays idle', async () => {
93
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-server-dormant-'));
94
+ try {
95
+ const configDir = join(tempDir, '.anima');
96
+ const homePath = join(tempDir, 'home');
97
+ await mkdir(homePath, { recursive: true });
98
+ await writeConfig(configDir, {
99
+ agents: [
100
+ {
101
+ id: 'anima',
102
+ provider: { kind: 'claude-code' },
103
+ homePath,
104
+ },
105
+ ],
106
+ });
107
+ const server = await runAnimactlUntil(['server'], {
108
+ env: { ANIMA_HOME: configDir },
109
+ until: ({ stdout }) => /Agent anima: idle \/ awaiting Slack connection/.test(stdout),
110
+ });
111
+ assert.match(server.stdout, /Agent anima: idle \/ awaiting Slack connection/);
112
+ assert.doesNotMatch(server.stderr, /No agents started/);
113
+ assert.doesNotMatch(server.stderr, /failed to start/);
114
+ }
115
+ finally {
116
+ await rm(tempDir, { force: true, recursive: true });
117
+ }
118
+ });
119
+ test('server --agent only loads the requested agent', async () => {
120
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-server-one-agent-'));
121
+ try {
122
+ const configDir = join(tempDir, '.anima');
123
+ const homePath = join(tempDir, 'home');
124
+ await mkdir(homePath, { recursive: true });
125
+ await writeConfig(configDir, {
126
+ agents: [
127
+ {
128
+ id: 'broken',
129
+ provider: { kind: 'claude-code' },
130
+ homePath: join(tempDir, 'missing-home'),
131
+ },
132
+ {
133
+ id: 'scout',
134
+ provider: { kind: 'claude-code' },
135
+ homePath,
136
+ },
137
+ ],
138
+ });
139
+ const server = await runAnimactlUntil(['--agent', 'scout', 'server'], {
140
+ env: { ANIMA_HOME: configDir },
141
+ until: ({ stdout }) => /Agent scout: idle \/ awaiting Slack connection/.test(stdout),
142
+ });
143
+ assert.match(server.stdout, /Agent scout: idle \/ awaiting Slack connection/);
144
+ assert.doesNotMatch(server.stderr, /No agents started/);
145
+ assert.doesNotMatch(server.stderr + server.stdout, /broken/);
146
+ }
147
+ finally {
148
+ await rm(tempDir, { force: true, recursive: true });
149
+ }
150
+ });
151
+ test('services restart refuses to stop its own active runtime', async () => {
152
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-self-restart-'));
153
+ try {
154
+ const configDir = join(tempDir, '.anima');
155
+ await writeMinimalConfig(configDir, {});
156
+ const restart = await runAnimactl(['services', 'restart'], {
157
+ env: {
158
+ ANIMA_INBOX_ITEM_ID: 'item_self_restart',
159
+ ANIMA_HOME: configDir,
160
+ ANIMA_RUNTIME_HOME: configDir,
161
+ },
162
+ });
163
+ assert.equal(restart.status, 1);
164
+ assert.match(restart.stderr, /Refusing to stop or restart the agent service from inside its own active runtime/);
165
+ assert.doesNotMatch(restart.stdout, /stopped pid/);
166
+ assert.doesNotMatch(restart.stdout, /started pid/);
167
+ }
168
+ finally {
169
+ await rm(tempDir, { force: true, recursive: true });
170
+ }
171
+ });
172
+ test('services restart permits web-only restart from inside its own active runtime', async () => {
173
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-self-web-restart-'));
174
+ const childPids = new Set();
175
+ try {
176
+ const configDir = join(tempDir, '.anima');
177
+ await writeMinimalConfig(configDir, {});
178
+ const restart = await runAnimactl(['services', 'restart', '--only', 'web'], {
179
+ env: {
180
+ ANIMA_INBOX_ITEM_ID: 'item_self_ui_restart',
181
+ ANIMA_HOME: configDir,
182
+ ANIMA_RUNTIME_HOME: configDir,
183
+ },
184
+ });
185
+ for (const match of restart.stdout.matchAll(/started pid (\d+)/g)) {
186
+ const pidText = match[1];
187
+ if (pidText)
188
+ childPids.add(Number.parseInt(pidText, 10));
189
+ }
190
+ assert.equal(restart.status, 0, restart.stderr || restart.stdout);
191
+ assert.doesNotMatch(restart.stderr, /Refusing/);
192
+ assert.doesNotMatch(restart.stdout, /agent:/);
193
+ assert.match(restart.stdout, /web: started pid/);
194
+ }
195
+ finally {
196
+ for (const pid of childPids) {
197
+ try {
198
+ process.kill(pid, 'SIGTERM');
199
+ }
200
+ catch { }
201
+ }
202
+ await rm(tempDir, { force: true, recursive: true });
203
+ }
204
+ });
205
+ test('services restart proceeds when invoked from a different environment', async () => {
206
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-cross-restart-'));
207
+ const childPids = new Set();
208
+ try {
209
+ const targetConfigDir = join(tempDir, 'target', '.anima');
210
+ const otherConfigDir = join(tempDir, 'other', '.anima');
211
+ await writeMinimalConfig(targetConfigDir, {});
212
+ await writeMinimalConfig(otherConfigDir, {});
213
+ const restart = await runAnimactl(['services', 'restart'], {
214
+ env: {
215
+ ANIMA_INBOX_ITEM_ID: 'item_cross_restart',
216
+ ANIMA_HOME: targetConfigDir,
217
+ ANIMA_RUNTIME_HOME: otherConfigDir,
218
+ },
219
+ });
220
+ for (const match of restart.stdout.matchAll(/started pid (\d+)/g)) {
221
+ const pidText = match[1];
222
+ if (pidText)
223
+ childPids.add(Number.parseInt(pidText, 10));
224
+ }
225
+ assert.equal(restart.status, 0, restart.stderr || restart.stdout);
226
+ assert.doesNotMatch(restart.stderr, /Refusing/);
227
+ assert.match(restart.stdout, /agent: started pid/);
228
+ assert.match(restart.stdout, /web: started pid/);
229
+ }
230
+ finally {
231
+ for (const pid of childPids) {
232
+ try {
233
+ process.kill(pid, 'SIGTERM');
234
+ }
235
+ catch { }
236
+ }
237
+ await rm(tempDir, { force: true, recursive: true });
238
+ }
239
+ });
240
+ test('services restart refuses to kill running or queued inbox items by default', async () => {
241
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-restart-gate-'));
242
+ const childPids = new Set();
243
+ try {
244
+ const configDir = join(tempDir, '.anima');
245
+ await writeTokenlessAgentConfig(configDir, tempDir);
246
+ const start = await runAnimactl(['services', 'start', '--only', 'agent'], {
247
+ env: { ANIMA_HOME: configDir },
248
+ });
249
+ collectStartedPids(start.stdout, childPids);
250
+ assert.equal(start.status, 0, start.stderr || start.stdout);
251
+ await writeInbox(configDir, 'anima', [
252
+ slackInboxItem('item_running', 'running', 'Felix is working on this one.'),
253
+ slackInboxItem('item_queued', 'queued', 'Queued message that could be claimed during restart.'),
254
+ ]);
255
+ const restart = await runAnimactl(['services', 'restart', '--idle-timeout-ms', '0'], {
256
+ env: { ANIMA_HOME: configDir },
257
+ });
258
+ assert.equal(restart.status, 1);
259
+ assert.match(restart.stderr, /Timed out waiting for agents to become idle/);
260
+ assert.match(restart.stderr, /agent=anima status=running item=item_running/);
261
+ assert.match(restart.stderr, /agent=anima status=queued item=item_queued/);
262
+ assert.match(restart.stderr, /Use --force to restart anyway/);
263
+ assert.doesNotMatch(restart.stdout, /stopped pid/);
264
+ assert.doesNotMatch(restart.stdout, /started pid/);
265
+ for (const pid of childPids)
266
+ assert.equal(pidIsRunning(pid), true);
267
+ }
268
+ finally {
269
+ for (const pid of childPids) {
270
+ try {
271
+ process.kill(pid, 'SIGTERM');
272
+ }
273
+ catch { }
274
+ }
275
+ await rm(tempDir, { force: true, recursive: true });
276
+ }
277
+ });
278
+ test('services restart requires drain-active and resume-running together', async () => {
279
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-restart-drain-flags-'));
280
+ try {
281
+ const configDir = join(tempDir, '.anima');
282
+ await writeMinimalConfig(configDir, {});
283
+ const restart = await runAnimactl(['services', 'restart', '--drain-active'], {
284
+ env: { ANIMA_HOME: configDir },
285
+ });
286
+ assert.equal(restart.status, 1);
287
+ assert.match(restart.stderr, /--drain-active and --resume-running must be used together/);
288
+ assert.doesNotMatch(restart.stdout, /stopped pid/);
289
+ assert.doesNotMatch(restart.stdout, /started pid/);
290
+ }
291
+ finally {
292
+ await rm(tempDir, { force: true, recursive: true });
293
+ }
294
+ });
295
+ test('services restart drain mode leaves queued inbox items for the new worker', async () => {
296
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-restart-drain-queued-'));
297
+ const childPids = new Set();
298
+ try {
299
+ const configDir = join(tempDir, '.anima');
300
+ const resultPath = join(configDir, 'run', 'services-restart-result.json');
301
+ await writeTokenlessAgentConfig(configDir, tempDir);
302
+ const start = await runAnimactl(['services', 'start', '--only', 'agent'], {
303
+ env: { ANIMA_HOME: configDir },
304
+ });
305
+ collectStartedPids(start.stdout, childPids);
306
+ assert.equal(start.status, 0, start.stderr || start.stdout);
307
+ const [oldPid] = childPids;
308
+ assert.ok(oldPid);
309
+ await writeInbox(configDir, 'anima', [
310
+ slackInboxItem('item_queued', 'queued', 'Queued message should remain queued.'),
311
+ ]);
312
+ const restart = await runAnimactl([
313
+ 'services',
314
+ 'restart',
315
+ '--only',
316
+ 'agent',
317
+ '--drain-active',
318
+ '--resume-running',
319
+ ], {
320
+ env: { ANIMA_HOME: configDir, ANIMA_RESTART_RESULT_FILE: resultPath },
321
+ });
322
+ collectStartedPids(restart.stdout, childPids);
323
+ assert.equal(restart.status, 0, restart.stderr || restart.stdout);
324
+ assert.match(restart.stdout, new RegExp(`agent: stopped pid ${oldPid}`));
325
+ assert.match(restart.stdout, /agent: started pid/);
326
+ const inbox = JSON.parse(await readFile(join(configDir, 'agents', 'anima', 'inbox.json'), 'utf8'));
327
+ assert.equal(inbox['item_queued']?.handling?.status, 'queued');
328
+ }
329
+ finally {
330
+ for (const pid of childPids) {
331
+ try {
332
+ process.kill(pid, 'SIGTERM');
333
+ }
334
+ catch { }
335
+ }
336
+ await rm(tempDir, { force: true, recursive: true });
337
+ }
338
+ });
339
+ test('services restart drain mode times out a running item without stopping services', async () => {
340
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-restart-drain-timeout-'));
341
+ const childPids = new Set();
342
+ try {
343
+ const configDir = join(tempDir, '.anima');
344
+ const resultPath = join(configDir, 'run', 'services-restart-result.json');
345
+ await writeTokenlessAgentConfig(configDir, tempDir);
346
+ const start = await runAnimactl(['services', 'start', '--only', 'agent'], {
347
+ env: { ANIMA_HOME: configDir },
348
+ });
349
+ collectStartedPids(start.stdout, childPids);
350
+ assert.equal(start.status, 0, start.stderr || start.stdout);
351
+ const [oldPid] = childPids;
352
+ assert.ok(oldPid);
353
+ await writeInbox(configDir, 'anima', [
354
+ slackInboxItem('item_running', 'running', 'Long-running tool should block drain.'),
355
+ ]);
356
+ const restart = await runAnimactl([
357
+ 'services',
358
+ 'restart',
359
+ '--only',
360
+ 'agent',
361
+ '--drain-active',
362
+ '--resume-running',
363
+ '--drain-timeout-ms',
364
+ '0',
365
+ ], {
366
+ env: { ANIMA_HOME: configDir, ANIMA_RESTART_RESULT_FILE: resultPath },
367
+ });
368
+ assert.equal(restart.status, 1);
369
+ assert.match(restart.stderr, /Timed out waiting for running agents to reach a restart drain point/);
370
+ assert.match(restart.stderr, /agent=anima status=running item=item_running/);
371
+ assert.doesNotMatch(restart.stdout, /stopped pid/);
372
+ assert.doesNotMatch(restart.stdout, /started pid/);
373
+ assert.equal(pidIsRunning(oldPid), true);
374
+ const inbox = JSON.parse(await readFile(join(configDir, 'agents', 'anima', 'inbox.json'), 'utf8'));
375
+ assert.equal(inbox['item_running']?.handling?.status, 'running');
376
+ assert.equal(inbox['item_running']?.handling?.drainRequestedAt, undefined);
377
+ assert.equal(inbox['item_running']?.handling?.drainTimeoutMs, undefined);
378
+ const result = JSON.parse(await readFile(resultPath, 'utf8'));
379
+ assert.equal(result.status, 'blocked');
380
+ assert.equal(result.reason, 'drain_timeout');
381
+ assert.deepEqual(result.blockers?.map((blocker) => ({
382
+ agentId: blocker.agentId,
383
+ itemId: blocker.itemId,
384
+ status: blocker.status,
385
+ })), [
386
+ { agentId: 'anima', itemId: 'item_running', status: 'running' },
387
+ ]);
388
+ }
389
+ finally {
390
+ for (const pid of childPids) {
391
+ try {
392
+ process.kill(pid, 'SIGTERM');
393
+ }
394
+ catch { }
395
+ }
396
+ await rm(tempDir, { force: true, recursive: true });
397
+ }
398
+ });
399
+ test('services restart --force bypasses the inbox idle gate', async () => {
400
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-restart-force-'));
401
+ const childPids = new Set();
402
+ try {
403
+ const configDir = join(tempDir, '.anima');
404
+ await writeTokenlessAgentConfig(configDir, tempDir);
405
+ const start = await runAnimactl(['services', 'start', '--only', 'agent'], {
406
+ env: { ANIMA_HOME: configDir },
407
+ });
408
+ collectStartedPids(start.stdout, childPids);
409
+ assert.equal(start.status, 0, start.stderr || start.stdout);
410
+ const [oldPid] = childPids;
411
+ assert.ok(oldPid);
412
+ await writeInbox(configDir, 'anima', [
413
+ slackInboxItem('item_running', 'running', 'Force restart intentionally ignores this item.'),
414
+ ]);
415
+ const restart = await runAnimactl(['services', 'restart', '--only', 'agent', '--force'], {
416
+ env: { ANIMA_HOME: configDir },
417
+ });
418
+ collectStartedPids(restart.stdout, childPids);
419
+ assert.equal(restart.status, 0, restart.stderr || restart.stdout);
420
+ assert.match(restart.stdout, new RegExp(`agent: stopped pid ${oldPid}`));
421
+ assert.match(restart.stdout, /agent: started pid/);
422
+ }
423
+ finally {
424
+ for (const pid of childPids) {
425
+ try {
426
+ process.kill(pid, 'SIGTERM');
427
+ }
428
+ catch { }
429
+ }
430
+ await rm(tempDir, { force: true, recursive: true });
431
+ }
432
+ });
433
+ test('services start rotates oversized logs and keeps five generations', async () => {
434
+ const tempDir = await mkdtemp(join(tmpdir(), 'anima-services-log-rotate-'));
435
+ const childPids = new Set();
436
+ try {
437
+ const configDir = join(tempDir, '.anima');
438
+ await writeMinimalConfig(configDir, {});
439
+ const logsDir = join(configDir, 'logs');
440
+ await mkdir(logsDir, { recursive: true });
441
+ await writeFile(join(logsDir, 'web.log'), 'x'.repeat(20 * 1024 * 1024), 'utf8');
442
+ for (let i = 1; i <= 5; i += 1) {
443
+ await writeFile(join(logsDir, `web.log.${i}`), `old-${i}`, 'utf8');
444
+ }
445
+ const start = await runAnimactl(['services', 'start', '--only', 'web'], {
446
+ env: { ANIMA_HOME: configDir },
447
+ });
448
+ for (const match of start.stdout.matchAll(/started pid (\d+)/g)) {
449
+ const pidText = match[1];
450
+ if (pidText)
451
+ childPids.add(Number.parseInt(pidText, 10));
452
+ }
453
+ assert.equal(start.status, 0, start.stderr || start.stdout);
454
+ assert.match(await readFile(join(logsDir, 'web.log'), 'utf8'), /starting web/);
455
+ assert.equal((await stat(join(logsDir, 'web.log.1'))).size, 20 * 1024 * 1024);
456
+ assert.equal(await readFile(join(logsDir, 'web.log.2'), 'utf8'), 'old-1');
457
+ assert.equal(await readFile(join(logsDir, 'web.log.5'), 'utf8'), 'old-4');
458
+ await assert.rejects(stat(join(logsDir, 'web.log.6')), /ENOENT/);
459
+ }
460
+ finally {
461
+ for (const pid of childPids) {
462
+ try {
463
+ process.kill(pid, 'SIGTERM');
464
+ }
465
+ catch { }
466
+ }
467
+ await rm(tempDir, { force: true, recursive: true });
468
+ }
469
+ });
470
+ async function writeMinimalConfig(configDir, extras) {
471
+ const body = {
472
+ agents: [
473
+ {
474
+ id: 'anima',
475
+ slack: {
476
+ appToken: 'xapp-fake',
477
+ botToken: 'xoxb-fake',
478
+ },
479
+ provider: { kind: 'claude-code' },
480
+ },
481
+ ],
482
+ };
483
+ if (extras.dashboardPort !== undefined)
484
+ body['dashboardPort'] = extras.dashboardPort;
485
+ await writeConfig(configDir, body);
486
+ }
487
+ async function writeTokenlessAgentConfig(configDir, tempDir) {
488
+ const homePath = join(tempDir, 'home');
489
+ await mkdir(homePath, { recursive: true });
490
+ await writeConfig(configDir, {
491
+ agents: [
492
+ {
493
+ id: 'anima',
494
+ homePath,
495
+ provider: { kind: 'claude-code' },
496
+ },
497
+ ],
498
+ });
499
+ }
500
+ async function writeConfig(configDir, body) {
501
+ await mkdir(configDir, { recursive: true });
502
+ const { agents, ...env } = body;
503
+ await writeFile(join(configDir, 'config.json'), `${JSON.stringify(env, null, 2)}\n`, 'utf8');
504
+ if (Array.isArray(agents)) {
505
+ for (const agent of agents) {
506
+ const id = agent.id;
507
+ const agentDir = join(configDir, 'agents', id);
508
+ await mkdir(agentDir, { recursive: true });
509
+ await writeFile(join(agentDir, 'config.json'), `${JSON.stringify(agent, null, 2)}\n`, 'utf8');
510
+ }
511
+ }
512
+ }
513
+ async function writeInbox(configDir, agentId, items) {
514
+ const inbox = Object.fromEntries(items.map((item) => [String(item['id']), item]));
515
+ await writeFile(join(configDir, 'agents', agentId, 'inbox.json'), `${JSON.stringify(inbox, null, 2)}\n`, 'utf8');
516
+ }
517
+ function slackInboxItem(id, status, text) {
518
+ const createdAt = '2026-05-26T16:57:20.000Z';
519
+ const handling = {
520
+ createdAt,
521
+ queuedAt: '2026-05-26T16:57:22.000Z',
522
+ status,
523
+ updatedAt: status === 'running' ? '2026-05-26T16:57:23.000Z' : '2026-05-26T16:57:22.000Z',
524
+ };
525
+ if (status === 'running') {
526
+ handling['startedAt'] = '2026-05-26T16:57:23.000Z';
527
+ handling['workerId'] = 'anima:12345';
528
+ }
529
+ return {
530
+ channelId: 'C1',
531
+ handling,
532
+ id,
533
+ kind: 'slack',
534
+ messageTs: '1779814640.760089',
535
+ receivedAt: createdAt,
536
+ teamId: 'T1',
537
+ text,
538
+ };
539
+ }
540
+ function collectStartedPids(stdout, target) {
541
+ for (const match of stdout.matchAll(/started pid (\d+)/g)) {
542
+ const pidText = match[1];
543
+ if (pidText)
544
+ target.add(Number.parseInt(pidText, 10));
545
+ }
546
+ }
547
+ function pidIsRunning(pid) {
548
+ try {
549
+ process.kill(pid, 0);
550
+ return true;
551
+ }
552
+ catch {
553
+ return false;
554
+ }
555
+ }
556
+ async function runAnimactl(args, options = {}) {
557
+ const env = {
558
+ ...process.env,
559
+ // Clear any inherited runtime env so refuse checks see a clean slate
560
+ // unless the test explicitly opts back in via options.env.
561
+ ANIMA_INBOX_ITEM_ID: '',
562
+ ANIMA_HOME: '',
563
+ ANIMA_RUNTIME_HOME: '',
564
+ ...(options.env ?? {}),
565
+ };
566
+ const child = spawn(process.execPath, [animactl, ...args], {
567
+ env,
568
+ stdio: ['ignore', 'pipe', 'pipe'],
569
+ });
570
+ let stdout = '';
571
+ let stderr = '';
572
+ child.stdout.setEncoding('utf8');
573
+ child.stderr.setEncoding('utf8');
574
+ child.stdout.on('data', (chunk) => {
575
+ stdout += chunk;
576
+ });
577
+ child.stderr.on('data', (chunk) => {
578
+ stderr += chunk;
579
+ });
580
+ const [status] = (await once(child, 'exit'));
581
+ return { status, stderr, stdout };
582
+ }
583
+ async function runAnimactlUntil(args, options) {
584
+ const env = {
585
+ ...process.env,
586
+ ANIMA_INBOX_ITEM_ID: '',
587
+ ANIMA_HOME: '',
588
+ ANIMA_RUNTIME_HOME: '',
589
+ ...(options.env ?? {}),
590
+ };
591
+ const child = spawn(process.execPath, [animactl, ...args], {
592
+ env,
593
+ stdio: ['ignore', 'pipe', 'pipe'],
594
+ });
595
+ let stdout = '';
596
+ let stderr = '';
597
+ child.stdout.setEncoding('utf8');
598
+ child.stderr.setEncoding('utf8');
599
+ await new Promise((resolveDone, reject) => {
600
+ const finish = () => {
601
+ if (options.until({ stderr, stdout })) {
602
+ cleanup();
603
+ resolveDone();
604
+ }
605
+ };
606
+ const onStdout = (chunk) => {
607
+ stdout += chunk;
608
+ finish();
609
+ };
610
+ const onStderr = (chunk) => {
611
+ stderr += chunk;
612
+ finish();
613
+ };
614
+ const onExit = () => {
615
+ cleanup();
616
+ reject(new Error(`animactl exited before expected output. stdout=${stdout} stderr=${stderr}`));
617
+ };
618
+ const timeout = setTimeout(() => {
619
+ cleanup();
620
+ reject(new Error(`timed out waiting for animactl output. stdout=${stdout} stderr=${stderr}`));
621
+ }, options.timeoutMs ?? 2_000);
622
+ const cleanup = () => {
623
+ clearTimeout(timeout);
624
+ child.stdout.off('data', onStdout);
625
+ child.stderr.off('data', onStderr);
626
+ child.off('exit', onExit);
627
+ };
628
+ child.stdout.on('data', onStdout);
629
+ child.stderr.on('data', onStderr);
630
+ child.once('exit', onExit);
631
+ });
632
+ const exit = once(child, 'exit');
633
+ child.kill('SIGTERM');
634
+ const [status] = await exit;
635
+ return { status, stderr, stdout };
636
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=slack-files.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-files.test.d.ts","sourceRoot":"","sources":["../../../server/tests/slack-files.test.ts"],"names":[],"mappings":""}