feat(server,contracts,web): dashboard library counts and engine runs triggeredBy/multi-status filters

- GET /dashboard: automation.library {entities, movies, series, seasons,
  episodes, assets} and automation.slotSatisfiedRate; home media overview
  shows the counts instead of placeholders.
- GET /engine/runs: `status` accepts comma-separated values; new
  `triggeredBy` filter (comma-separated) evaluated server-side by scanning
  batches and paging with the last inspected row's cursor (scan cap 2000).
- Task stream: source filter and the "active" tab (running,pending) are now
  one paginated server query.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 16:49:11 +08:00
1 parent 615917bb45
commit 363f85b1a1
13 files changed
+165 -250

No files matched your search

+22
View File
@@ -353,6 +353,28 @@ describe.skipIf(!hasPg)("W9-I 进程与健康(真实 PG)", () => {
expect(s.data.items.map((r: { id: string }) => r.id)).toEqual(["b4"]);
expect((await admin("GET", "/engine/runs?businessKind=bogus")).status).toBe(422);
// triggeredBy:服务端逐页推断后筛选;多值逗号分隔;limit 截断时游标接着扫
const range = "from=2026-09-10T00:00:00Z&to=2026-09-11T00:00:00Z";
const ids = (r: { data: { items: { id: string }[] } }) => r.data.items.map((x) => x.id);
expect(ids(await json(await admin("GET", `/engine/runs?${range}&triggeredBy=schedule`)))).toEqual(["b4", "b2"]);
expect(ids(await json(await admin("GET", `/engine/runs?${range}&triggeredBy=event,api_key`)))).toEqual([
"b3",
"b1",
]);
const t1 = await json(await admin("GET", `/engine/runs?${range}&triggeredBy=schedule&limit=1`));
expect(ids(t1)).toEqual(["b4"]);
expect(t1.data.nextCursor).toBeTruthy();
const t2 = await json(
await admin("GET", `/engine/runs?${range}&triggeredBy=schedule&limit=1&cursor=${t1.data.nextCursor}`),
);
expect(ids(t2)).toEqual(["b2"]);
const t3 = await json(
await admin("GET", `/engine/runs?${range}&triggeredBy=schedule&limit=1&cursor=${t2.data.nextCursor}`),
);
expect(ids(t3)).toEqual([]);
expect(t3.data.nextCursor).toBeNull();
expect((await admin("GET", "/engine/runs?triggeredBy=bogus")).status).toBe(422);
const detail = (await json(await admin("GET", "/engine/runs/b2"))).data;
expect(detail.business).toMatchObject({ kind: "agent", refId: agentId });
expect(detail.stepAttempts).toEqual([]);