@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.
- package/README.md +11 -6
- package/dist/server/{runtime/activity-payload.d.ts → activities/format.d.ts} +4 -1
- package/dist/server/activities/format.d.ts.map +1 -0
- package/dist/server/{runtime/activity-payload.js → activities/format.js} +16 -1
- package/dist/server/agents/agent-skills.d.ts +7 -3
- package/dist/server/agents/agent-skills.d.ts.map +1 -1
- package/dist/server/agents/agent-skills.js +243 -38
- package/dist/server/agents/agent-slack.service.d.ts +15 -0
- package/dist/server/agents/agent-slack.service.d.ts.map +1 -1
- package/dist/server/agents/agent-slack.service.js +43 -6
- package/dist/server/agents/default-skills.d.ts +9 -0
- package/dist/server/agents/default-skills.d.ts.map +1 -0
- package/dist/server/agents/default-skills.js +52 -0
- package/dist/server/cli/runtime-cli.d.ts +7 -1
- package/dist/server/cli/runtime-cli.d.ts.map +1 -1
- package/dist/server/cli/runtime-cli.js +107 -3
- package/dist/server/cli/services-cli.d.ts.map +1 -1
- package/dist/server/cli/services-cli.js +39 -0
- package/dist/server/inbox/slack-subscriber.d.ts +2 -0
- package/dist/server/inbox/slack-subscriber.d.ts.map +1 -1
- package/dist/server/inbox/slack-subscriber.js +28 -2
- package/dist/server/inbox/slack-subscription.service.d.ts.map +1 -1
- package/dist/server/inbox/slack-subscription.service.js +13 -1
- package/dist/server/inbox/wake-queue.service.d.ts +3 -1
- package/dist/server/inbox/wake-queue.service.d.ts.map +1 -1
- package/dist/server/inbox/wake-queue.service.js +5 -3
- package/dist/server/provider-usage/http.d.ts +1 -0
- package/dist/server/provider-usage/http.d.ts.map +1 -1
- package/dist/server/provider-usage/http.js +23 -1
- package/dist/server/provider-usage/provider-usage.service.d.ts +1 -4
- package/dist/server/provider-usage/provider-usage.service.d.ts.map +1 -1
- package/dist/server/provider-usage/provider-usage.service.js +1 -18
- package/dist/server/provider-usage/providers/claude.d.ts +1 -1
- package/dist/server/provider-usage/providers/claude.d.ts.map +1 -1
- package/dist/server/provider-usage/providers/claude.js +1 -1
- package/dist/server/provider-usage/providers/codex.d.ts +1 -1
- package/dist/server/provider-usage/providers/codex.d.ts.map +1 -1
- package/dist/server/provider-usage/providers/codex.js +1 -1
- package/dist/server/provider-usage/providers/kimi.d.ts +1 -1
- package/dist/server/provider-usage/providers/kimi.d.ts.map +1 -1
- package/dist/server/provider-usage/providers/kimi.js +4 -4
- package/dist/server/provider-usage/result.d.ts +5 -0
- package/dist/server/provider-usage/result.d.ts.map +1 -0
- package/dist/server/provider-usage/result.js +18 -0
- package/dist/server/providers/active-runtime.d.ts +1 -1
- package/dist/server/providers/active-runtime.d.ts.map +1 -1
- package/dist/server/providers/claude-events.d.ts +1 -1
- package/dist/server/providers/claude-events.d.ts.map +1 -1
- package/dist/server/providers/claude-events.js +2 -2
- package/dist/server/providers/claude.d.ts +1 -1
- package/dist/server/providers/claude.d.ts.map +1 -1
- package/dist/server/providers/claude.js +2 -2
- package/dist/server/providers/codex-app-server.d.ts +1 -1
- package/dist/server/providers/codex-app-server.d.ts.map +1 -1
- package/dist/server/providers/codex-app-server.js +1 -1
- package/dist/server/providers/codex-events.js +1 -1
- package/dist/server/providers/codex.d.ts +1 -1
- package/dist/server/providers/codex.d.ts.map +1 -1
- package/dist/server/providers/codex.js +2 -2
- package/dist/server/{runtime/provider-contract.d.ts → providers/contract.d.ts} +1 -1
- package/dist/server/providers/contract.d.ts.map +1 -0
- package/dist/server/providers/factory.d.ts +1 -1
- package/dist/server/providers/factory.d.ts.map +1 -1
- package/dist/server/providers/kimi-events.d.ts +1 -1
- package/dist/server/providers/kimi-events.d.ts.map +1 -1
- package/dist/server/providers/kimi-events.js +1 -1
- package/dist/server/providers/kimi.d.ts +1 -1
- package/dist/server/providers/kimi.d.ts.map +1 -1
- package/dist/server/providers/kimi.js +3 -2
- package/dist/server/providers/reasoning-events.js +1 -1
- package/dist/server/runtime/active-run-control.d.ts +19 -0
- package/dist/server/runtime/active-run-control.d.ts.map +1 -0
- package/dist/server/runtime/active-run-control.js +89 -0
- package/dist/server/runtime/activity.js +1 -1
- package/dist/server/runtime/agent-runner.d.ts +1 -1
- package/dist/server/runtime/agent-runner.d.ts.map +1 -1
- package/dist/server/runtime/delivery-prompt.d.ts +6 -0
- package/dist/server/runtime/delivery-prompt.d.ts.map +1 -1
- package/dist/server/runtime/delivery-prompt.js +53 -34
- package/dist/server/runtime/followup-appender.d.ts +20 -0
- package/dist/server/runtime/followup-appender.d.ts.map +1 -0
- package/dist/server/runtime/followup-appender.js +128 -0
- package/dist/server/runtime/host.d.ts +2 -0
- package/dist/server/runtime/host.d.ts.map +1 -1
- package/dist/server/runtime/host.js +8 -0
- package/dist/server/runtime/provider-runner.d.ts +16 -0
- package/dist/server/runtime/provider-runner.d.ts.map +1 -0
- package/dist/server/runtime/provider-runner.js +86 -0
- package/dist/server/runtime/runtime-bridge.d.ts +1 -1
- package/dist/server/runtime/runtime-bridge.d.ts.map +1 -1
- package/dist/server/runtime/runtime-session.service.d.ts +1 -1
- package/dist/server/runtime/runtime-session.service.d.ts.map +1 -1
- package/dist/server/runtime/runtime-session.service.js +1 -1
- package/dist/server/runtime/runtime-worker.d.ts +2 -10
- package/dist/server/runtime/runtime-worker.d.ts.map +1 -1
- package/dist/server/runtime/runtime-worker.js +60 -286
- package/dist/server/runtime-management/managed-runtime.d.ts.map +1 -0
- package/dist/server/runtime-management/runtime-release.d.ts.map +1 -0
- package/dist/server/{runtime → runtime-management}/runtime-upgrade.d.ts +1 -0
- package/dist/server/runtime-management/runtime-upgrade.d.ts.map +1 -0
- package/dist/server/{runtime → runtime-management}/runtime-upgrade.js +14 -0
- package/dist/server/services/system.service.d.ts +41 -0
- package/dist/server/services/system.service.d.ts.map +1 -0
- package/dist/server/services/system.service.js +154 -0
- package/dist/server/settings/settings.service.d.ts +2 -1
- package/dist/server/settings/settings.service.d.ts.map +1 -1
- package/dist/server/settings/settings.service.js +9 -1
- package/dist/server/slack/shortcuts.d.ts +0 -3
- package/dist/server/slack/shortcuts.d.ts.map +1 -1
- package/dist/server/slack/shortcuts.js +0 -3
- package/dist/server/slack-interactions/shortcut-ids.d.ts +4 -0
- package/dist/server/slack-interactions/shortcut-ids.d.ts.map +1 -0
- package/dist/server/slack-interactions/shortcut-ids.js +3 -0
- package/dist/server/slack-interactions/shortcut-views.d.ts +65 -0
- package/dist/server/slack-interactions/shortcut-views.d.ts.map +1 -0
- package/dist/server/slack-interactions/shortcut-views.js +211 -0
- package/dist/server/{slack → slack-interactions}/shortcut.service.d.ts +2 -47
- package/dist/server/slack-interactions/shortcut.service.d.ts.map +1 -0
- package/dist/server/slack-interactions/shortcut.service.js +227 -0
- package/dist/server/storage/jsonl-log.d.ts +9 -0
- package/dist/server/storage/jsonl-log.d.ts.map +1 -1
- package/dist/server/storage/jsonl-log.js +36 -1
- package/dist/server/storage/schema/server.store.d.ts +5 -0
- package/dist/server/storage/schema/server.store.d.ts.map +1 -1
- package/dist/server/storage/schema/server.store.js +2 -1
- package/dist/server/storage/schema/wake-queue.store.d.ts +15 -1
- package/dist/server/storage/schema/wake-queue.store.d.ts.map +1 -1
- package/dist/server/storage/schema/wake-queue.store.js +5 -3
- package/dist/server/tests/agent-config.test.d.ts +2 -0
- package/dist/server/tests/agent-config.test.d.ts.map +1 -0
- package/dist/server/tests/agent-config.test.js +229 -0
- package/dist/server/tests/agent-runtime.test.d.ts +2 -0
- package/dist/server/tests/agent-runtime.test.d.ts.map +1 -0
- package/dist/server/tests/agent-runtime.test.js +1110 -0
- package/dist/server/tests/agent-seed-memory.test.d.ts +2 -0
- package/dist/server/tests/agent-seed-memory.test.d.ts.map +1 -0
- package/dist/server/tests/agent-seed-memory.test.js +16 -0
- package/dist/server/tests/agent-skills.test.d.ts +2 -0
- package/dist/server/tests/agent-skills.test.d.ts.map +1 -0
- package/dist/server/tests/agent-skills.test.js +140 -0
- package/dist/server/tests/anima-home.d.ts +2 -0
- package/dist/server/tests/anima-home.d.ts.map +1 -0
- package/dist/server/tests/anima-home.js +4 -0
- package/dist/server/tests/cli-file.test.d.ts +2 -0
- package/dist/server/tests/cli-file.test.d.ts.map +1 -0
- package/dist/server/tests/cli-file.test.js +596 -0
- package/dist/server/tests/cli-message.test.d.ts +2 -0
- package/dist/server/tests/cli-message.test.d.ts.map +1 -0
- package/dist/server/tests/cli-message.test.js +1196 -0
- package/dist/server/tests/client-error-routes.test.d.ts +2 -0
- package/dist/server/tests/client-error-routes.test.d.ts.map +1 -0
- package/dist/server/tests/client-error-routes.test.js +163 -0
- package/dist/server/tests/config.test.d.ts +2 -0
- package/dist/server/tests/config.test.d.ts.map +1 -0
- package/dist/server/tests/config.test.js +497 -0
- package/dist/server/tests/default-skills.test.d.ts +2 -0
- package/dist/server/tests/default-skills.test.d.ts.map +1 -0
- package/dist/server/tests/default-skills.test.js +57 -0
- package/dist/server/tests/helpers/inbox.d.ts +10 -0
- package/dist/server/tests/helpers/inbox.d.ts.map +1 -0
- package/dist/server/tests/helpers/inbox.js +21 -0
- package/dist/server/tests/helpers/slack.d.ts +11 -0
- package/dist/server/tests/helpers/slack.d.ts.map +1 -0
- package/dist/server/tests/helpers/slack.js +17 -0
- package/dist/server/tests/helpers/state.d.ts +19 -0
- package/dist/server/tests/helpers/state.d.ts.map +1 -0
- package/dist/server/tests/helpers/state.js +69 -0
- package/dist/server/tests/inbox-slack-events.test.d.ts +2 -0
- package/dist/server/tests/inbox-slack-events.test.d.ts.map +1 -0
- package/dist/server/tests/inbox-slack-events.test.js +271 -0
- package/dist/server/tests/inbox.test.d.ts +2 -0
- package/dist/server/tests/inbox.test.d.ts.map +1 -0
- package/dist/server/tests/inbox.test.js +144 -0
- package/dist/server/tests/interactive-ask.test.d.ts +2 -0
- package/dist/server/tests/interactive-ask.test.d.ts.map +1 -0
- package/dist/server/tests/interactive-ask.test.js +387 -0
- package/dist/server/tests/kb.test.d.ts +2 -0
- package/dist/server/tests/kb.test.d.ts.map +1 -0
- package/dist/server/tests/kb.test.js +371 -0
- package/dist/server/tests/message.service.test.d.ts +2 -0
- package/dist/server/tests/message.service.test.d.ts.map +1 -0
- package/dist/server/tests/message.service.test.js +151 -0
- package/dist/server/tests/messages.test.d.ts +2 -0
- package/dist/server/tests/messages.test.d.ts.map +1 -0
- package/dist/server/tests/messages.test.js +12 -0
- package/dist/server/tests/prompt-attachments.test.d.ts +2 -0
- package/dist/server/tests/prompt-attachments.test.d.ts.map +1 -0
- package/dist/server/tests/prompt-attachments.test.js +274 -0
- package/dist/server/tests/provider-usage.test.d.ts +2 -0
- package/dist/server/tests/provider-usage.test.d.ts.map +1 -0
- package/dist/server/tests/provider-usage.test.js +97 -0
- package/dist/server/tests/reminders.test.d.ts +2 -0
- package/dist/server/tests/reminders.test.d.ts.map +1 -0
- package/dist/server/tests/reminders.test.js +383 -0
- package/dist/server/tests/runtime-cli.test.d.ts +2 -0
- package/dist/server/tests/runtime-cli.test.d.ts.map +1 -0
- package/dist/server/tests/runtime-cli.test.js +21 -0
- package/dist/server/tests/runtime-upgrade.test.d.ts +2 -0
- package/dist/server/tests/runtime-upgrade.test.d.ts.map +1 -0
- package/dist/server/tests/runtime-upgrade.test.js +307 -0
- package/dist/server/tests/runtime-worker.test.d.ts +2 -0
- package/dist/server/tests/runtime-worker.test.d.ts.map +1 -0
- package/dist/server/tests/runtime-worker.test.js +1021 -0
- package/dist/server/tests/runtime.test.d.ts +2 -0
- package/dist/server/tests/runtime.test.d.ts.map +1 -0
- package/dist/server/tests/runtime.test.js +354 -0
- package/dist/server/tests/services.test.d.ts +2 -0
- package/dist/server/tests/services.test.d.ts.map +1 -0
- package/dist/server/tests/services.test.js +636 -0
- package/dist/server/tests/slack-files.test.d.ts +2 -0
- package/dist/server/tests/slack-files.test.d.ts.map +1 -0
- package/dist/server/tests/slack-files.test.js +118 -0
- package/dist/server/tests/slack-shortcuts.test.d.ts +2 -0
- package/dist/server/tests/slack-shortcuts.test.d.ts.map +1 -0
- package/dist/server/tests/slack-shortcuts.test.js +310 -0
- package/dist/server/tests/slack.test.d.ts +2 -0
- package/dist/server/tests/slack.test.d.ts.map +1 -0
- package/dist/server/tests/slack.test.js +213 -0
- package/dist/server/tests/state-cache.test.d.ts +2 -0
- package/dist/server/tests/state-cache.test.d.ts.map +1 -0
- package/dist/server/tests/state-cache.test.js +184 -0
- package/dist/server/tests/subscriptions.test.d.ts +2 -0
- package/dist/server/tests/subscriptions.test.d.ts.map +1 -0
- package/dist/server/tests/subscriptions.test.js +292 -0
- package/dist/server/tests/system-service.test.d.ts +2 -0
- package/dist/server/tests/system-service.test.d.ts.map +1 -0
- package/dist/server/tests/system-service.test.js +20 -0
- package/dist/server/tests/url-routes.test.d.ts +2 -0
- package/dist/server/tests/url-routes.test.d.ts.map +1 -0
- package/dist/server/tests/url-routes.test.js +217 -0
- package/dist/server/tests/web-api.test.d.ts +2 -0
- package/dist/server/tests/web-api.test.d.ts.map +1 -0
- package/dist/server/tests/web-api.test.js +1759 -0
- package/dist/server/web/app.d.ts.map +1 -1
- package/dist/server/web/app.js +2 -0
- package/dist/server/web/client-error-routes.d.ts +3 -0
- package/dist/server/web/client-error-routes.d.ts.map +1 -0
- package/dist/server/web/client-error-routes.js +101 -0
- package/dist/server/web/system-routes.d.ts.map +1 -1
- package/dist/server/web/system-routes.js +21 -131
- package/dist/shared/agent-config.d.ts +8 -18
- package/dist/shared/agent-config.d.ts.map +1 -1
- package/dist/shared/agent-config.js +3 -4
- package/dist/shared/inbox.d.ts +27 -0
- package/dist/shared/inbox.d.ts.map +1 -1
- package/dist/shared/inbox.js +1 -0
- package/dist/shared/server-info.d.ts +6 -5
- package/dist/shared/server-info.d.ts.map +1 -1
- package/dist/shared/server-info.js +3 -1
- package/dist/shared/server-settings.d.ts +6 -0
- package/dist/shared/server-settings.d.ts.map +1 -1
- package/dist/shared/server-settings.js +1 -0
- package/dist/shared/skills.d.ts +24 -3
- package/dist/shared/skills.d.ts.map +1 -1
- package/dist/web/assets/activity-DHjip8tY.js +7 -0
- package/dist/web/assets/clipboard-D2NSC52v.js +1 -0
- package/dist/web/assets/{external-link-Dt0qTpmV.js → external-link-Bhr1WdRS.js} +1 -1
- package/dist/web/assets/{file-text-Dd9ys6py.js → file-text-CYMA_SMA.js} +1 -1
- package/dist/web/assets/{index-CzAQnbuO.js → index-CVpew409.js} +5 -5
- package/dist/web/assets/index-mh0uJTkY.css +2 -0
- package/dist/web/assets/kb-DDVpYkT9.js +35 -0
- package/dist/web/assets/profile-CBy6GNzp.js +1 -0
- package/dist/web/assets/reminders-Bbwb9QNT.js +1 -0
- package/dist/web/favicon.svg +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/templates/runtime-standing-prompt.md +16 -2
- package/templates/skills/find-skills/SKILL.md +133 -0
- package/dist/server/runtime/activity-payload.d.ts.map +0 -1
- package/dist/server/runtime/activity-text.d.ts +0 -4
- package/dist/server/runtime/activity-text.d.ts.map +0 -1
- package/dist/server/runtime/activity-text.js +0 -16
- package/dist/server/runtime/managed-runtime.d.ts.map +0 -1
- package/dist/server/runtime/provider-contract.d.ts.map +0 -1
- package/dist/server/runtime/runtime-release.d.ts.map +0 -1
- package/dist/server/runtime/runtime-upgrade.d.ts.map +0 -1
- package/dist/server/slack/shortcut.service.d.ts.map +0 -1
- package/dist/server/slack/shortcut.service.js +0 -493
- package/dist/web/assets/activity-BP0C81IV.js +0 -7
- package/dist/web/assets/globe-DmDZSgDP.js +0 -1
- package/dist/web/assets/index-DKecrvN0.css +0 -2
- package/dist/web/assets/kb-3wFeP2Jt.js +0 -32
- package/dist/web/assets/profile-D7wPZXW_.js +0 -1
- package/dist/web/assets/reminders-DOhroVwo.js +0 -1
- /package/dist/server/{runtime/provider-contract.js → providers/contract.js} +0 -0
- /package/dist/server/{runtime → runtime-management}/managed-runtime.d.ts +0 -0
- /package/dist/server/{runtime → runtime-management}/managed-runtime.js +0 -0
- /package/dist/server/{runtime → runtime-management}/runtime-release.d.ts +0 -0
- /package/dist/server/{runtime → runtime-management}/runtime-release.js +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { JsonFile } from '../storage/json-file.js';
|
|
7
|
+
import { JsonlAppendLog } from '../storage/jsonl-log.js';
|
|
8
|
+
test('JsonFile cache invalidates when another writer changes the file on disk', async () => {
|
|
9
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonfile-cache-'));
|
|
10
|
+
try {
|
|
11
|
+
const path = join(dir, 'value.json');
|
|
12
|
+
const reader = new JsonFile(path, () => ({ count: 0 }));
|
|
13
|
+
const writer = new JsonFile(path, () => ({ count: 0 }));
|
|
14
|
+
await writer.write({ count: 1 });
|
|
15
|
+
assert.deepEqual(await reader.read(), { count: 1 });
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
17
|
+
await writeFile(path, `${JSON.stringify({ count: 2 })}\n`, 'utf8');
|
|
18
|
+
assert.deepEqual(await reader.read(), { count: 2 }, 'reader should see the external write via stat invalidation');
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await rm(dir, { force: true, recursive: true });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
test('JsonlAppendLog cache invalidates when another writer appends to the file on disk', async () => {
|
|
25
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-cache-'));
|
|
26
|
+
try {
|
|
27
|
+
const path = join(dir, 'log.jsonl');
|
|
28
|
+
const log = new JsonlAppendLog(path);
|
|
29
|
+
await log.append({ id: 'a' });
|
|
30
|
+
assert.deepEqual(await log.readAll(), [{ id: 'a' }]);
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
32
|
+
await writeFile(path, `${JSON.stringify({ id: 'a' })}\n${JSON.stringify({ id: 'b' })}\n`, 'utf8');
|
|
33
|
+
assert.deepEqual(await log.readAll(), [{ id: 'a' }, { id: 'b' }], 'readAll should see the external append');
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
await rm(dir, { force: true, recursive: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test('JsonlAppendLog rotates active files and reads archives chronologically', async () => {
|
|
40
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-rotate-'));
|
|
41
|
+
const realNow = Date.now;
|
|
42
|
+
try {
|
|
43
|
+
Date.now = () => 1;
|
|
44
|
+
const path = join(dir, 'activity.jsonl');
|
|
45
|
+
const archiveDir = join(dir, 'activity.archive');
|
|
46
|
+
const log = new JsonlAppendLog(path, { archiveDir, maxBytes: 1 });
|
|
47
|
+
await log.append({ id: 'a' });
|
|
48
|
+
await log.append({ id: 'b' });
|
|
49
|
+
await log.append({ id: 'c' });
|
|
50
|
+
const archives = await readdir(archiveDir);
|
|
51
|
+
assert.equal(archives.length, 2);
|
|
52
|
+
assert.deepEqual(await log.readAll(), [{ id: 'a' }, { id: 'b' }, { id: 'c' }]);
|
|
53
|
+
assert.deepEqual(await log.readTail(1), [{ id: 'c' }]);
|
|
54
|
+
assert.deepEqual(await log.readTail(2), [{ id: 'b' }, { id: 'c' }]);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
Date.now = realNow;
|
|
58
|
+
await rm(dir, { force: true, recursive: true });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
test('JsonlAppendLog keeps every archive by default (no maxArchives)', async () => {
|
|
62
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-no-prune-'));
|
|
63
|
+
const realNow = Date.now;
|
|
64
|
+
try {
|
|
65
|
+
// Monotonic clock: each 10MB rotation gets a strictly greater stamp in
|
|
66
|
+
// production, so archive names sort chronologically. Mirror that here.
|
|
67
|
+
let now = 1;
|
|
68
|
+
Date.now = () => now++;
|
|
69
|
+
const path = join(dir, 'activity.jsonl');
|
|
70
|
+
const archiveDir = join(dir, 'activity.archive');
|
|
71
|
+
const log = new JsonlAppendLog(path, { archiveDir, maxBytes: 1 });
|
|
72
|
+
// 5 appends ⇒ 4 rotations ⇒ 4 archive segments, none pruned.
|
|
73
|
+
for (const id of ['a', 'b', 'c', 'd', 'e'])
|
|
74
|
+
await log.append({ id });
|
|
75
|
+
assert.equal((await readdir(archiveDir)).length, 4);
|
|
76
|
+
assert.deepEqual((await log.readAll()).map((r) => r.id), ['a', 'b', 'c', 'd', 'e']);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
Date.now = realNow;
|
|
80
|
+
await rm(dir, { force: true, recursive: true });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
test('JsonlAppendLog maxArchives prunes oldest segments past the cap', async () => {
|
|
84
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-prune-'));
|
|
85
|
+
const realNow = Date.now;
|
|
86
|
+
try {
|
|
87
|
+
let now = 1;
|
|
88
|
+
Date.now = () => now++;
|
|
89
|
+
const path = join(dir, 'activity.jsonl');
|
|
90
|
+
const archiveDir = join(dir, 'activity.archive');
|
|
91
|
+
const log = new JsonlAppendLog(path, { archiveDir, maxBytes: 1, maxArchives: 2 });
|
|
92
|
+
// 5 appends ⇒ 4 rotations, but only the newest 2 archives survive.
|
|
93
|
+
for (const id of ['a', 'b', 'c', 'd', 'e'])
|
|
94
|
+
await log.append({ id });
|
|
95
|
+
assert.equal((await readdir(archiveDir)).length, 2, 'only maxArchives segments retained');
|
|
96
|
+
// a, b pruned; c, d archived; e active.
|
|
97
|
+
assert.deepEqual((await log.readAll()).map((r) => r.id), ['c', 'd', 'e']);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
Date.now = realNow;
|
|
101
|
+
await rm(dir, { force: true, recursive: true });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
test('JsonlAppendLog maxArchives prunes only its own segments in a shared archive dir', async () => {
|
|
105
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-prune-scoped-'));
|
|
106
|
+
const realNow = Date.now;
|
|
107
|
+
try {
|
|
108
|
+
let now = 100;
|
|
109
|
+
Date.now = () => now++;
|
|
110
|
+
const archiveDir = join(dir, 'shared.archive');
|
|
111
|
+
// A foreign segment from another log living in the same archive dir.
|
|
112
|
+
await mkdir(archiveDir, { recursive: true });
|
|
113
|
+
const foreign = join(archiveDir, '0000000000001-other-000.jsonl');
|
|
114
|
+
await writeFile(foreign, `${JSON.stringify({ id: 'foreign' })}\n`, 'utf8');
|
|
115
|
+
const path = join(dir, 'activity.jsonl');
|
|
116
|
+
const log = new JsonlAppendLog(path, { archiveDir, maxBytes: 1, maxArchives: 1 });
|
|
117
|
+
for (const id of ['a', 'b', 'c'])
|
|
118
|
+
await log.append({ id });
|
|
119
|
+
const names = (await readdir(archiveDir)).sort();
|
|
120
|
+
// Foreign file untouched; only this log's own newest segment kept.
|
|
121
|
+
assert.ok(names.includes('0000000000001-other-000.jsonl'), 'foreign archive must survive');
|
|
122
|
+
const ownArchives = names.filter((n) => /-activity-\d{3}\.jsonl$/.test(n));
|
|
123
|
+
assert.equal(ownArchives.length, 1, 'only maxArchives of this log kept');
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
Date.now = realNow;
|
|
127
|
+
await rm(dir, { force: true, recursive: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
test('JsonlAppendLog appendIf dedupes across rotated archives', async () => {
|
|
131
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-rotate-dedupe-'));
|
|
132
|
+
try {
|
|
133
|
+
const path = join(dir, 'messages.jsonl');
|
|
134
|
+
const log = new JsonlAppendLog(path, {
|
|
135
|
+
archiveDir: join(dir, 'messages.archive'),
|
|
136
|
+
maxBytes: 1,
|
|
137
|
+
});
|
|
138
|
+
await log.append({ id: 'a' });
|
|
139
|
+
await log.append({ id: 'b' });
|
|
140
|
+
const result = await log.appendIf({ id: 'a' }, (records) => !records.some((record) => record.id === 'a'));
|
|
141
|
+
assert.equal(result.appended, false);
|
|
142
|
+
assert.deepEqual(await log.readAll(), [{ id: 'a' }, { id: 'b' }]);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await rm(dir, { force: true, recursive: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
test('JsonlAppendLog appendIfRecent dedupes only within the recent tail window', async () => {
|
|
149
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonl-recent-dedupe-'));
|
|
150
|
+
try {
|
|
151
|
+
const path = join(dir, 'messages.jsonl');
|
|
152
|
+
const log = new JsonlAppendLog(path, {
|
|
153
|
+
archiveDir: join(dir, 'messages.archive'),
|
|
154
|
+
maxBytes: 1,
|
|
155
|
+
});
|
|
156
|
+
await log.append({ id: 'old' });
|
|
157
|
+
await log.append({ id: 'recent' });
|
|
158
|
+
const oldDuplicate = await log.appendIfRecent({ id: 'old' }, (records) => !records.some((record) => record.id === 'old'), 1);
|
|
159
|
+
const recentDuplicate = await log.appendIfRecent({ id: 'recent' }, (records) => !records.some((record) => record.id === 'recent'), 10);
|
|
160
|
+
assert.equal(oldDuplicate.appended, true);
|
|
161
|
+
assert.equal(recentDuplicate.appended, false);
|
|
162
|
+
assert.deepEqual((await log.readAll()).map((record) => record.id), ['old', 'recent', 'old']);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await rm(dir, { force: true, recursive: true });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
test('JsonFile cache serves the warm value across two readers in the same process', async () => {
|
|
169
|
+
const dir = await mkdtemp(join(tmpdir(), 'anima-jsonfile-warm-'));
|
|
170
|
+
try {
|
|
171
|
+
const path = join(dir, 'cached.json');
|
|
172
|
+
const writer = new JsonFile(path, () => ({ n: 0 }));
|
|
173
|
+
const reader = new JsonFile(path, () => ({ n: 0 }));
|
|
174
|
+
await writer.write({ n: 42 });
|
|
175
|
+
const first = await reader.read();
|
|
176
|
+
const second = await reader.read();
|
|
177
|
+
assert.deepEqual(first, { n: 42 });
|
|
178
|
+
assert.deepEqual(second, { n: 42 });
|
|
179
|
+
assert.strictEqual(first, second, 'cached reads share a reference; callers must not mutate the returned value');
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await rm(dir, { force: true, recursive: true });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriptions.test.d.ts","sourceRoot":"","sources":["../../../server/tests/subscriptions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { attentionMapForSubscriptions, ensureThreadSubscriptionForSentMessage, muteSubscriptionForAgent, recordChannelPost, shouldReply, slackRuntimeDecision, } from '../inbox/slack-subscription.service.js';
|
|
7
|
+
import { withAnimaHome } from './anima-home.js';
|
|
8
|
+
import { loadState } from './helpers/state.js';
|
|
9
|
+
test('Slack routing replies to DMs without mention', () => {
|
|
10
|
+
assert.equal(shouldReply({
|
|
11
|
+
channel: 'D123',
|
|
12
|
+
channel_type: 'im',
|
|
13
|
+
text: 'Can you help?',
|
|
14
|
+
ts: '1770000010.000001',
|
|
15
|
+
type: 'message',
|
|
16
|
+
user: 'U123',
|
|
17
|
+
}), true);
|
|
18
|
+
});
|
|
19
|
+
test('Slack routing replies to explicit channel mentions', () => {
|
|
20
|
+
assert.equal(shouldReply({
|
|
21
|
+
channel: 'C123',
|
|
22
|
+
channel_type: 'channel',
|
|
23
|
+
text: '<@U999> summarize this',
|
|
24
|
+
ts: '1770000010.000001',
|
|
25
|
+
type: 'app_mention',
|
|
26
|
+
user: 'U123',
|
|
27
|
+
}), true);
|
|
28
|
+
});
|
|
29
|
+
test('member channel top-level messages wake unless muted', async () => {
|
|
30
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-channel-follow-test-'));
|
|
31
|
+
try {
|
|
32
|
+
await withAnimaHome(stateDir, async () => {
|
|
33
|
+
const topLevel = await slackRuntimeDecision({
|
|
34
|
+
channel: 'C123',
|
|
35
|
+
channel_type: 'channel',
|
|
36
|
+
text: 'top-level member-channel message',
|
|
37
|
+
ts: '1770000011.000001',
|
|
38
|
+
type: 'message',
|
|
39
|
+
user: 'U123',
|
|
40
|
+
}, { agentId: 'scout', nowMs: 2_000 });
|
|
41
|
+
assert.equal(topLevel.shouldStartRuntime, true);
|
|
42
|
+
assert.equal(topLevel.reason, 'channel_follow');
|
|
43
|
+
assert.equal(topLevel.subscription?.kind, 'channel');
|
|
44
|
+
assert.equal(topLevel.subscription?.status, 'following');
|
|
45
|
+
await muteSubscriptionForAgent({
|
|
46
|
+
agentId: 'scout',
|
|
47
|
+
channelId: 'C123',
|
|
48
|
+
nowMs: 3_000,
|
|
49
|
+
});
|
|
50
|
+
const muted = await slackRuntimeDecision({
|
|
51
|
+
channel: 'C123',
|
|
52
|
+
channel_type: 'channel',
|
|
53
|
+
text: 'muted top-level message',
|
|
54
|
+
ts: '1770000012.000001',
|
|
55
|
+
type: 'message',
|
|
56
|
+
user: 'U123',
|
|
57
|
+
}, { agentId: 'scout', nowMs: 4_000 });
|
|
58
|
+
assert.equal(muted.shouldStartRuntime, false);
|
|
59
|
+
assert.equal(muted.reason, 'muted');
|
|
60
|
+
const mention = await slackRuntimeDecision({
|
|
61
|
+
channel: 'C123',
|
|
62
|
+
channel_type: 'channel',
|
|
63
|
+
text: '<@U999> muted channel pierce',
|
|
64
|
+
ts: '1770000013.000001',
|
|
65
|
+
type: 'app_mention',
|
|
66
|
+
user: 'U123',
|
|
67
|
+
}, { agentId: 'scout', nowMs: 5_000 });
|
|
68
|
+
assert.equal(mention.shouldStartRuntime, true);
|
|
69
|
+
assert.equal(mention.reason, 'mention');
|
|
70
|
+
const stillMuted = await slackRuntimeDecision({
|
|
71
|
+
channel: 'C123',
|
|
72
|
+
channel_type: 'channel',
|
|
73
|
+
text: 'still muted without mention',
|
|
74
|
+
ts: '1770000014.000001',
|
|
75
|
+
type: 'message',
|
|
76
|
+
user: 'U123',
|
|
77
|
+
}, { agentId: 'scout', nowMs: 6_000 });
|
|
78
|
+
assert.equal(stillMuted.shouldStartRuntime, false);
|
|
79
|
+
assert.equal(stillMuted.reason, 'muted');
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
test('thread follows are permanent and mute is revived by mention', async () => {
|
|
87
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-thread-follow-test-'));
|
|
88
|
+
try {
|
|
89
|
+
await withAnimaHome(stateDir, async () => {
|
|
90
|
+
const mention = await slackRuntimeDecision({
|
|
91
|
+
channel: 'C123',
|
|
92
|
+
channel_type: 'channel',
|
|
93
|
+
text: '<@U999> help here',
|
|
94
|
+
thread_ts: '1770000010.000001',
|
|
95
|
+
ts: '1770000011.000001',
|
|
96
|
+
type: 'app_mention',
|
|
97
|
+
user: 'U123',
|
|
98
|
+
}, { agentId: 'scout', nowMs: 1_000 });
|
|
99
|
+
assert.equal(mention.shouldStartRuntime, true);
|
|
100
|
+
assert.equal(mention.reason, 'mention');
|
|
101
|
+
assert.equal(mention.subscription?.kind, 'thread');
|
|
102
|
+
const muchLater = await slackRuntimeDecision({
|
|
103
|
+
channel: 'C123',
|
|
104
|
+
channel_type: 'channel',
|
|
105
|
+
text: 'follow-up weeks later',
|
|
106
|
+
thread_ts: '1770000010.000001',
|
|
107
|
+
ts: '1770000012.000001',
|
|
108
|
+
type: 'message',
|
|
109
|
+
user: 'U123',
|
|
110
|
+
}, { agentId: 'scout', nowMs: 30 * 24 * 60 * 60 * 1000 });
|
|
111
|
+
assert.equal(muchLater.shouldStartRuntime, true);
|
|
112
|
+
assert.equal(muchLater.reason, 'thread_follow');
|
|
113
|
+
await muteSubscriptionForAgent({
|
|
114
|
+
agentId: 'scout',
|
|
115
|
+
channelId: 'C123',
|
|
116
|
+
threadTs: '1770000010.000001',
|
|
117
|
+
nowMs: 30 * 24 * 60 * 60 * 1000 + 1,
|
|
118
|
+
});
|
|
119
|
+
const muted = await slackRuntimeDecision({
|
|
120
|
+
channel: 'C123',
|
|
121
|
+
channel_type: 'channel',
|
|
122
|
+
text: 'muted thread follow-up',
|
|
123
|
+
thread_ts: '1770000010.000001',
|
|
124
|
+
ts: '1770000013.000001',
|
|
125
|
+
type: 'message',
|
|
126
|
+
user: 'U123',
|
|
127
|
+
}, { agentId: 'scout', nowMs: 30 * 24 * 60 * 60 * 1000 + 2 });
|
|
128
|
+
assert.equal(muted.shouldStartRuntime, false);
|
|
129
|
+
assert.equal(muted.reason, 'muted');
|
|
130
|
+
const revived = await slackRuntimeDecision({
|
|
131
|
+
channel: 'C123',
|
|
132
|
+
channel_type: 'channel',
|
|
133
|
+
text: '<@U999> come back',
|
|
134
|
+
thread_ts: '1770000010.000001',
|
|
135
|
+
ts: '1770000014.000001',
|
|
136
|
+
type: 'app_mention',
|
|
137
|
+
user: 'U123',
|
|
138
|
+
}, { agentId: 'scout', nowMs: 30 * 24 * 60 * 60 * 1000 + 3 });
|
|
139
|
+
assert.equal(revived.shouldStartRuntime, true);
|
|
140
|
+
assert.equal(revived.reason, 'mention');
|
|
141
|
+
assert.equal(revived.subscription?.status, 'following');
|
|
142
|
+
const state = await loadState();
|
|
143
|
+
const thread = Object.values(state.subscriptions).find((subscription) => subscription.kind === 'thread' && subscription.threadTs === '1770000010.000001');
|
|
144
|
+
assert.equal(thread?.mutedAt, undefined);
|
|
145
|
+
assert.equal('expiresAt' in (thread ?? {}), false);
|
|
146
|
+
assert.equal('remainingMessages' in (thread ?? {}), false);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
test('sent messages follow only threads, not whole channels', async () => {
|
|
154
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-sent-thread-follow-test-'));
|
|
155
|
+
try {
|
|
156
|
+
await withAnimaHome(stateDir, async () => {
|
|
157
|
+
const sentSubscription = await ensureThreadSubscriptionForSentMessage({
|
|
158
|
+
agentId: 'scout',
|
|
159
|
+
channelId: 'C123',
|
|
160
|
+
messageTs: '1770000020.000001',
|
|
161
|
+
nowMs: 1_000,
|
|
162
|
+
});
|
|
163
|
+
assert.equal(sentSubscription?.kind, 'thread');
|
|
164
|
+
assert.equal(sentSubscription?.threadTs, '1770000020.000001');
|
|
165
|
+
const state = await loadState();
|
|
166
|
+
assert.equal(Object.values(state.subscriptions).some((subscription) => subscription.kind === 'channel'), false);
|
|
167
|
+
const reply = await slackRuntimeDecision({
|
|
168
|
+
channel: 'C123',
|
|
169
|
+
channel_type: 'channel',
|
|
170
|
+
text: 'replying in the agent-started thread',
|
|
171
|
+
thread_ts: '1770000020.000001',
|
|
172
|
+
ts: '1770000021.000001',
|
|
173
|
+
type: 'message',
|
|
174
|
+
user: 'U456',
|
|
175
|
+
}, { agentId: 'scout', nowMs: 2_000 });
|
|
176
|
+
assert.equal(reply.shouldStartRuntime, true);
|
|
177
|
+
assert.equal(reply.reason, 'thread_follow');
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
test('sent replies also follow their own message thread root', async () => {
|
|
185
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-sent-reply-thread-follow-test-'));
|
|
186
|
+
try {
|
|
187
|
+
await withAnimaHome(stateDir, async () => {
|
|
188
|
+
const sentSubscription = await ensureThreadSubscriptionForSentMessage({
|
|
189
|
+
agentId: 'scout',
|
|
190
|
+
channelId: 'C123',
|
|
191
|
+
messageTs: '1770000020.000123',
|
|
192
|
+
nowMs: 1_000,
|
|
193
|
+
threadTs: '1770000010.000001',
|
|
194
|
+
});
|
|
195
|
+
assert.equal(sentSubscription?.kind, 'thread');
|
|
196
|
+
assert.equal(sentSubscription?.threadTs, '1770000010.000001');
|
|
197
|
+
const state = await loadState();
|
|
198
|
+
assert.equal(Object.values(state.subscriptions).some((subscription) => subscription.kind === 'channel'), false);
|
|
199
|
+
assert.ok(Object.values(state.subscriptions).some((subscription) => subscription.kind === 'thread' && subscription.threadTs === '1770000010.000001'));
|
|
200
|
+
assert.ok(Object.values(state.subscriptions).some((subscription) => subscription.kind === 'thread' && subscription.threadTs === '1770000020.000123'));
|
|
201
|
+
const replyToSentReply = await slackRuntimeDecision({
|
|
202
|
+
channel: 'C123',
|
|
203
|
+
channel_type: 'channel',
|
|
204
|
+
text: 'replying to the agent reply as its own thread',
|
|
205
|
+
thread_ts: '1770000020.000123',
|
|
206
|
+
ts: '1770000021.000001',
|
|
207
|
+
type: 'message',
|
|
208
|
+
user: 'U456',
|
|
209
|
+
}, { agentId: 'scout', nowMs: 2_000 });
|
|
210
|
+
assert.equal(replyToSentReply.shouldStartRuntime, true);
|
|
211
|
+
assert.equal(replyToSentReply.reason, 'thread_follow');
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
test('attention nudge suggests muting after repeated wakes without posting', async () => {
|
|
219
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-attention-nudge-test-'));
|
|
220
|
+
try {
|
|
221
|
+
await withAnimaHome(stateDir, async () => {
|
|
222
|
+
let last;
|
|
223
|
+
for (let i = 0; i < 6; i += 1) {
|
|
224
|
+
last = await slackRuntimeDecision({
|
|
225
|
+
channel: 'C123',
|
|
226
|
+
channel_type: 'channel',
|
|
227
|
+
text: `wake ${i}`,
|
|
228
|
+
ts: `17700000${10 + i}.000001`,
|
|
229
|
+
type: 'message',
|
|
230
|
+
user: 'U123',
|
|
231
|
+
}, { agentId: 'scout', nowMs: 1_000 + i });
|
|
232
|
+
}
|
|
233
|
+
assert.equal(last?.shouldStartRuntime, true);
|
|
234
|
+
assert.match(last?.attentionSuggestion ?? '', /anima subscription mute --channel C123/);
|
|
235
|
+
await recordChannelPost({ agentId: 'scout', channelId: 'C123', nowMs: 2_000 });
|
|
236
|
+
const afterPost = await slackRuntimeDecision({
|
|
237
|
+
channel: 'C123',
|
|
238
|
+
channel_type: 'channel',
|
|
239
|
+
text: 'after post',
|
|
240
|
+
ts: '1770000020.000001',
|
|
241
|
+
type: 'message',
|
|
242
|
+
user: 'U123',
|
|
243
|
+
}, { agentId: 'scout', nowMs: 2_001 });
|
|
244
|
+
assert.equal(afterPost.attentionSuggestion, undefined);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
test('attention map shows member channels, muted threads, and quiet thread tails', async () => {
|
|
252
|
+
const stateDir = await mkdtemp(join(tmpdir(), 'anima-slack-attention-map-test-'));
|
|
253
|
+
try {
|
|
254
|
+
await withAnimaHome(stateDir, async () => {
|
|
255
|
+
await slackRuntimeDecision({
|
|
256
|
+
channel: 'C123',
|
|
257
|
+
channel_type: 'channel',
|
|
258
|
+
text: '<@U999> recent',
|
|
259
|
+
thread_ts: '1770000010.000001',
|
|
260
|
+
ts: '1770000011.000001',
|
|
261
|
+
type: 'app_mention',
|
|
262
|
+
user: 'U123',
|
|
263
|
+
}, { agentId: 'scout', nowMs: Date.UTC(2026, 0, 10) });
|
|
264
|
+
await ensureThreadSubscriptionForSentMessage({
|
|
265
|
+
agentId: 'scout',
|
|
266
|
+
channelId: 'C123',
|
|
267
|
+
messageTs: '1770000001.000001',
|
|
268
|
+
nowMs: Date.UTC(2025, 0, 1),
|
|
269
|
+
});
|
|
270
|
+
await muteSubscriptionForAgent({
|
|
271
|
+
agentId: 'scout',
|
|
272
|
+
channelId: 'C456',
|
|
273
|
+
threadTs: '1770000030.000001',
|
|
274
|
+
nowMs: Date.UTC(2026, 0, 11),
|
|
275
|
+
});
|
|
276
|
+
const subscriptions = Object.values((await loadState()).subscriptions);
|
|
277
|
+
const map = attentionMapForSubscriptions({
|
|
278
|
+
memberChannels: [{ id: 'C123', name: 'team' }, { id: 'C999', name: 'support' }],
|
|
279
|
+
nowMs: Date.UTC(2026, 0, 12),
|
|
280
|
+
subscriptions,
|
|
281
|
+
});
|
|
282
|
+
assert.deepEqual(map.channels.map((channel) => `${channel.channelId}:${channel.status}`).sort(), ['C999:following', 'C123:following'].sort());
|
|
283
|
+
assert.equal(map.activeThreads.length, 1);
|
|
284
|
+
assert.equal(map.mutedThreads.length, 1);
|
|
285
|
+
assert.equal(map.quietThreadCount, 1);
|
|
286
|
+
assert.equal(map.quietThreads.length, 0);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
await rm(stateDir, { force: true, recursive: true });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-service.test.d.ts","sourceRoot":"","sources":["../../../server/tests/system-service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { docsUrl } from '../services/system.service.js';
|
|
4
|
+
test('docsUrl uses public docs by default and accepts an explicit override', () => {
|
|
5
|
+
const previous = process.env.ANIMA_DOCS_URL;
|
|
6
|
+
try {
|
|
7
|
+
delete process.env.ANIMA_DOCS_URL;
|
|
8
|
+
assert.equal(docsUrl('dev'), 'http://127.0.0.1:14175/');
|
|
9
|
+
assert.equal(docsUrl('canary'), 'https://anima.meetquinn.ai/');
|
|
10
|
+
assert.equal(docsUrl('stable'), 'https://anima.meetquinn.ai/');
|
|
11
|
+
process.env.ANIMA_DOCS_URL = 'http://127.0.0.1:14175/';
|
|
12
|
+
assert.equal(docsUrl('stable'), 'http://127.0.0.1:14175/');
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
if (previous === undefined)
|
|
16
|
+
delete process.env.ANIMA_DOCS_URL;
|
|
17
|
+
else
|
|
18
|
+
process.env.ANIMA_DOCS_URL = previous;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-routes.test.d.ts","sourceRoot":"","sources":["../../../server/tests/url-routes.test.ts"],"names":[],"mappings":""}
|