@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.
- package/dist/services/adapter-hook-sources.d.ts +3 -0
- package/dist/services/adapter-hook-sources.d.ts.map +1 -1
- package/dist/services/adapter-hook-sources.js +3 -0
- package/dist/services/adapter-hook-sources.js.map +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.d.ts.map +1 -1
- package/dist/services/adapters.js +5 -2
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/adapters.test.js +1 -1
- package/dist/services/adapters.test.js.map +1 -1
- package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -1
- package/dist/services/codex-conversation-lifecycle-hook.js +16 -6
- package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -1
- package/dist/services/codex-hook-activation.d.ts +1 -1
- package/dist/services/codex-hook-activation.d.ts.map +1 -1
- package/dist/services/codex-hook-activation.js +116 -10
- package/dist/services/codex-hook-activation.js.map +1 -1
- package/dist/services/codex-pre-tool-cli-capture-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-cli-capture-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-cli-capture-template.js +104 -0
- package/dist/services/codex-pre-tool-cli-capture-template.js.map +1 -0
- package/dist/services/codex-pre-tool-cloud-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-cloud-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-cloud-template.js +190 -0
- package/dist/services/codex-pre-tool-cloud-template.js.map +1 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.d.ts +2 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.js +9 -0
- package/dist/services/codex-pre-tool-dependencies.test-support.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts +5 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.js +19 -0
- package/dist/services/codex-pre-tool-hook-fixture.test-support.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts +2 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js +107 -0
- package/dist/services/codex-pre-tool-hook-template-bootstrap.test.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +152 -190
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +63 -75
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-host-context-template.d.ts +2 -0
- package/dist/services/codex-pre-tool-host-context-template.d.ts.map +1 -0
- package/dist/services/codex-pre-tool-host-context-template.js +139 -0
- package/dist/services/codex-pre-tool-host-context-template.js.map +1 -0
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +77 -100
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
- package/dist/services/codex-subagent-hook-template.js +59 -15
- package/dist/services/codex-subagent-hook-template.js.map +1 -1
- package/dist/services/codex-validation-runtime-template.d.ts.map +1 -1
- package/dist/services/codex-validation-runtime-template.js +37 -10
- package/dist/services/codex-validation-runtime-template.js.map +1 -1
- package/dist/services/runtime-template-test-support.d.ts +1 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -1
- package/dist/services/runtime-template-test-support.js +23 -2
- package/dist/services/runtime-template-test-support.js.map +1 -1
- package/dist/services/runtime-template.test.js +7 -24
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README-ENG.md +49 -7
- package/templates/.agents/README.md +41 -10
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +125 -73
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BW7Vy1cp.js → CostCalculationGuidePage-w6pjt4OM.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BN8KQPrr.js → DashboardHelpDialog-DhlntQcR.js} +117 -17
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Prcs-ydy.js → PricingSettingsDialog-BHoZT2an.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-HqGUImIG.js → ProjectSettingsPage-IcncIZ9Y.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CNpmQMO4.js → TraceabilityWorkspace-C6rORjpr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-uPnOBShc.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-CF7B1Hjn.js → dashboard-localization-DWXjEIgc.js} +49 -7
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DOhy6D2-.js → icons-vendor-Hc9fp_gs.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{index-DX65Ufuu.js → index-BqeOEZW5.js} +2 -2
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CkPC-yMz.js → react-vendor-C9ta_O10.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-BEdGoIDm.js → traceability-IcefoTOv.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +10 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +5 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostGroupsTable.period-values.test.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables-keeps-legacy-codex-identity-fields-blank.test.ts +5 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +36 -43
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-loaders.ts +1 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-formatters.ts +6 -4
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +11 -25
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/cost-summary-defaults.ts +26 -0
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +11 -16
- package/templates/.agents/dashboard/modules/operations/src/planning-contracts.ts +14 -0
- package/templates/.agents/dashboard/modules/operations/src/react/DecisionTable.tsx +65 -0
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +5 -71
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +10 -36
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/react/use-operations-filter-sync.ts +66 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +229 -222
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +167 -167
- package/templates/.agents/dashboard/packages/local-runtime/package.json +3 -3
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +229 -222
- package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.test.ts +38 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/active-capture-failure.ts +35 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +69 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +62 -15
- package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +8 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-capture-projection-order.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.test.ts +90 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-cost-run-search.ts +86 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-routes.ts +8 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/server-projection-loaders.ts +13 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +30 -74
- package/templates/.agents/dashboard/packages/local-runtime/src/server-route-handlers.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-routes.test.ts +60 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +12 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +29 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +7 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +51 -54
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +37 -48
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-filter-matrix.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +49 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +44 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-cached-cost-page.ts +11 -10
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +18 -1
- package/templates/.agents/dashboard/packages/local-runtime/vitest.config.ts +12 -0
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +13 -6
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +1 -1
- package/templates/.agents/design/.zaip-managed.json +3 -3
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +39 -3
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +3 -3
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +11 -2
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +2 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/rules/production-readiness.md +24 -0
- package/templates/.agents/rules/run-lifecycle-closure.md +2 -1
- package/templates/.agents/rules/security.md +2 -2
- package/templates/.agents/rules/workflow.md +4 -3
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +1185 -610
- package/templates/.agents/runtime/checksums.sha256 +6 -6
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
- package/templates/.agents/runtime/provenance.json +5 -5
- package/templates/.agents/runtime/sbom.cdx.json +71 -71
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -2
- package/templates/.agents/skills/zaip-skill-execution/references/controlled-release-recovery.md +44 -0
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +1 -1
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +7 -1
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-gate-matrix.md +15 -20
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +38 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +62 -4
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +8 -6
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +5 -3
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +7 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/SKILL.md +23 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.mjs +213 -0
- package/templates/.agents/skills/zaip-skill-maintenance-enter/scripts/maintenance.test.mjs +207 -0
- package/templates/.agents/skills/zaip-skill-maintenance-exit/SKILL.md +21 -0
- package/templates/.agents/skills/zaip-skill-maintenance-exit/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +6 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +26 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +46 -11
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +9 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +15 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +22 -0
- package/templates/.agents/zaip-aios/guides/14-troubleshooting.md +1 -1
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +52 -5
- package/templates/template-manifest.json +210 -126
- 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
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts
CHANGED
|
@@ -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("
|
|
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
|
-
"
|
|
30
|
+
"bounded pending progress",
|
|
30
31
|
);
|
|
31
32
|
expect(execute.mock.calls[0]?.[2]).toMatchObject({
|
|
32
|
-
timeout:
|
|
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') ||
|
|
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("
|
|
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
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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):
|
|
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 {
|
|
147
|
-
|
|
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
|
-
|
|
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:
|
|
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¤cy=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
|
+
}
|