@zaip-ai/cli 4.8.0 → 5.0.0

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 (195) hide show
  1. package/dist/services/adapter-hook-sources.d.ts +3 -0
  2. package/dist/services/adapter-hook-sources.d.ts.map +1 -1
  3. package/dist/services/adapter-hook-sources.js +3 -0
  4. package/dist/services/adapter-hook-sources.js.map +1 -1
  5. package/dist/services/adapters.d.ts +1 -1
  6. package/dist/services/adapters.d.ts.map +1 -1
  7. package/dist/services/adapters.js +5 -2
  8. package/dist/services/adapters.js.map +1 -1
  9. package/dist/services/adapters.test.js +1 -1
  10. package/dist/services/adapters.test.js.map +1 -1
  11. package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
  12. package/dist/services/codex-conversation-lifecycle-hook.js +16 -6
  13. package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
  14. package/dist/services/codex-hook-activation.d.ts +1 -1
  15. package/dist/services/codex-hook-activation.d.ts.map +1 -1
  16. package/dist/services/codex-hook-activation.js +116 -10
  17. package/dist/services/codex-hook-activation.js.map +1 -1
  18. package/dist/services/codex-pre-tool-cli-capture-template.d.ts +2 -0
  19. package/dist/services/codex-pre-tool-cli-capture-template.d.ts.map +1 -0
  20. package/dist/services/codex-pre-tool-cli-capture-template.js +104 -0
  21. package/dist/services/codex-pre-tool-cli-capture-template.js.map +1 -0
  22. package/dist/services/codex-pre-tool-cloud-template.d.ts +2 -0
  23. package/dist/services/codex-pre-tool-cloud-template.d.ts.map +1 -0
  24. package/dist/services/codex-pre-tool-cloud-template.js +190 -0
  25. package/dist/services/codex-pre-tool-cloud-template.js.map +1 -0
  26. package/dist/services/codex-pre-tool-dependencies.test-support.d.ts +2 -0
  27. package/dist/services/codex-pre-tool-dependencies.test-support.d.ts.map +1 -0
  28. package/dist/services/codex-pre-tool-dependencies.test-support.js +9 -0
  29. package/dist/services/codex-pre-tool-dependencies.test-support.js.map +1 -0
  30. package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts +5 -0
  31. package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts.map +1 -0
  32. package/dist/services/codex-pre-tool-hook-fixture.test-support.js +19 -0
  33. package/dist/services/codex-pre-tool-hook-fixture.test-support.js.map +1 -0
  34. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts +2 -0
  35. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts.map +1 -0
  36. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js +107 -0
  37. package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js.map +1 -0
  38. package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
  39. package/dist/services/codex-pre-tool-hook-template.js +152 -190
  40. package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
  41. package/dist/services/codex-pre-tool-hook-template.test.js +63 -75
  42. package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
  43. package/dist/services/codex-pre-tool-host-context-template.d.ts +2 -0
  44. package/dist/services/codex-pre-tool-host-context-template.d.ts.map +1 -0
  45. package/dist/services/codex-pre-tool-host-context-template.js +139 -0
  46. package/dist/services/codex-pre-tool-host-context-template.js.map +1 -0
  47. package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
  48. package/dist/services/codex-pre-tool-support-template.js +77 -100
  49. package/dist/services/codex-pre-tool-support-template.js.map +1 -1
  50. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  51. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  52. package/dist/services/codex-subagent-hook-template.js +59 -15
  53. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  54. package/dist/services/codex-validation-runtime-template.d.ts.map +1 -1
  55. package/dist/services/codex-validation-runtime-template.js +37 -10
  56. package/dist/services/codex-validation-runtime-template.js.map +1 -1
  57. package/dist/services/runtime-template-test-support.d.ts +1 -0
  58. package/dist/services/runtime-template-test-support.d.ts.map +1 -1
  59. package/dist/services/runtime-template-test-support.js +23 -2
  60. package/dist/services/runtime-template-test-support.js.map +1 -1
  61. package/dist/services/runtime-template.test.js +7 -24
  62. package/dist/services/runtime-template.test.js.map +1 -1
  63. package/package.json +1 -1
  64. package/templates/.agents/README-ENG.md +49 -7
  65. package/templates/.agents/README.md +41 -10
  66. package/templates/.agents/config/agents/discovery.json +1 -1
  67. package/templates/.agents/dashboard/.zaip-managed.json +125 -73
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BW7Vy1cp.js → CostCalculationGuidePage-w6pjt4OM.js} +1 -1
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BN8KQPrr.js → DashboardHelpDialog-DhlntQcR.js} +117 -17
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Prcs-ydy.js → PricingSettingsDialog-BHoZT2an.js} +1 -1
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-HqGUImIG.js → ProjectSettingsPage-IcncIZ9Y.js} +1 -1
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CNpmQMO4.js → TraceabilityWorkspace-C6rORjpr.js} +1 -1
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-uPnOBShc.js +68 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-CF7B1Hjn.js → dashboard-localization-DWXjEIgc.js} +49 -7
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DOhy6D2-.js → icons-vendor-Hc9fp_gs.js} +1 -1
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-DX65Ufuu.js → index-BqeOEZW5.js} +2 -2
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CkPC-yMz.js → react-vendor-C9ta_O10.js} +1 -1
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-BEdGoIDm.js → traceability-IcefoTOv.js} +1 -1
  79. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  80. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  81. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  82. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +10 -3
  83. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +5 -1
  84. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostGroupsTable.period-values.test.tsx +2 -2
  85. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-keeps-legacy-codex-identity-fields-blank.test.ts +5 -5
  86. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +36 -43
  87. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-loaders.ts +1 -0
  88. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-formatters.ts +6 -4
  89. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +2 -2
  90. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +1 -1
  91. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +11 -25
  92. package/templates/.agents/dashboard/modules/cost-monitor/src/server/cost-summary-defaults.ts +26 -0
  93. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  94. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +11 -16
  95. package/templates/.agents/dashboard/modules/operations/src/planning-contracts.ts +14 -0
  96. package/templates/.agents/dashboard/modules/operations/src/react/DecisionTable.tsx +65 -0
  97. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +5 -71
  98. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +10 -36
  99. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +7 -2
  100. package/templates/.agents/dashboard/modules/operations/src/react/use-operations-filter-sync.ts +66 -0
  101. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  102. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  103. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  104. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  105. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +229 -222
  106. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +167 -167
  107. package/templates/.agents/dashboard/packages/local-runtime/package.json +3 -3
  108. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +229 -222
  109. package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.test.ts +38 -0
  110. package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.ts +35 -0
  111. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +69 -6
  112. package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +62 -15
  113. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +8 -2
  114. package/templates/.agents/dashboard/packages/local-runtime/src/server-capture-projection-order.test.ts +118 -0
  115. package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.test.ts +90 -0
  116. package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.ts +86 -0
  117. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-routes.ts +8 -1
  118. package/templates/.agents/dashboard/packages/local-runtime/src/server-projection-loaders.ts +13 -0
  119. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +30 -74
  120. package/templates/.agents/dashboard/packages/local-runtime/src/server-route-handlers.ts +42 -0
  121. package/templates/.agents/dashboard/packages/local-runtime/src/server-routes.test.ts +60 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +12 -2
  123. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +29 -4
  124. package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +7 -0
  125. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +51 -54
  126. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +37 -48
  127. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-filter-matrix.test.ts +5 -0
  128. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +49 -0
  129. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +44 -1
  130. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-cached-cost-page.ts +11 -10
  131. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +1 -1
  132. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +18 -1
  133. package/templates/.agents/dashboard/packages/local-runtime/vitest.config.ts +12 -0
  134. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  135. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  136. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +13 -6
  137. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +1 -1
  138. package/templates/.agents/design/.zaip-managed.json +3 -3
  139. package/templates/.agents/design/apps/catalog/package.json +1 -1
  140. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  141. package/templates/.agents/requirements/autonomous-assurance.md +39 -3
  142. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
  143. package/templates/.agents/requirements/comparative-audit.md +1 -1
  144. package/templates/.agents/requirements/context-engine.md +3 -3
  145. package/templates/.agents/requirements/cost-observability.md +1 -1
  146. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  147. package/templates/.agents/requirements/data-export.md +1 -1
  148. package/templates/.agents/requirements/design-system.md +1 -1
  149. package/templates/.agents/requirements/execution-engine.md +11 -2
  150. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  151. package/templates/.agents/requirements/model-orchestration.md +1 -1
  152. package/templates/.agents/requirements/public-distribution.md +1 -1
  153. package/templates/.agents/requirements/software-architecture.md +1 -1
  154. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  155. package/templates/.agents/requirements/work-lifecycle.md +2 -1
  156. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  157. package/templates/.agents/rules/cloud-hostinger-autonomy.md +1 -1
  158. package/templates/.agents/rules/production-readiness.md +24 -0
  159. package/templates/.agents/rules/run-lifecycle-closure.md +2 -1
  160. package/templates/.agents/rules/security.md +2 -2
  161. package/templates/.agents/rules/workflow.md +4 -3
  162. package/templates/.agents/runtime/VERSION +1 -1
  163. package/templates/.agents/runtime/bin/zaip.mjs +1185 -610
  164. package/templates/.agents/runtime/checksums.sha256 +6 -6
  165. package/templates/.agents/runtime/manifest.json +1 -1
  166. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  167. package/templates/.agents/runtime/provenance.json +5 -5
  168. package/templates/.agents/runtime/sbom.cdx.json +71 -71
  169. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -2
  170. package/templates/.agents/skills/zaip-skill-execution/references/controlled-release-recovery.md +44 -0
  171. package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +1 -1
  172. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +7 -1
  173. package/templates/.agents/skills/zaip-skill-github-deploy/references/release-gate-matrix.md +15 -20
  174. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +38 -2
  175. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +62 -4
  176. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +8 -6
  177. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +5 -3
  178. package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +7 -0
  179. package/templates/.agents/skills/zaip-skill-maintenance-enter/SKILL.md +23 -0
  180. package/templates/.agents/skills/zaip-skill-maintenance-enter/agents/openai.yaml +4 -0
  181. package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.mjs +213 -0
  182. package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.test.mjs +207 -0
  183. package/templates/.agents/skills/zaip-skill-maintenance-exit/SKILL.md +21 -0
  184. package/templates/.agents/skills/zaip-skill-maintenance-exit/agents/openai.yaml +4 -0
  185. package/templates/.agents/skills/zaip-skill-prd/SKILL.md +6 -0
  186. package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +26 -0
  187. package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +46 -11
  188. package/templates/.agents/skills/zaip-skill-spec/SKILL.md +9 -0
  189. package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +15 -0
  190. package/templates/.agents/zaip-aios/VERSION +1 -1
  191. package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +22 -0
  192. package/templates/.agents/zaip-aios/guides/14-troubleshooting.md +1 -1
  193. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +52 -5
  194. package/templates/template-manifest.json +210 -126
  195. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Co2Jiuzh.js +0 -68
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { activeCaptureFailure } from "./active-capture-failure.js";
3
+
4
+ describe("public active capture diagnostics", () => {
5
+ it.each([
6
+ [{ code: "ETIMEDOUT" }, "timeout"],
7
+ [{ code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" }, "output_limit"],
8
+ [{ killed: true, signal: "SIGTERM" }, "process_terminated"],
9
+ [{ code: 1 }, "process_exit"],
10
+ [{ code: "ENOENT" }, "spawn_failure"],
11
+ ])("classifies process failures without guessing their cause", (fields, kind) => {
12
+ expect(activeCaptureFailure(Object.assign(new Error("private"), fields))).toMatchObject({
13
+ code: "active_telemetry_capture_failed",
14
+ failure_kind: kind,
15
+ exit_code: "code" in fields && typeof fields.code === "number" ? fields.code : null,
16
+ });
17
+ });
18
+
19
+ it("never exposes child output, command, paths or arbitrary error properties", () => {
20
+ const error = Object.assign(new Error("Bearer sensitive-value"), {
21
+ name: "sensitive-value",
22
+ stdout: "sensitive-value",
23
+ stderr: "sensitive-value",
24
+ cmd: "sensitive-value",
25
+ code: "sensitive-value",
26
+ signal: "sensitive-value",
27
+ });
28
+ const result = activeCaptureFailure(error);
29
+ expect(JSON.stringify(result)).not.toContain("sensitive-value");
30
+ expect(result).toMatchObject({
31
+ failure_kind: "capture_error",
32
+ process_code: null,
33
+ exit_code: null,
34
+ signal: null,
35
+ });
36
+ expect(activeCaptureFailure(null).cause).toBe("unknown_error");
37
+ });
38
+ });
@@ -0,0 +1,35 @@
1
+ /** Public health diagnostics never include command lines, environment or process output. */
2
+ export function activeCaptureFailure(error: unknown) {
3
+ const failure = error instanceof Error ? error : null;
4
+ const fields = failure as (Error & { code?: unknown; signal?: unknown; killed?: unknown }) | null;
5
+ const code = fields?.code;
6
+ const signal = fields?.signal;
7
+ const exitCode = Number.isSafeInteger(code) ? (code as number) : null;
8
+ const processCode =
9
+ typeof code === "string" &&
10
+ ["ENOENT", "EACCES", "EPERM", "ETIMEDOUT", "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"].includes(code)
11
+ ? code
12
+ : null;
13
+ const processSignal =
14
+ typeof signal === "string" && /^SIG[A-Z0-9]{1,12}$/.test(signal) ? signal : null;
15
+ return {
16
+ code: "active_telemetry_capture_failed",
17
+ occurred_at: new Date().toISOString(),
18
+ cause: failure === null ? "unknown_error" : "Error",
19
+ failure_kind:
20
+ processCode === "ETIMEDOUT"
21
+ ? "timeout"
22
+ : processCode === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
23
+ ? "output_limit"
24
+ : fields?.killed === true || processSignal !== null
25
+ ? "process_terminated"
26
+ : exitCode !== null
27
+ ? "process_exit"
28
+ : processCode !== null
29
+ ? "spawn_failure"
30
+ : "capture_error",
31
+ process_code: processCode,
32
+ exit_code: exitCode,
33
+ signal: processSignal,
34
+ };
35
+ }
@@ -2,10 +2,11 @@ import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterEach, describe, expect, it, vi } from "vitest";
5
-
5
+ import { activeCaptureFailure } from "./active-capture-failure.js";
6
6
  import {
7
7
  captureActiveCodexTelemetry,
8
8
  captureRepositoryActiveCodexTelemetry,
9
+ ObservedCodexCaptureDeadlineError,
9
10
  } from "./active-codex-telemetry.js";
10
11
  import * as sources from "./sqlite-read-model-source-fingerprint.js";
11
12
 
@@ -17,7 +18,7 @@ afterEach(async () => {
17
18
  });
18
19
 
19
20
  describe("active Codex telemetry capture", () => {
20
- it("allows the measured catch-up budget and still rejects incomplete output", async () => {
21
+ it("bounds one automatic pass and rejects a partial receipt without progress", async () => {
21
22
  const root = await runtimeFixture("");
22
23
  const execute = vi.fn(async (_file: string, _args: string[], _options: unknown) => ({
23
24
  stdout: JSON.stringify({
@@ -26,14 +27,64 @@ describe("active Codex telemetry capture", () => {
26
27
  stderr: "",
27
28
  }));
28
29
  await expect(captureActiveCodexTelemetry(root, root, execute as never)).rejects.toThrow(
29
- "incomplete",
30
+ "bounded pending progress",
30
31
  );
31
32
  expect(execute.mock.calls[0]?.[2]).toMatchObject({
32
- timeout: 600_000,
33
+ timeout: 125_000,
34
+ signal: expect.any(AbortSignal),
33
35
  windowsHide: true,
34
36
  cwd: root,
35
37
  });
36
38
  });
39
+ it("marks timeout only when its own child deadline aborts the project process", async () => {
40
+ const root = await runtimeFixture("");
41
+ const deadline = new AbortController();
42
+ const execute = vi.fn(
43
+ async (_file: string, _args: string[], options: { signal: AbortSignal }) =>
44
+ new Promise<never>((_resolve, reject) => {
45
+ options.signal.addEventListener(
46
+ "abort",
47
+ () =>
48
+ reject(
49
+ Object.assign(new Error("private child output"), {
50
+ name: "AbortError",
51
+ code: "ABORT_ERR",
52
+ }),
53
+ ),
54
+ { once: true },
55
+ );
56
+ deadline.abort();
57
+ }),
58
+ );
59
+ await expect(
60
+ captureActiveCodexTelemetry(root, root, execute as never, deadline.signal),
61
+ ).rejects.toBeInstanceOf(ObservedCodexCaptureDeadlineError);
62
+ const failure = activeCaptureFailure(new ObservedCodexCaptureDeadlineError());
63
+ expect(failure).toMatchObject({ failure_kind: "timeout", process_code: "ETIMEDOUT" });
64
+ expect(JSON.stringify(failure)).not.toContain("private child output");
65
+ expect(
66
+ activeCaptureFailure(
67
+ Object.assign(new Error("external termination"), {
68
+ killed: true,
69
+ signal: "SIGTERM",
70
+ }),
71
+ ).failure_kind,
72
+ ).toBe("process_terminated");
73
+ const externalAbort = Object.assign(new Error("external cancellation"), {
74
+ name: "AbortError",
75
+ code: "ABORT_ERR",
76
+ });
77
+ await expect(
78
+ captureActiveCodexTelemetry(
79
+ root,
80
+ root,
81
+ (async () => {
82
+ throw externalAbort;
83
+ }) as never,
84
+ new AbortController().signal,
85
+ ),
86
+ ).rejects.toBe(externalAbort);
87
+ });
37
88
  it("uses the primary CLI for registered worktrees while preserving each target workspace and cwd", async () => {
38
89
  const primary = await runtimeFixture(`import {writeFileSync} from 'node:fs';
39
90
  import {resolve} from 'node:path';
@@ -77,7 +128,7 @@ describe("active Codex telemetry capture", () => {
77
128
 
78
129
  it("does not let an empty or stale active index suppress terminal usage capture", async () => {
79
130
  const root = await runtimeFixture(
80
- "if(!process.argv.includes('--recent') || !process.argv.includes('--drain') || process.argv.includes('--active') || process.argv[process.argv.indexOf('--limit')+1]!=='32') process.exit(8); process.stdout.write(JSON.stringify({telemetry:{mode:'recent_exact_session',complete:true,captured:[{captured:true}]}}));\n",
131
+ "if(!process.argv.includes('--recent') || process.argv.includes('--drain') || process.argv.includes('--active') || process.argv[process.argv.indexOf('--limit')+1]!=='32') process.exit(8); process.stdout.write(JSON.stringify({telemetry:{mode:'recent_exact_session',complete:true,captured:[{captured:true}]}}));\n",
81
132
  );
82
133
  await writeFile(
83
134
  join(root, ".agents/records/lifecycle/active-work.json"),
@@ -150,7 +201,19 @@ describe("active Codex telemetry capture", () => {
150
201
  const root = await runtimeFixture(
151
202
  "process.stdout.write(JSON.stringify({telemetry:{mode:'recent_exact_session',complete:false,captured:[]}}));",
152
203
  );
153
- await expect(captureActiveCodexTelemetry(root)).rejects.toThrow("incomplete");
204
+ await expect(captureActiveCodexTelemetry(root)).rejects.toThrow("bounded pending progress");
205
+ });
206
+ it("reports an examined partial window as pending without inventing missing usage", async () => {
207
+ const root = await runtimeFixture(
208
+ "process.stdout.write(JSON.stringify({telemetry:{mode:'recent_exact_session',complete:false,candidate_count:70,processed_count:32,pending_count:38,captured:[{captured:false}]}}));",
209
+ );
210
+ await expect(captureActiveCodexTelemetry(root)).resolves.toMatchObject({
211
+ captured_runs: 0,
212
+ captured_segments: 0,
213
+ catch_up_complete: false,
214
+ pending_runs: 38,
215
+ skipped_reason: "catch_up_pending",
216
+ });
154
217
  });
155
218
  it.each([
156
219
  "active_exact_session",
@@ -6,13 +6,21 @@ import { lifecycleManifestState } from "./server-lifecycle-diagnostics.js";
6
6
  import { dashboardCanonicalSourceRoots } from "./sqlite-read-model-source-fingerprint.js";
7
7
 
8
8
  const execFileAsync = promisify(execFile);
9
- // Measured complete catch-up of a 98-Run / 30k-envelope backlog exceeded the
10
- // old 55s deadline. Keep the UI updating while this finite selection commits;
11
- // expiry remains a failure and can never promote a partial capture to current.
12
- export const RECENT_CODEX_CAPTURE_TIMEOUT_MS = 600_000;
9
+ // One automatic pass examines at most 32 Runs. Manual --drain remains separate.
10
+ export const RECENT_CODEX_CAPTURE_TIMEOUT_MS = 120_000;
11
+
12
+ export class ObservedCodexCaptureDeadlineError extends Error {
13
+ readonly code = "ETIMEDOUT";
14
+ readonly deadline_observed = true;
15
+ constructor() {
16
+ super("Recent Codex capture exceeded its observed process deadline");
17
+ }
18
+ }
13
19
 
14
20
  export type ActiveCodexTelemetryCapture = {
15
21
  captured_runs: number;
22
+ catch_up_complete?: boolean;
23
+ pending_runs?: number;
16
24
  /** The active CLI reports whether a Run changed, not its segment cardinality. */
17
25
  captured_segments: number | null;
18
26
  manifest_state: "initialized" | "missing" | "invalid";
@@ -23,6 +31,7 @@ export type ActiveCodexTelemetryCapture = {
23
31
  | "no_recent_active_runs"
24
32
  | "no_recent_runs"
25
33
  | "no_new_segments"
34
+ | "catch_up_pending"
26
35
  | null;
27
36
  last_capture_at: string;
28
37
  };
@@ -36,6 +45,7 @@ export async function captureActiveCodexTelemetry(
36
45
  workspaceRoot: string,
37
46
  runtimeRoot = workspaceRoot,
38
47
  execute = execFileAsync,
48
+ deadline = AbortSignal.timeout(RECENT_CODEX_CAPTURE_TIMEOUT_MS),
39
49
  ): Promise<ActiveCodexTelemetryCapture> {
40
50
  // The CLI intentionally treats an absent active-work index as an empty pass.
41
51
  // Validate the canonical manifest first so that exit 0 cannot mask missing lifecycle.
@@ -61,7 +71,6 @@ export async function captureActiveCodexTelemetry(
61
71
  "--recent",
62
72
  "--limit",
63
73
  "32",
64
- "--drain",
65
74
  "--disable-codex-raw",
66
75
  "--json",
67
76
  ],
@@ -69,24 +78,40 @@ export async function captureActiveCodexTelemetry(
69
78
  cwd: workspaceRoot,
70
79
  encoding: "utf8",
71
80
  windowsHide: true,
72
- timeout: RECENT_CODEX_CAPTURE_TIMEOUT_MS,
81
+ signal: deadline,
82
+ // A secondary child-process guard covers a broken abort delivery.
83
+ timeout: RECENT_CODEX_CAPTURE_TIMEOUT_MS + 5_000,
73
84
  maxBuffer: 1_024 * 1_024,
74
85
  },
75
86
  );
76
87
  const payload = parseJsonResult(outcome.stdout);
77
- const captured = readCaptured(payload);
88
+ const receipt = readCaptured(payload);
89
+ const captured = receipt.captured;
78
90
  const capturedRuns = captured.filter((item) => item.captured).length;
79
91
  return {
80
92
  captured_runs: capturedRuns,
93
+ catch_up_complete: receipt.complete,
94
+ pending_runs: receipt.pending,
81
95
  // Zero is factual for an idle pass. A non-idle pass has no segment count
82
96
  // in the CLI contract, so retain unavailable rather than inventing one.
83
97
  captured_segments: capturedRuns === 0 ? 0 : null,
84
98
  manifest_state: "initialized",
85
- skipped_reason:
86
- captured.length === 0 ? "no_recent_runs" : capturedRuns === 0 ? "no_new_segments" : null,
99
+ skipped_reason: !receipt.complete
100
+ ? "catch_up_pending"
101
+ : captured.length === 0
102
+ ? "no_recent_runs"
103
+ : capturedRuns === 0
104
+ ? "no_new_segments"
105
+ : null,
87
106
  last_capture_at: new Date().toISOString(),
88
107
  };
89
108
  } catch (error) {
109
+ if (
110
+ deadline.aborted &&
111
+ error instanceof Error &&
112
+ (error.name === "AbortError" || ("code" in error && error.code === "ABORT_ERR"))
113
+ )
114
+ throw new ObservedCodexCaptureDeadlineError();
90
115
  if (isManifestUninitialized(error)) {
91
116
  return {
92
117
  captured_runs: 0,
@@ -117,10 +142,13 @@ export async function captureRepositoryActiveCodexTelemetry(
117
142
  const capturedRuns = results.reduce((count, result) => count + result.captured_runs, 0);
118
143
  return {
119
144
  captured_runs: capturedRuns,
145
+ catch_up_complete: results.every((result) => result.catch_up_complete !== false),
146
+ pending_runs: results.reduce((count, result) => count + (result.pending_runs ?? 0), 0),
120
147
  captured_segments: capturedRuns === 0 ? 0 : null,
121
148
  manifest_state: primary.manifest_state,
122
- skipped_reason:
123
- capturedRuns > 0
149
+ skipped_reason: results.some((result) => result.catch_up_complete === false)
150
+ ? "catch_up_pending"
151
+ : capturedRuns > 0
124
152
  ? null
125
153
  : results.some((result) => result.skipped_reason === "no_new_segments")
126
154
  ? "no_new_segments"
@@ -138,17 +166,26 @@ function parseJsonResult(stdout: string): unknown {
138
166
  }
139
167
  }
140
168
 
141
- function readCaptured(payload: unknown): Array<{ captured: boolean }> {
169
+ function readCaptured(payload: unknown): {
170
+ captured: Array<{ captured: boolean }>;
171
+ complete: boolean;
172
+ pending: number;
173
+ } {
142
174
  if (typeof payload !== "object" || payload === null) throw new Error("Invalid capture payload");
143
175
  const telemetry = (payload as { telemetry?: unknown }).telemetry;
144
176
  if (typeof telemetry !== "object" || telemetry === null)
145
177
  throw new Error("Invalid capture telemetry");
146
- const receipt = telemetry as { mode?: unknown; complete?: unknown };
147
- if (receipt.mode !== "recent_exact_session" || receipt.complete !== true)
178
+ const receipt = telemetry as {
179
+ mode?: unknown;
180
+ complete?: unknown;
181
+ candidate_count?: unknown;
182
+ pending_count?: unknown;
183
+ };
184
+ if (receipt.mode !== "recent_exact_session" || typeof receipt.complete !== "boolean")
148
185
  throw new Error("Recent telemetry capture is incomplete");
149
186
  const captured = (telemetry as { captured?: unknown }).captured;
150
187
  if (!Array.isArray(captured)) throw new Error("Invalid capture entries");
151
- return captured.map((item) => {
188
+ const items = captured.map((item) => {
152
189
  if (
153
190
  typeof item !== "object" ||
154
191
  item === null ||
@@ -157,6 +194,16 @@ function readCaptured(payload: unknown): Array<{ captured: boolean }> {
157
194
  throw new Error("Invalid capture entry");
158
195
  return { captured: (item as { captured: boolean }).captured };
159
196
  });
197
+ const pending = receipt.complete ? 0 : receipt.pending_count;
198
+ if (
199
+ !receipt.complete &&
200
+ (!Number.isSafeInteger(pending) ||
201
+ (pending as number) < 1 ||
202
+ !Number.isSafeInteger(receipt.candidate_count) ||
203
+ (receipt.candidate_count as number) < items.length + (pending as number))
204
+ )
205
+ throw new Error("Recent telemetry capture lacks bounded pending progress");
206
+ return { captured: items, complete: receipt.complete, pending: pending as number };
160
207
  }
161
208
 
162
209
  function isManifestUninitialized(error: unknown): boolean {
@@ -160,7 +160,12 @@ export function manifestRun(
160
160
  artifact_path: `.agents/records/runs/${summary.run_id}`,
161
161
  status: summary.status ?? "completed",
162
162
  phase: summary.phase ?? "execution",
163
- entrypoint: summary.task_id === null ? "conversation" : "task",
163
+ entrypoint:
164
+ summary.purpose === "observation"
165
+ ? "observation"
166
+ : summary.task_id === null
167
+ ? "conversation"
168
+ : "task",
164
169
  work_type: "new",
165
170
  run_role: delegated ? ("subagent" as const) : ("orchestrator" as const),
166
171
  run_kind: delegated ? ("subrun" as const) : ("run" as const),
@@ -187,7 +192,7 @@ export function manifestRun(
187
192
  terminal_call_count: summary.llm_call_count,
188
193
  failed_call_count: summary.status === "failed" ? summary.llm_call_count : 0,
189
194
  cancelled_call_count: summary.status === "cancelled" ? summary.llm_call_count : 0,
190
- conversation_id: summary.agent_invocation_id,
195
+ conversation_id: summary.purpose === "observation" ? null : summary.agent_invocation_id,
191
196
  execution_id: summary.execution_id,
192
197
  task_id: summary.task_id,
193
198
  spec_id: summary.spec_id,
@@ -226,6 +231,7 @@ function manifestRunPurpose(
226
231
  delegated: boolean,
227
232
  ): RunProjection["run_purpose"] {
228
233
  if (
234
+ value === "observation" ||
229
235
  value === "intake" ||
230
236
  value === "orchestration" ||
231
237
  value === "execution" ||
@@ -0,0 +1,118 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("./server-snapshot-synchronization.js", () => ({
4
+ startSnapshotSynchronization: vi.fn(),
5
+ }));
6
+
7
+ import { createDashboardApp } from "./server.js";
8
+ import { withSqliteReadModelFixture } from "./server.test-fixtures.js";
9
+ import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
10
+
11
+ afterEach(() => vi.clearAllMocks());
12
+
13
+ describe("source changes during active telemetry", () => {
14
+ it.each([
15
+ false,
16
+ true,
17
+ ])("waits for an earlier projection before capture (failure=%s)", async (fail) => {
18
+ await withSqliteReadModelFixture("projection-first", async ({ root, filesystem }) => {
19
+ let settle!: () => void;
20
+ const refresh = vi
21
+ .fn(async () => undefined)
22
+ .mockImplementationOnce(async () => {
23
+ await new Promise<void>((resolve) => {
24
+ settle = resolve;
25
+ });
26
+ if (fail) throw new Error("projection failed");
27
+ });
28
+ const capture = vi.fn(async () => ({
29
+ captured_runs: 0,
30
+ captured_segments: null,
31
+ manifest_state: "initialized" as const,
32
+ skipped_reason: null,
33
+ last_capture_at: new Date().toISOString(),
34
+ }));
35
+ const app = createDashboardApp(root, "session", undefined, {
36
+ filesystem,
37
+ refreshReadModel: refresh,
38
+ captureTelemetry: capture,
39
+ });
40
+ const synchronization = vi.mocked(startSnapshotSynchronization).mock.calls.at(-1)?.[0];
41
+ if (!synchronization?.synchronizeTelemetry) throw new Error("Synchronization was not wired");
42
+ const source = synchronization.refresh().catch(() => undefined);
43
+ const capturing = synchronization.synchronizeTelemetry();
44
+ const joined = synchronization.refresh();
45
+ try {
46
+ await Promise.resolve();
47
+ expect(capture).not.toHaveBeenCalled();
48
+ expect(refresh).toHaveBeenCalledOnce();
49
+ } finally {
50
+ settle();
51
+ await Promise.all([source, capturing, joined]);
52
+ }
53
+ expect(capture).toHaveBeenCalledOnce();
54
+ expect(refresh).toHaveBeenCalledTimes(2);
55
+ expect(await (await app.request("/api/v2/health")).json()).toEqual(
56
+ expect.objectContaining({
57
+ active_capture_failure: null,
58
+ projection_refresh_failure: null,
59
+ }),
60
+ );
61
+ });
62
+ });
63
+ it.each([
64
+ false,
65
+ true,
66
+ ])("defers source projection until capture settles (failure=%s)", async (fail) => {
67
+ await withSqliteReadModelFixture("capture-order", async ({ root, filesystem }) => {
68
+ let settle!: () => void;
69
+ const refresh = vi.fn(async () => undefined);
70
+ const capture = vi.fn(async () => {
71
+ await new Promise<void>((resolve) => {
72
+ settle = resolve;
73
+ });
74
+ if (fail) throw Object.assign(new Error("capture failed"), { code: 1 });
75
+ return {
76
+ captured_runs: 1,
77
+ captured_segments: null,
78
+ manifest_state: "initialized" as const,
79
+ skipped_reason: null,
80
+ last_capture_at: new Date().toISOString(),
81
+ };
82
+ });
83
+ const app = createDashboardApp(root, "session", undefined, {
84
+ filesystem,
85
+ refreshReadModel: refresh,
86
+ captureTelemetry: capture,
87
+ });
88
+ const synchronization = vi.mocked(startSnapshotSynchronization).mock.calls.at(-1)?.[0];
89
+ if (!synchronization?.synchronizeTelemetry) throw new Error("Synchronization was not wired");
90
+ const capturing = synchronization.synchronizeTelemetry();
91
+ const sourceChange = synchronization.refresh();
92
+ const secondChange = synchronization.refresh();
93
+ try {
94
+ await Promise.resolve();
95
+ expect(refresh).not.toHaveBeenCalled();
96
+ } finally {
97
+ settle();
98
+ await Promise.all([capturing, sourceChange, secondChange]);
99
+ }
100
+ expect(capture).toHaveBeenCalledOnce();
101
+ expect(refresh).toHaveBeenCalledOnce();
102
+ const health = await (await app.request("/api/v2/health")).json();
103
+ expect(health).toEqual(
104
+ expect.objectContaining({
105
+ active_capture_failure: fail
106
+ ? expect.objectContaining({
107
+ failure_kind: "process_exit",
108
+ exit_code: 1,
109
+ })
110
+ : null,
111
+ }),
112
+ );
113
+ await synchronization.refresh();
114
+ expect(refresh).toHaveBeenCalledTimes(2);
115
+ expect(capture).toHaveBeenCalledOnce();
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,90 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
5
+ import { LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
6
+ import { Hono } from "hono";
7
+ import { expect, it } from "vitest";
8
+ import { buildManifestCostRunPage, type CompactRunMetric } from "./server-projections.js";
9
+
10
+ it("searches Task titles through the real compact projection before Run pagination", async () => {
11
+ const root = await mkdtemp(join(tmpdir(), "zaip-cost-title-"));
12
+ try {
13
+ const filesystem = new WorkspaceFileSystem(root);
14
+ await new LifecycleManifestStore(filesystem).replace([
15
+ {
16
+ schema_version: 1,
17
+ event_id: "task-title",
18
+ operation: "upsert",
19
+ occurred_at: "2026-08-09T12:00:00.000Z",
20
+ entity: {
21
+ kind: "task",
22
+ id: "TASK-1",
23
+ title: "Needle title",
24
+ status: "completed",
25
+ risk: "lite",
26
+ priority: null,
27
+ updated_at: "2026-08-09T12:00:00.000Z",
28
+ relations: [],
29
+ usage: null,
30
+ data: { created_at: "2026-08-09T12:00:00.000Z", finished_at: "2026-08-09T12:00:00.000Z" },
31
+ },
32
+ },
33
+ ]);
34
+ const metric = (id: string, task_id: string | null, hour: number): CompactRunMetric => ({
35
+ id,
36
+ task_id,
37
+ started_at: `2026-08-09T${hour}:00:00.000Z`,
38
+ finished_at: null,
39
+ execution_id: null,
40
+ conversation_id: null,
41
+ agent: null,
42
+ agent_name: null,
43
+ provider: null,
44
+ model: null,
45
+ spec_id: null,
46
+ prd_id: null,
47
+ change_id: null,
48
+ phase: "execution",
49
+ parent_run_id: null,
50
+ status: "completed",
51
+ duration_ms: null,
52
+ total_tokens: null,
53
+ cost: null,
54
+ currency: null,
55
+ events: null,
56
+ });
57
+ const summaryPath = ".agents/records/lifecycle/summary.json";
58
+ const summary = JSON.parse(await filesystem.readText(summaryPath));
59
+ summary.run_metrics = [
60
+ metric("RUN-UNRELATED", null, 15),
61
+ metric("RUN-SECOND", "TASK-1", 14),
62
+ metric("RUN-FIRST", "TASK-1", 13),
63
+ ];
64
+ await filesystem.writeTextAtomic(summaryPath, JSON.stringify(summary));
65
+ const app = new Hono();
66
+ app.get("/", async (context) =>
67
+ context.json(
68
+ await buildManifestCostRunPage(
69
+ filesystem,
70
+ { version: 2 } as WorkspaceLayout,
71
+ context,
72
+ { from: "2026-08-09", to: "2026-08-09" },
73
+ 1,
74
+ 1,
75
+ ),
76
+ ),
77
+ );
78
+ const response = await app.request("/?query=needle%20title&currency=USD");
79
+ expect(response.status).toBe(200);
80
+ expect(await response.json()).toMatchObject({
81
+ page_only: true,
82
+ run_page: { total: 2, offset: 1, limit: 1, has_more: false },
83
+ runs: [
84
+ { run_id: "RUN-FIRST", task_title: "Needle title", input_tokens: null, total_tokens: null },
85
+ ],
86
+ });
87
+ } finally {
88
+ await rm(root, { recursive: true, force: true });
89
+ }
90
+ });
@@ -0,0 +1,86 @@
1
+ import type { CostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/contracts";
2
+ import type { CompactRunMetric } from "./server-projections.js";
3
+
4
+ /** Match the complete compact population before either lifecycle or manifest pagination. */
5
+ export function filterCompactCostRuns(
6
+ runs: readonly CompactRunMetric[],
7
+ query: string,
8
+ tasks: readonly { id: string; title: string }[],
9
+ ): CompactRunMetric[] {
10
+ const needle = query.trim().toLowerCase();
11
+ if (!needle) return [...runs];
12
+ const titles = new Map(tasks.map((task) => [task.id, task.title]));
13
+ return runs.filter((run) =>
14
+ [
15
+ run.id,
16
+ run.task_id,
17
+ run.task_id === null ? null : titles.get(run.task_id),
18
+ run.provider,
19
+ run.agent,
20
+ run.agent_name,
21
+ run.model,
22
+ run.phase,
23
+ run.spec_id,
24
+ run.prd_id,
25
+ run.execution_id,
26
+ run.conversation_id,
27
+ ].some((value) => value?.toLowerCase().includes(needle)),
28
+ );
29
+ }
30
+
31
+ /** Keeps Cost Run transport incremental without changing aggregate semantics. */
32
+ export function pageCostRunRows(
33
+ projection: CostMonitorProjection,
34
+ offset: number,
35
+ limit: number,
36
+ ): CostMonitorProjection {
37
+ const safeOffset = Math.max(0, offset);
38
+ const safeLimit = Math.min(500, Math.max(1, limit));
39
+ return {
40
+ ...projection,
41
+ runs: projection.runs.slice(safeOffset, safeOffset + safeLimit),
42
+ run_page: {
43
+ offset: safeOffset,
44
+ limit: safeLimit,
45
+ total: projection.runs.length,
46
+ has_more: safeOffset + safeLimit < projection.runs.length,
47
+ },
48
+ };
49
+ }
50
+
51
+ /**
52
+ * The Runs endpoint advertises these dimensions as server-side filters. Apply
53
+ * them before offset pagination so the reported total and every page have the
54
+ * same population. Aggregate fields intentionally retain their full filtered
55
+ * projection semantics and are not derived from the transport page.
56
+ */
57
+ export function filterCostRunRows(
58
+ projection: CostMonitorProjection,
59
+ filters: {
60
+ provider: readonly string[];
61
+ model: readonly string[];
62
+ agent: readonly string[];
63
+ phase: readonly string[];
64
+ status: readonly string[];
65
+ },
66
+ ): CostMonitorProjection {
67
+ const includes = (values: readonly string[], value: string) =>
68
+ values.length === 0 || values.includes(value);
69
+ const provider = (value: string | null) => {
70
+ const normalized = value?.trim().toLowerCase();
71
+ return !normalized || normalized === "unknown" || normalized === "codex"
72
+ ? "openai"
73
+ : normalized;
74
+ };
75
+ return {
76
+ ...projection,
77
+ runs: projection.runs.filter(
78
+ (run) =>
79
+ includes(filters.provider, provider(run.provider)) &&
80
+ includes(filters.model, run.model) &&
81
+ includes(filters.agent, run.agent_name) &&
82
+ includes(filters.phase, run.phase) &&
83
+ includes(filters.status, run.task_status),
84
+ ),
85
+ };
86
+ }