@zaip-ai/cli 4.5.0 → 4.7.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/README.md +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.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 +6 -6
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-config-overlay.d.ts.map +1 -1
- package/dist/services/codex-config-overlay.js +27 -10
- package/dist/services/codex-config-overlay.js.map +1 -1
- package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
- package/dist/services/codex-native-tool-authority-template.js +188 -9
- package/dist/services/codex-native-tool-authority-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +50 -128
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +1 -4
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
- package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
- package/dist/services/codex-stale-git-lock-template.js +64 -0
- package/dist/services/codex-stale-git-lock-template.js.map +1 -0
- package/dist/services/global-codex-config-migration.test.js +1 -4
- package/dist/services/global-codex-config-migration.test.js.map +1 -1
- package/dist/services/global-codex-config.d.ts.map +1 -1
- package/dist/services/global-codex-config.js +2 -1
- package/dist/services/global-codex-config.js.map +1 -1
- package/dist/services/global-codex-config.test.js +3 -1
- package/dist/services/global-codex-config.test.js.map +1 -1
- package/dist/services/hook-readiness.d.ts.map +1 -1
- package/dist/services/hook-readiness.js.map +1 -1
- package/dist/services/install-agents-template.js +2 -2
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/install-template-policy.d.ts.map +1 -1
- package/dist/services/install-template-policy.js +2 -0
- package/dist/services/install-template-policy.js.map +1 -1
- package/dist/services/logger.d.ts +1 -1
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +77 -0
- package/dist/services/logger.test.js.map +1 -1
- package/dist/services/runtime-template-integrity.test.d.ts +2 -0
- package/dist/services/runtime-template-integrity.test.d.ts.map +1 -0
- package/dist/services/runtime-template-integrity.test.js +26 -0
- package/dist/services/runtime-template-integrity.test.js.map +1 -0
- package/dist/services/runtime-template-test-support.d.ts +3 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -0
- package/dist/services/runtime-template-test-support.js +32 -0
- package/dist/services/runtime-template-test-support.js.map +1 -0
- package/dist/services/runtime-template.test.js +37 -48
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README.md +8 -0
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/config/deploy-policy.json +5 -0
- package/templates/.agents/dashboard/.zaip-managed.json +235 -83
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-OUjdwEcM.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-Cei1ChjE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-_woNCrTT.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-B2_7MVOP.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-C_jCsSZe.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C8zQDBT7.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-iMPXot7Y.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DiCwz0sr.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-721Re3Ss.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-BjdTkWS0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +6 -6
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/overview-duration-hint.ts +14 -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/DIAGNOSTICS.md +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +285 -205
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-summary-elapsed.ts +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +41 -54
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +7 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +5 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +4 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +13 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +9 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +10 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed-view.ts +61 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed.test.ts +210 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
- package/templates/.agents/design/.zaip-managed.json +17 -5
- package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
- package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- 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 +1 -1
- 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 +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +21 -6
- package/templates/.agents/requirements/public-distribution.md +4 -4
- 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 +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/core.md +3 -1
- package/templates/.agents/rules/workflow.md +3 -2
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +368 -199
- package/templates/.agents/runtime/checksums.sha256 +5 -5
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/provenance.json +4 -4
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
- package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +38 -48
- package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +307 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +337 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +46 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +100 -44
- package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
- package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
- package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
- package/templates/template-manifest.json +354 -150
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { prepareDashboardSqliteStartup } from "./sqlite-dashboard-startup.js";
|
|
6
|
+
import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
7
|
+
import { sqliteReadModelPath, validateSqliteReadModelSchema } from "./sqlite-read-model-schema.js";
|
|
8
|
+
import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
|
|
9
|
+
|
|
10
|
+
vi.mock("./sqlite-read-model-source-fingerprint.js", async (importOriginal) => {
|
|
11
|
+
const actual = await importOriginal<typeof import("./sqlite-read-model-source-fingerprint.js")>();
|
|
12
|
+
return { ...actual, sourceFingerprint: vi.fn(actual.sourceFingerprint) };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const roots: string[] = [];
|
|
16
|
+
const taskContent = (title: string) =>
|
|
17
|
+
`---\nid: TASK-STARTUP\ntitle: ${title}\nstatus: ready\nrisk: standard\ncreated_at: 2026-08-01T12:00:00.000Z\n---\n`;
|
|
18
|
+
|
|
19
|
+
async function fixture(seed = true) {
|
|
20
|
+
vi.stubEnv("ZAIP_SQLITE_DB_PATH", undefined);
|
|
21
|
+
const fixtures = resolve(import.meta.dirname, "../../../../../.local/sqlite/tests");
|
|
22
|
+
await mkdir(fixtures, { recursive: true });
|
|
23
|
+
const root = await mkdtemp(resolve(fixtures, "startup-"));
|
|
24
|
+
roots.push(root);
|
|
25
|
+
const task = resolve(root, ".agents/records/tasks/task.md");
|
|
26
|
+
await mkdir(dirname(task), { recursive: true });
|
|
27
|
+
await mkdir(resolve(root, ".agents/config"), { recursive: true });
|
|
28
|
+
await writeFile(resolve(root, ".agents/config/project.yaml"), "zaip:\n enabled: true\n");
|
|
29
|
+
await writeFile(task, taskContent("Original seed"));
|
|
30
|
+
if (seed) await rebuildSqliteReadModel(root);
|
|
31
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
32
|
+
return { root, task, path: sqliteReadModelPath(root) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function editDatabase(path: string, sql: string) {
|
|
36
|
+
const database = new DatabaseSync(path);
|
|
37
|
+
try {
|
|
38
|
+
database.exec(sql);
|
|
39
|
+
} finally {
|
|
40
|
+
database.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function persistedTask(path: string): string {
|
|
45
|
+
const database = new DatabaseSync(path, { readOnly: true });
|
|
46
|
+
try {
|
|
47
|
+
return (
|
|
48
|
+
database
|
|
49
|
+
.prepare("SELECT content FROM zaip_vw_files WHERE path=?")
|
|
50
|
+
.get(".agents/records/tasks/task.md") as { content: string }
|
|
51
|
+
).content;
|
|
52
|
+
} finally {
|
|
53
|
+
database.close();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
afterEach(async () => {
|
|
58
|
+
vi.unstubAllEnvs();
|
|
59
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
60
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("dashboard SQLite startup", () => {
|
|
64
|
+
it("serves a validated stale seed without source inventory even after canonical changes", async () => {
|
|
65
|
+
const { root, task, path } = await fixture();
|
|
66
|
+
await writeFile(task, taskContent("Changed canonically"));
|
|
67
|
+
const before = await readFile(path);
|
|
68
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
69
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
70
|
+
path,
|
|
71
|
+
files: 2,
|
|
72
|
+
cache: "stale_seed",
|
|
73
|
+
fingerprint_ms: 0,
|
|
74
|
+
materialize_ms: 0,
|
|
75
|
+
refresh: { processed_sources: 0, reused_sources: 0, canonical_runs_refolded: 0 },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
expect(sourceFingerprint).not.toHaveBeenCalled();
|
|
79
|
+
expect(await readFile(path)).toEqual(before);
|
|
80
|
+
expect(persistedTask(path)).toContain("Original seed");
|
|
81
|
+
// The normal refresh remains canonical and publishes the changed source.
|
|
82
|
+
await rebuildSqliteReadModel(root);
|
|
83
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
84
|
+
expect(persistedTask(path)).toContain("Changed canonically");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it.each([
|
|
88
|
+
[
|
|
89
|
+
"schema",
|
|
90
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','old') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='schema_version'",
|
|
91
|
+
],
|
|
92
|
+
["index", "DROP INDEX zaip_activities_work_item_identity"],
|
|
93
|
+
[
|
|
94
|
+
"missing first fold",
|
|
95
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type='first_fold_all_time_cache'",
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"truncated board",
|
|
99
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.pages.task',json('[]')) WHERE payload_type='first_fold_all_time_board_cache'",
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"malformed board",
|
|
103
|
+
"UPDATE zaip_aios_activities SET payload_json='invalid' WHERE payload_type='first_fold_all_time_board_cache'",
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"malformed cost summary",
|
|
107
|
+
"UPDATE zaip_aios_activities SET payload_json='invalid' WHERE payload_type='cost_monitor_summary_cache'",
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"invalid file count",
|
|
111
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','NaN') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='source_file_count'",
|
|
112
|
+
],
|
|
113
|
+
])("rejects %s and rebuilds changed canonical sources", async (_damage, sql) => {
|
|
114
|
+
const { root, task, path } = await fixture();
|
|
115
|
+
editDatabase(path, sql);
|
|
116
|
+
await writeFile(task, taskContent("Recovered canonical source"));
|
|
117
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
118
|
+
path,
|
|
119
|
+
cache: "rebuilt",
|
|
120
|
+
});
|
|
121
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
122
|
+
expect(() => validateSqliteReadModelSchema(path)).not.toThrow();
|
|
123
|
+
expect(persistedTask(path)).toContain("Recovered canonical source");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("builds canonical data when the seed is absent", async () => {
|
|
127
|
+
const { root, path } = await fixture(false);
|
|
128
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
129
|
+
path,
|
|
130
|
+
cache: "rebuilt",
|
|
131
|
+
});
|
|
132
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
133
|
+
expect(() => validateSqliteReadModelSchema(path)).not.toThrow();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("accepts a genuinely empty committed model without fabricating source work", async () => {
|
|
137
|
+
const { root, task, path } = await fixture(false);
|
|
138
|
+
await rm(task);
|
|
139
|
+
await rm(resolve(root, ".agents/config/project.yaml"));
|
|
140
|
+
await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 0 });
|
|
141
|
+
vi.mocked(sourceFingerprint).mockClear();
|
|
142
|
+
await expect(prepareDashboardSqliteStartup(root)).resolves.toMatchObject({
|
|
143
|
+
path,
|
|
144
|
+
files: 0,
|
|
145
|
+
cache: "stale_seed",
|
|
146
|
+
fingerprint_ms: 0,
|
|
147
|
+
});
|
|
148
|
+
expect(sourceFingerprint).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("honors an explicit forced canonical rebuild", async () => {
|
|
152
|
+
const { root, task, path } = await fixture();
|
|
153
|
+
await writeFile(task, taskContent("Forced canonical source"));
|
|
154
|
+
await expect(
|
|
155
|
+
prepareDashboardSqliteStartup(root, { forceCanonical: true }),
|
|
156
|
+
).resolves.toMatchObject({
|
|
157
|
+
path,
|
|
158
|
+
cache: "rebuilt",
|
|
159
|
+
});
|
|
160
|
+
expect(sourceFingerprint).toHaveBeenCalled();
|
|
161
|
+
expect(persistedTask(path)).toContain("Forced canonical source");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
3
|
+
import {
|
|
4
|
+
hasReusableSnapshotPayloads,
|
|
5
|
+
sqliteReadModelPath,
|
|
6
|
+
validateSqliteReadModelSchema,
|
|
7
|
+
} from "./sqlite-read-model-schema.js";
|
|
8
|
+
import type { SqliteRebuildOptions } from "./sqlite-rebuild-fingerprint-options.js";
|
|
9
|
+
import type { SqliteRebuildResult } from "./sqlite-rebuild-result.js";
|
|
10
|
+
|
|
11
|
+
export type SqliteDashboardStartupOptions = Pick<
|
|
12
|
+
SqliteRebuildOptions,
|
|
13
|
+
"forceCanonical" | "sourceRoots"
|
|
14
|
+
>;
|
|
15
|
+
|
|
16
|
+
function hasReadableFirstFoldPayloads(database: DatabaseSync): boolean {
|
|
17
|
+
const rows = database
|
|
18
|
+
.prepare(`SELECT payload_json FROM zaip_aios_activities
|
|
19
|
+
WHERE payload_type IN ('first_fold_viewport_cache', 'first_fold_all_time_cache',
|
|
20
|
+
'first_fold_all_time_board_cache', 'cost_monitor_summary_cache',
|
|
21
|
+
'cost_monitor_summary_month_cache', 'first_fold_monthly_quick_cache')`)
|
|
22
|
+
.all() as Array<{ payload_json: string }>;
|
|
23
|
+
return rows.every(({ payload_json }) => {
|
|
24
|
+
const payload: unknown = JSON.parse(payload_json);
|
|
25
|
+
return payload !== null && typeof payload === "object" && !Array.isArray(payload);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Startup validates only the committed database. Canonical source discovery
|
|
31
|
+
* belongs to the asynchronous refresh after the server starts listening.
|
|
32
|
+
* Even an unchanged seed is stale until that refresh proves its freshness.
|
|
33
|
+
*/
|
|
34
|
+
export async function prepareDashboardSqliteStartup(
|
|
35
|
+
workspaceRoot: string,
|
|
36
|
+
options: SqliteDashboardStartupOptions = {},
|
|
37
|
+
): Promise<SqliteRebuildResult> {
|
|
38
|
+
const path = sqliteReadModelPath(workspaceRoot);
|
|
39
|
+
if (!options.forceCanonical) {
|
|
40
|
+
try {
|
|
41
|
+
validateSqliteReadModelSchema(path);
|
|
42
|
+
const database = new DatabaseSync(path, { readOnly: true });
|
|
43
|
+
try {
|
|
44
|
+
if (hasReusableSnapshotPayloads(database) && hasReadableFirstFoldPayloads(database)) {
|
|
45
|
+
const row = database
|
|
46
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='source_file_count'")
|
|
47
|
+
.get() as { value?: unknown } | undefined;
|
|
48
|
+
const files =
|
|
49
|
+
typeof row?.value === "string" && /^\d+$/.test(row.value)
|
|
50
|
+
? Number(row.value)
|
|
51
|
+
: Number.NaN;
|
|
52
|
+
if (Number.isSafeInteger(files) && files >= 0) {
|
|
53
|
+
return {
|
|
54
|
+
files,
|
|
55
|
+
path,
|
|
56
|
+
cache: "stale_seed",
|
|
57
|
+
fingerprint_ms: 0,
|
|
58
|
+
materialize_ms: 0,
|
|
59
|
+
refresh: {
|
|
60
|
+
reused_sources: 0,
|
|
61
|
+
processed_sources: 0,
|
|
62
|
+
added_sources: 0,
|
|
63
|
+
changed_sources: 0,
|
|
64
|
+
removed_sources: 0,
|
|
65
|
+
canonical_runs_refolded: 0,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
database.close();
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// Missing, damaged, incompatible or incomplete databases are never served.
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return rebuildSqliteReadModel(workspaceRoot, { ...options, preferExisting: false });
|
|
78
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
|
|
5
|
+
import { prepareMultiProviderFixture } from "./sqlite-read-model-multi-provider-fixture.js";
|
|
6
|
+
|
|
7
|
+
it("status-only reconciliation promotes the snapshot without faking a new token collection time", async () => {
|
|
8
|
+
const base = resolve(import.meta.dirname, "../../../../../../.tmp/dashboard-header-time");
|
|
9
|
+
await mkdir(base, { recursive: true });
|
|
10
|
+
const root = await mkdtemp(join(base, "fixture-"));
|
|
11
|
+
let filesystem: SqliteReadModelFileSystem | undefined;
|
|
12
|
+
try {
|
|
13
|
+
await prepareMultiProviderFixture(root);
|
|
14
|
+
await rebuildSqliteReadModel(root);
|
|
15
|
+
filesystem = new SqliteReadModelFileSystem(root);
|
|
16
|
+
const collected = filesystem.latestTokenCollectedAt();
|
|
17
|
+
const snapshot = filesystem.projectionUpdatedAt();
|
|
18
|
+
expect(collected).toBe("2026-08-04T16:00:01.000Z");
|
|
19
|
+
await mkdir(join(root, ".agents/records/tasks"), { recursive: true });
|
|
20
|
+
await writeFile(
|
|
21
|
+
join(root, ".agents/records/tasks/TASK-STATUS.md"),
|
|
22
|
+
"---\nschema_version: 2\nid: TASK-STATUS\ntitle: Status only\nstatus: blocked\ncreated_at: 2026-09-29T10:00:00Z\n---\n# Status\n",
|
|
23
|
+
);
|
|
24
|
+
await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(root));
|
|
25
|
+
expect(filesystem.latestTokenCollectedAt()).toBe(collected);
|
|
26
|
+
expect(filesystem.projectionUpdatedAt()).not.toBe(snapshot);
|
|
27
|
+
} finally {
|
|
28
|
+
filesystem?.close();
|
|
29
|
+
await rm(root, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { hasVisibleProviderMetrics, providerVisibleMetrics } from "./sqlite-provider-visibility.js";
|
|
4
|
+
import { readCachedCostViewPage } from "./sqlite-query-cached-cost-page.js";
|
|
5
|
+
import type { SqliteQueryContext } from "./sqlite-query-context.js";
|
|
6
|
+
import { queryUncachedViewPage } from "./sqlite-query-view-page.js";
|
|
7
|
+
|
|
8
|
+
const databases: DatabaseSync[] = [];
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
while (databases.length) databases.pop()?.close();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function fixture() {
|
|
14
|
+
const database = new DatabaseSync(":memory:");
|
|
15
|
+
databases.push(database);
|
|
16
|
+
database.exec(`
|
|
17
|
+
CREATE TABLE invocations (
|
|
18
|
+
agent_invocation_id TEXT, task_id TEXT, duration_ms REAL, source_event_count REAL,
|
|
19
|
+
input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
|
|
20
|
+
total_tokens REAL, cost_usd REAL, cost_brl REAL, source_started_at TEXT,
|
|
21
|
+
source_occurred_at TEXT, occurred_at TEXT, zaip_agent TEXT, phase TEXT,
|
|
22
|
+
status TEXT, work_type TEXT, prd_id TEXT, spec_id TEXT
|
|
23
|
+
);
|
|
24
|
+
CREATE TABLE tasks (id TEXT, status TEXT, priority TEXT, risk TEXT);
|
|
25
|
+
CREATE TABLE cost_invocation_dimension_facts (
|
|
26
|
+
agent_invocation_id TEXT, provider TEXT, model TEXT, input_tokens REAL,
|
|
27
|
+
cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
|
|
28
|
+
total_tokens REAL, cost_usd REAL, cost_brl REAL, occurred_day_sp TEXT
|
|
29
|
+
);
|
|
30
|
+
CREATE TABLE zaip_aios_activities (
|
|
31
|
+
payload_type TEXT, payload_json TEXT, activity_source_kind TEXT,
|
|
32
|
+
agent_invocation_id TEXT, task_id TEXT, duration_ms REAL, source_event_count REAL,
|
|
33
|
+
input_tokens REAL, cached_input_tokens REAL, output_tokens REAL, reasoning_tokens REAL,
|
|
34
|
+
total_tokens REAL, cost_usd REAL, cost_brl REAL, occurred_day_sp TEXT
|
|
35
|
+
);
|
|
36
|
+
`);
|
|
37
|
+
const context = {
|
|
38
|
+
database,
|
|
39
|
+
technicalPayloadRows: () => [],
|
|
40
|
+
} as unknown as SqliteQueryContext;
|
|
41
|
+
const payload = (row: Record<string, unknown>) =>
|
|
42
|
+
database
|
|
43
|
+
.prepare("INSERT INTO zaip_aios_activities(payload_type,payload_json) VALUES (?,?)")
|
|
44
|
+
.run("cost_dimension_aggregate_v1", JSON.stringify({ dimension: "provider", ...row }));
|
|
45
|
+
const invocation = (id: string, tokens: number, duration: number | null = null) => {
|
|
46
|
+
database
|
|
47
|
+
.prepare(`INSERT INTO invocations
|
|
48
|
+
(agent_invocation_id,total_tokens,cost_usd,cost_brl,duration_ms,source_event_count,
|
|
49
|
+
source_started_at,phase) VALUES (?,?,0,0,?,9,'2026-09-29T12:00:00Z','execution')`)
|
|
50
|
+
.run(id, tokens, duration);
|
|
51
|
+
database
|
|
52
|
+
.prepare(`INSERT INTO zaip_aios_activities
|
|
53
|
+
(activity_source_kind,agent_invocation_id,total_tokens,cost_usd,cost_brl,
|
|
54
|
+
duration_ms,source_event_count,occurred_day_sp) VALUES ('agent_run',?,?,0,0,?,9,'2026-09-29')`)
|
|
55
|
+
.run(id, tokens, duration);
|
|
56
|
+
};
|
|
57
|
+
const segment = (id: string, provider: string | null, tokens: number) =>
|
|
58
|
+
database
|
|
59
|
+
.prepare(`INSERT INTO cost_invocation_dimension_facts
|
|
60
|
+
(agent_invocation_id,provider,model,total_tokens,cost_usd,cost_brl,occurred_day_sp)
|
|
61
|
+
VALUES (?,?,'model',?,0,0,'2026-09-29')`)
|
|
62
|
+
.run(id, provider, tokens);
|
|
63
|
+
return { database, context, payload, invocation, segment };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const empty = {
|
|
67
|
+
invocation_count: 0,
|
|
68
|
+
task_count: 0,
|
|
69
|
+
total_duration: null,
|
|
70
|
+
total_tokens: 0,
|
|
71
|
+
total_cost_usd: 0,
|
|
72
|
+
total_cost_brl: null,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe("provider visibility", () => {
|
|
76
|
+
it.each(
|
|
77
|
+
providerVisibleMetrics,
|
|
78
|
+
)("retains activity in %s independently of cost or invocation", (field) => {
|
|
79
|
+
const { context, payload } = fixture();
|
|
80
|
+
payload({ key: "unavailable", ...empty });
|
|
81
|
+
payload({ key: "active", ...empty, [field]: 1 });
|
|
82
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", {});
|
|
83
|
+
expect(page.rows.map((row) => row.key)).toEqual(["active"]);
|
|
84
|
+
expect(page.total_count).toBe(1);
|
|
85
|
+
expect(page.rows[0]?.[field]).toBe(1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("ignores unavailable/zero metrics and hidden metrics, retaining negative adjustments", () => {
|
|
89
|
+
expect(hasVisibleProviderMetrics({ ...empty, source_event_count: 9 })).toBe(false);
|
|
90
|
+
expect(hasVisibleProviderMetrics({})).toBe(false);
|
|
91
|
+
expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: -1 })).toBe(true);
|
|
92
|
+
expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: 0.0049 })).toBe(false);
|
|
93
|
+
expect(hasVisibleProviderMetrics({ ...empty, total_cost_usd: 0.005 })).toBe(true);
|
|
94
|
+
expect(hasVisibleProviderMetrics({ ...empty, total_cost_brl: -0.005 })).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("hides sub-cent residual costs from payload while preserving raw financial totals", () => {
|
|
98
|
+
const { context, payload } = fixture();
|
|
99
|
+
const residual = {
|
|
100
|
+
total_cost_usd: 0.00004640000001435857,
|
|
101
|
+
total_cost_brl: 0.000010780000006496704,
|
|
102
|
+
};
|
|
103
|
+
payload({ key: "unavailable", ...empty, ...residual });
|
|
104
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", {});
|
|
105
|
+
expect(page).toMatchObject({ total_count: 0, rows: [], has_more: false });
|
|
106
|
+
expect(page.totals).toMatchObject(residual);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it.each([
|
|
110
|
+
{ phase: "execution" },
|
|
111
|
+
{ from: "2026-09-29" },
|
|
112
|
+
])("hides SQL residual-cost-only rows at displayed precision: %j", (input) => {
|
|
113
|
+
const { context, invocation, segment, database } = fixture();
|
|
114
|
+
invocation("residual", 0);
|
|
115
|
+
segment("residual", null, 0);
|
|
116
|
+
database.exec(`
|
|
117
|
+
UPDATE invocations SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
|
|
118
|
+
UPDATE zaip_aios_activities SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
|
|
119
|
+
UPDATE cost_invocation_dimension_facts SET cost_usd=0.00004640000001435857,cost_brl=0.000010780000006496704;
|
|
120
|
+
`);
|
|
121
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", input);
|
|
122
|
+
expect(page).toMatchObject({ total_count: 0, rows: [], has_more: false });
|
|
123
|
+
expect(page.totals).toMatchObject({
|
|
124
|
+
total_cost_usd: 0.00004640000001435857,
|
|
125
|
+
total_cost_brl: 0.000010780000006496704,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("filters payload before pagination without changing factual totals or null coverage", () => {
|
|
130
|
+
const { context, payload } = fixture();
|
|
131
|
+
payload({ key: "unavailable", ...empty, source_event_count: 9 });
|
|
132
|
+
payload({ key: "known-empty", ...empty });
|
|
133
|
+
payload({ key: "active", ...empty, total_tokens: 7 });
|
|
134
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", { limit: 1 });
|
|
135
|
+
expect(page).toMatchObject({ total_count: 1, has_more: false });
|
|
136
|
+
expect(page.rows).toEqual([
|
|
137
|
+
expect.objectContaining({ key: "active", total_duration: null, total_cost_brl: null }),
|
|
138
|
+
]);
|
|
139
|
+
expect(page.totals).toMatchObject({
|
|
140
|
+
total_tokens: 7,
|
|
141
|
+
total_cost_usd: 0,
|
|
142
|
+
total_cost_brl: null,
|
|
143
|
+
total_duration: null,
|
|
144
|
+
source_event_count: 9,
|
|
145
|
+
});
|
|
146
|
+
const next = queryUncachedViewPage(context, "cost_monitor_providers", { limit: 1, offset: 1 });
|
|
147
|
+
expect(next).toMatchObject({ total_count: 1, rows: [], has_more: false });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("retains genuine unattributed payload activity and returns a coherent empty page", () => {
|
|
151
|
+
const { context, payload } = fixture();
|
|
152
|
+
payload({ key: "unavailable", ...empty });
|
|
153
|
+
expect(queryUncachedViewPage(context, "cost_monitor_providers", {})).toMatchObject({
|
|
154
|
+
total_count: 0,
|
|
155
|
+
rows: [],
|
|
156
|
+
has_more: false,
|
|
157
|
+
});
|
|
158
|
+
payload({ key: null, ...empty, total_tokens: 3 });
|
|
159
|
+
expect(queryUncachedViewPage(context, "cost_monitor_providers", {}).rows).toEqual([
|
|
160
|
+
expect.objectContaining({ key: null, total_tokens: 3 }),
|
|
161
|
+
]);
|
|
162
|
+
expect(readCachedCostViewPage(context, "cost_monitor_providers", {})).toBeNull();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it.each([
|
|
166
|
+
{ phase: "execution" },
|
|
167
|
+
{ from: "2026-09-29", to: "2026-09-29" },
|
|
168
|
+
])("filters SQL before count and paging for %j while retaining factual populations", (input) => {
|
|
169
|
+
const { context, invocation, segment, database } = fixture();
|
|
170
|
+
invocation("mixed", 10, 100);
|
|
171
|
+
segment("mixed", "openai", 10);
|
|
172
|
+
segment("mixed", "empty-known", 0);
|
|
173
|
+
segment("mixed", null, 0);
|
|
174
|
+
invocation("unattributed", 7);
|
|
175
|
+
segment("unattributed", null, 7);
|
|
176
|
+
invocation("free", 0);
|
|
177
|
+
segment("free", "free-provider", 0);
|
|
178
|
+
const factsBefore = database.prepare("SELECT * FROM cost_invocation_dimension_facts").all();
|
|
179
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", { ...input, limit: 2 });
|
|
180
|
+
expect(page).toMatchObject({ total_count: 3, has_more: true });
|
|
181
|
+
expect(page.rows.map((row) => row.key)).toEqual(["free-provider", "openai"]);
|
|
182
|
+
expect(page.rows[0]).toMatchObject({ invocation_count: 1, total_tokens: 0, total_cost_usd: 0 });
|
|
183
|
+
expect(page.rows[1]).toMatchObject({
|
|
184
|
+
invocation_count: 0,
|
|
185
|
+
total_tokens: 10,
|
|
186
|
+
total_cost_brl: 0,
|
|
187
|
+
total_duration: null,
|
|
188
|
+
});
|
|
189
|
+
expect(page.totals).toMatchObject({
|
|
190
|
+
invocation_count: 3,
|
|
191
|
+
total_tokens: 17,
|
|
192
|
+
total_duration: 100,
|
|
193
|
+
total_cost_usd: 0,
|
|
194
|
+
source_event_count: 27,
|
|
195
|
+
});
|
|
196
|
+
const next = queryUncachedViewPage(context, "cost_monitor_providers", {
|
|
197
|
+
...input,
|
|
198
|
+
limit: 2,
|
|
199
|
+
offset: 2,
|
|
200
|
+
});
|
|
201
|
+
expect(next).toMatchObject({ total_count: 3, has_more: false });
|
|
202
|
+
expect(next.rows).toEqual([
|
|
203
|
+
expect.objectContaining({ key: "unavailable", invocation_count: 0, total_tokens: 7 }),
|
|
204
|
+
]);
|
|
205
|
+
expect(next.totals).toEqual(page.totals);
|
|
206
|
+
expect(database.prepare("SELECT * FROM cost_invocation_dimension_facts").all()).toEqual(
|
|
207
|
+
factsBefore,
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it.each([
|
|
212
|
+
{ phase: "execution" },
|
|
213
|
+
{ from: "2026-09-29" },
|
|
214
|
+
])("returns empty SQL page for only zero/null rows: %j", (input) => {
|
|
215
|
+
const { context, invocation, segment } = fixture();
|
|
216
|
+
invocation("zero", 0);
|
|
217
|
+
segment("zero", null, 0);
|
|
218
|
+
expect(queryUncachedViewPage(context, "cost_monitor_providers", input)).toMatchObject({
|
|
219
|
+
total_count: 0,
|
|
220
|
+
rows: [],
|
|
221
|
+
has_more: false,
|
|
222
|
+
totals: { invocation_count: 1, total_tokens: 0, total_cost_usd: 0 },
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it.each([
|
|
227
|
+
{ phase: "execution" },
|
|
228
|
+
{ from: "2026-09-29" },
|
|
229
|
+
])("retains zero-cost task-only and meaningful unattributed cost in SQL: %j", (input) => {
|
|
230
|
+
const { context, invocation, segment, database } = fixture();
|
|
231
|
+
invocation("task-only", 0);
|
|
232
|
+
segment("task-only", "provider-a", 0);
|
|
233
|
+
segment("task-only", "provider-b", 0);
|
|
234
|
+
database.exec(
|
|
235
|
+
"UPDATE invocations SET task_id='TASK-1'; UPDATE zaip_aios_activities SET task_id='TASK-1'",
|
|
236
|
+
);
|
|
237
|
+
invocation("cost-only", 0);
|
|
238
|
+
segment("cost-only", null, 0);
|
|
239
|
+
database.exec(`
|
|
240
|
+
UPDATE invocations SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
|
|
241
|
+
UPDATE zaip_aios_activities SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
|
|
242
|
+
UPDATE cost_invocation_dimension_facts SET cost_usd=0.005 WHERE agent_invocation_id='cost-only';
|
|
243
|
+
`);
|
|
244
|
+
const page = queryUncachedViewPage(context, "cost_monitor_providers", input);
|
|
245
|
+
expect(page.total_count).toBe(3);
|
|
246
|
+
expect(page.rows).toEqual([
|
|
247
|
+
expect.objectContaining({
|
|
248
|
+
key: "provider-a",
|
|
249
|
+
invocation_count: 0,
|
|
250
|
+
task_count: 1,
|
|
251
|
+
total_tokens: 0,
|
|
252
|
+
total_cost_usd: 0,
|
|
253
|
+
}),
|
|
254
|
+
expect.objectContaining({
|
|
255
|
+
key: "provider-b",
|
|
256
|
+
invocation_count: 0,
|
|
257
|
+
task_count: 1,
|
|
258
|
+
total_tokens: 0,
|
|
259
|
+
total_cost_usd: 0,
|
|
260
|
+
}),
|
|
261
|
+
expect.objectContaining({
|
|
262
|
+
key: "unavailable",
|
|
263
|
+
invocation_count: 0,
|
|
264
|
+
task_count: 0,
|
|
265
|
+
total_tokens: 0,
|
|
266
|
+
total_cost_usd: 0.005,
|
|
267
|
+
}),
|
|
268
|
+
]);
|
|
269
|
+
expect(page.totals).toMatchObject({ task_count: 1, total_cost_usd: 0.005 });
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Only metrics displayed in Provedores determine whether a row has activity. */
|
|
2
|
+
export const providerVisibleMetrics = [
|
|
3
|
+
"invocation_count",
|
|
4
|
+
"task_count",
|
|
5
|
+
"total_duration",
|
|
6
|
+
"total_tokens",
|
|
7
|
+
"total_cost_usd",
|
|
8
|
+
"total_cost_brl",
|
|
9
|
+
] as const;
|
|
10
|
+
|
|
11
|
+
// Costs are displayed with two decimal places; sub-cent reconciliation
|
|
12
|
+
// remainders remain factual totals but do not constitute visible activity.
|
|
13
|
+
const minimumVisibleCost = 0.005;
|
|
14
|
+
|
|
15
|
+
export function hasVisibleProviderMetrics(row: Record<string, unknown>): boolean {
|
|
16
|
+
return providerVisibleMetrics.some(
|
|
17
|
+
(field) =>
|
|
18
|
+
typeof row[field] === "number" &&
|
|
19
|
+
(field === "total_cost_usd" || field === "total_cost_brl"
|
|
20
|
+
? Math.abs(row[field]) >= minimumVisibleCost
|
|
21
|
+
: row[field] !== 0),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Applied to aggregated rows, before COUNT, LIMIT and OFFSET. */
|
|
26
|
+
export const providerVisibleMetricsSql = providerVisibleMetrics
|
|
27
|
+
.map((field) =>
|
|
28
|
+
field === "total_cost_usd" || field === "total_cost_brl"
|
|
29
|
+
? `ABS(COALESCE(${field}, 0)) >= ${minimumVisibleCost}`
|
|
30
|
+
: `COALESCE(${field}, 0) <> 0`,
|
|
31
|
+
)
|
|
32
|
+
.join(" OR ");
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { SqliteViewPageInput } from "./sqlite-query-cached-cost-page.js";
|
|
2
|
+
import type { SqliteQueryContext } from "./sqlite-query-context.js";
|
|
3
|
+
import type { SqliteViewName } from "./sqlite-read-model-types.js";
|
|
4
|
+
|
|
5
|
+
/** Recompose coarse period rows from daily facts when a date range cuts through them. */
|
|
6
|
+
export function attachOperationsDuration(
|
|
7
|
+
context: SqliteQueryContext,
|
|
8
|
+
view: SqliteViewName,
|
|
9
|
+
input: SqliteViewPageInput,
|
|
10
|
+
rows: Array<Record<string, unknown>>,
|
|
11
|
+
): void {
|
|
12
|
+
if (!["operations_daily", "operations_monthly", "operations_annual"].includes(view)) return;
|
|
13
|
+
const partialCoarse = view !== "operations_daily" && (input.from || input.to);
|
|
14
|
+
const partialFromDay = input.from?.slice(0, 10) ?? null;
|
|
15
|
+
const partialToDay = input.to?.slice(0, 10) ?? null;
|
|
16
|
+
const dailyRows = partialCoarse
|
|
17
|
+
? (context.database
|
|
18
|
+
.prepare(
|
|
19
|
+
`SELECT * FROM operations_daily WHERE (? IS NULL OR day >= ?) AND (? IS NULL OR day <= ?)`,
|
|
20
|
+
)
|
|
21
|
+
.all(partialFromDay, partialFromDay, partialToDay, partialToDay) as Array<
|
|
22
|
+
Record<string, unknown>
|
|
23
|
+
>)
|
|
24
|
+
: [];
|
|
25
|
+
const daily = context.executionDurationByDay({
|
|
26
|
+
from: input.from ?? null,
|
|
27
|
+
to: input.to ?? null,
|
|
28
|
+
});
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const key = String(row.day ?? row.period_key);
|
|
31
|
+
if (partialCoarse) {
|
|
32
|
+
const matching = dailyRows.filter((day) => String(day.day).startsWith(key));
|
|
33
|
+
const matchingDays = matching.map((day) => String(day.day)).sort();
|
|
34
|
+
row.start_day = matchingDays[0] ?? null;
|
|
35
|
+
row.end_day = matchingDays.at(-1) ?? null;
|
|
36
|
+
for (const column of [
|
|
37
|
+
"prd_count",
|
|
38
|
+
"spec_count",
|
|
39
|
+
"task_count",
|
|
40
|
+
"validation_count",
|
|
41
|
+
"evidence_count",
|
|
42
|
+
"total_run_count",
|
|
43
|
+
"invocation_count",
|
|
44
|
+
"llm_call_count",
|
|
45
|
+
"total_tokens",
|
|
46
|
+
"total_cost_usd",
|
|
47
|
+
"total_cost_brl",
|
|
48
|
+
] as const)
|
|
49
|
+
row[column] = matching.reduce(
|
|
50
|
+
(sum, day) => sum + (typeof day[column] === "number" ? Number(day[column]) : 0),
|
|
51
|
+
0,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
row.operations_total_duration = [...daily]
|
|
55
|
+
.filter(([day]) => (view === "operations_daily" ? day === key : day.startsWith(key)))
|
|
56
|
+
.reduce((sum, [, duration]) => sum + duration, 0);
|
|
57
|
+
}
|
|
58
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { costDimensionFactDateFilter } from "./sqlite-cost-agent-page.js";
|
|
2
|
+
import { providerVisibleMetricsSql } from "./sqlite-provider-visibility.js";
|
|
2
3
|
import type { SqliteQueryContext, SqliteViewInput } from "./sqlite-query-context.js";
|
|
3
4
|
import type { SqliteViewName } from "./sqlite-read-model-types.js";
|
|
4
5
|
export function readProviderDimension(
|
|
@@ -131,13 +132,21 @@ export function readProviderDimension(
|
|
|
131
132
|
), task_population AS (
|
|
132
133
|
SELECT provider_key AS key, COUNT(*) AS task_count
|
|
133
134
|
FROM task_providers GROUP BY provider_key
|
|
134
|
-
), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population)
|
|
135
|
+
), keys AS (SELECT key FROM financial UNION SELECT key FROM population UNION SELECT key FROM task_population),
|
|
136
|
+
aggregated_rows AS (
|
|
137
|
+
SELECT keys.key, keys.key AS label, COALESCE(population.invocation_count,0) AS invocation_count,
|
|
138
|
+
COALESCE(task_population.task_count,0) AS task_count, population.total_duration,
|
|
139
|
+
population.source_event_count, financial.total_tokens, financial.total_cost_usd,
|
|
140
|
+
financial.total_cost_brl
|
|
141
|
+
FROM keys LEFT JOIN population USING(key) LEFT JOIN task_population USING(key)
|
|
142
|
+
LEFT JOIN financial USING(key)
|
|
143
|
+
), visible_rows AS (SELECT * FROM aggregated_rows WHERE ${providerVisibleMetricsSql})`;
|
|
135
144
|
const limit = Math.min(200, Math.max(1, input.limit ?? 50));
|
|
136
145
|
const offset = Math.max(0, input.offset ?? 0);
|
|
137
146
|
const total_count = Number(
|
|
138
147
|
(
|
|
139
148
|
context.database
|
|
140
|
-
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM
|
|
149
|
+
.prepare(`${ctes} ${facts} SELECT COUNT(*) AS count FROM visible_rows`)
|
|
141
150
|
.get(...values) as { count: number }
|
|
142
151
|
).count,
|
|
143
152
|
);
|
|
@@ -160,7 +169,7 @@ export function readProviderDimension(
|
|
|
160
169
|
.get(...values) as Record<string, unknown>;
|
|
161
170
|
const rows = context.database
|
|
162
171
|
.prepare(
|
|
163
|
-
`${ctes} ${facts} SELECT
|
|
172
|
+
`${ctes} ${facts} SELECT * FROM visible_rows ORDER BY total_cost_brl DESC, key LIMIT ? OFFSET ?`,
|
|
164
173
|
)
|
|
165
174
|
.all(...values, limit, offset) as Array<Record<string, unknown>>;
|
|
166
175
|
return {
|