@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
|
@@ -205,7 +205,14 @@ export function registerOperationsRoutes(app: Hono, dependencies: DashboardRoute
|
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
207
|
});
|
|
208
|
-
|
|
208
|
+
// The compact Run page reads the canonical lifecycle summary, which is not
|
|
209
|
+
// materialized as a SQLite file. Keep SQL-backed Operations views separate.
|
|
210
|
+
registerOperationsRunsRoute(
|
|
211
|
+
app,
|
|
212
|
+
isSqliteSnapshot ? canonicalFilesystem : filesystem,
|
|
213
|
+
readAggregate,
|
|
214
|
+
headOrJson,
|
|
215
|
+
);
|
|
209
216
|
// Explicit SQL read-model API for the comparison dashboard. It never falls
|
|
210
217
|
// back to filesystem pagination, making first-fold/entity query plans
|
|
211
218
|
// inspectable while preserving the established Operations contracts.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
2
|
+
import { PricingRepository } from "@zaip-ai/costs";
|
|
3
|
+
import type { Context } from "hono";
|
|
4
|
+
|
|
5
|
+
export async function readCurrentFxRate(filesystem: WorkspaceFileSystem) {
|
|
6
|
+
return new PricingRepository(filesystem).readLatestFxRate();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isUnfilteredCostRequest(context: Context): boolean {
|
|
10
|
+
return ["provider", "model", "agent", "phase", "status"].every(
|
|
11
|
+
(name) => (context.req.queries(name)?.length ?? 0) === 0,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { WorkspaceFileSystem, WorkspaceLayout } from "@zaip-ai/core";
|
|
2
2
|
import { PricingRepository, queryInvocationManifestPage } from "@zaip-ai/costs";
|
|
3
|
-
import type { CostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/contracts";
|
|
4
3
|
import { buildCostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/server";
|
|
5
4
|
import type { RunProjection } from "@zaip-ai/dashboard-operations/contracts";
|
|
6
5
|
import {
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
} from "@zaip-ai/dashboard-operations/server";
|
|
11
10
|
import type { Context } from "hono";
|
|
12
11
|
import { mergeManifestRuns } from "./manifest-runs.js";
|
|
12
|
+
import { filterCompactCostRuns } from "./server-cost-run-search.js";
|
|
13
13
|
import {
|
|
14
14
|
configuredAgentNames,
|
|
15
15
|
invocationSummariesForPeriod,
|
|
@@ -190,18 +190,21 @@ export async function buildManifestCostRunPage(
|
|
|
190
190
|
// API callers may request Run terminal status; absent telemetry never
|
|
191
191
|
// matches an explicit status and remains visible only without this filter.
|
|
192
192
|
filters.statuses.length > 0;
|
|
193
|
-
const [operations,
|
|
193
|
+
const [operations, lifecycleSummary] = await Promise.all([
|
|
194
194
|
// This compact projection reads the durable manifest summary plus Tasks;
|
|
195
195
|
// it intentionally does not materialise Run records.
|
|
196
196
|
import("@zaip-ai/dashboard-operations/server").then(({ buildQuickOperationsProjection }) =>
|
|
197
|
-
buildQuickOperationsProjection(filesystem, layout, { from, to }),
|
|
197
|
+
buildQuickOperationsProjection(filesystem, layout, { from, to }, { includeBoardTasks: true }),
|
|
198
198
|
),
|
|
199
|
-
queryInvocationManifestPage(filesystem, { from, to, filters, offset, limit }),
|
|
200
199
|
filesystem
|
|
201
200
|
.readText(".agents/records/lifecycle/summary.json")
|
|
202
201
|
.then((value) => JSON.parse(value) as { run_metrics?: CompactRunMetric[] }),
|
|
203
202
|
]);
|
|
204
|
-
const compactRuns = (
|
|
203
|
+
const compactRuns = filterCompactCostRuns(
|
|
204
|
+
lifecycleSummary.run_metrics ?? [],
|
|
205
|
+
context.req.query("query") ?? "",
|
|
206
|
+
operations.tasks,
|
|
207
|
+
)
|
|
205
208
|
.filter((run) => runTimestampInRange(run.finished_at ?? run.started_at, from, to))
|
|
206
209
|
.sort(
|
|
207
210
|
(left, right) =>
|
|
@@ -215,6 +218,16 @@ export async function buildManifestCostRunPage(
|
|
|
215
218
|
filters.phases.length === 0
|
|
216
219
|
? compactRuns
|
|
217
220
|
: compactRuns.filter((run) => run.phase !== null && filters.phases.includes(run.phase));
|
|
221
|
+
const page = await queryInvocationManifestPage(filesystem, {
|
|
222
|
+
from,
|
|
223
|
+
to,
|
|
224
|
+
filters,
|
|
225
|
+
offset,
|
|
226
|
+
limit,
|
|
227
|
+
...(context.req.query("query")
|
|
228
|
+
? { runIds: new Set(lifecycleCandidates.map((run) => run.id)) }
|
|
229
|
+
: {}),
|
|
230
|
+
});
|
|
218
231
|
const telemetryRunIds = new Set(page.summaries.map((summary) => summary.run_id));
|
|
219
232
|
const baseRuns = hasTelemetryFilters
|
|
220
233
|
? // Provider/model/agent filters are telemetry dimensions. Their candidate
|
|
@@ -284,6 +297,8 @@ export function mergeLifecycleRunPage(
|
|
|
284
297
|
|
|
285
298
|
export interface CompactRunMetric {
|
|
286
299
|
id: string;
|
|
300
|
+
entrypoint?: string | null;
|
|
301
|
+
run_purpose?: string | null;
|
|
287
302
|
started_at: string;
|
|
288
303
|
finished_at: string | null;
|
|
289
304
|
task_id: string | null;
|
|
@@ -334,11 +349,16 @@ export function compactRunProjection(run: CompactRunMetric): RunProjection {
|
|
|
334
349
|
? run.status
|
|
335
350
|
: "completed",
|
|
336
351
|
phase: run.phase ?? "execution",
|
|
337
|
-
entrypoint:
|
|
352
|
+
entrypoint:
|
|
353
|
+
run.entrypoint === "observation"
|
|
354
|
+
? "observation"
|
|
355
|
+
: run.task_id === null
|
|
356
|
+
? "conversation"
|
|
357
|
+
: "task",
|
|
338
358
|
work_type: "new",
|
|
339
359
|
run_role: run.run_kind === "subrun" ? "subagent" : "orchestrator",
|
|
340
360
|
run_kind: run.run_kind ?? "run",
|
|
341
|
-
run_purpose: "execution",
|
|
361
|
+
run_purpose: run.run_purpose === "observation" ? "observation" : "execution",
|
|
342
362
|
requires_finalizer: false,
|
|
343
363
|
parent_run_id: run.parent_run_id,
|
|
344
364
|
client: "unknown",
|
|
@@ -354,7 +374,7 @@ export function compactRunProjection(run: CompactRunMetric): RunProjection {
|
|
|
354
374
|
terminal_call_count: null,
|
|
355
375
|
failed_call_count: 0,
|
|
356
376
|
cancelled_call_count: 0,
|
|
357
|
-
conversation_id: run.conversation_id ?? run.id,
|
|
377
|
+
conversation_id: run.entrypoint === "observation" ? null : (run.conversation_id ?? run.id),
|
|
358
378
|
execution_id: run.execution_id,
|
|
359
379
|
task_id: run.task_id,
|
|
360
380
|
spec_id: run.spec_id,
|
|
@@ -415,69 +435,5 @@ function normalizeRunRangeBoundary(value: string, boundary: "start" | "end"): st
|
|
|
415
435
|
return value;
|
|
416
436
|
}
|
|
417
437
|
|
|
418
|
-
|
|
419
|
-
export
|
|
420
|
-
projection: CostMonitorProjection,
|
|
421
|
-
offset: number,
|
|
422
|
-
limit: number,
|
|
423
|
-
): CostMonitorProjection {
|
|
424
|
-
const safeOffset = Math.max(0, offset);
|
|
425
|
-
const safeLimit = Math.min(500, Math.max(1, limit));
|
|
426
|
-
return {
|
|
427
|
-
...projection,
|
|
428
|
-
runs: projection.runs.slice(safeOffset, safeOffset + safeLimit),
|
|
429
|
-
run_page: {
|
|
430
|
-
offset: safeOffset,
|
|
431
|
-
limit: safeLimit,
|
|
432
|
-
total: projection.runs.length,
|
|
433
|
-
has_more: safeOffset + safeLimit < projection.runs.length,
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* The Runs endpoint advertises these dimensions as server-side filters. Apply
|
|
440
|
-
* them before offset pagination so the reported total and every page have the
|
|
441
|
-
* same population. Aggregate fields intentionally retain their full filtered
|
|
442
|
-
* projection semantics and are not derived from the transport page.
|
|
443
|
-
*/
|
|
444
|
-
export function filterCostRunRows(
|
|
445
|
-
projection: CostMonitorProjection,
|
|
446
|
-
filters: {
|
|
447
|
-
provider: readonly string[];
|
|
448
|
-
model: readonly string[];
|
|
449
|
-
agent: readonly string[];
|
|
450
|
-
phase: readonly string[];
|
|
451
|
-
status: readonly string[];
|
|
452
|
-
},
|
|
453
|
-
): CostMonitorProjection {
|
|
454
|
-
const includes = (values: readonly string[], value: string) =>
|
|
455
|
-
values.length === 0 || values.includes(value);
|
|
456
|
-
const provider = (value: string | null) => {
|
|
457
|
-
const normalized = value?.trim().toLowerCase();
|
|
458
|
-
return !normalized || normalized === "unknown" || normalized === "codex"
|
|
459
|
-
? "openai"
|
|
460
|
-
: normalized;
|
|
461
|
-
};
|
|
462
|
-
return {
|
|
463
|
-
...projection,
|
|
464
|
-
runs: projection.runs.filter(
|
|
465
|
-
(run) =>
|
|
466
|
-
includes(filters.provider, provider(run.provider)) &&
|
|
467
|
-
includes(filters.model, run.model) &&
|
|
468
|
-
includes(filters.agent, run.agent_name) &&
|
|
469
|
-
includes(filters.phase, run.phase) &&
|
|
470
|
-
includes(filters.status, run.task_status),
|
|
471
|
-
),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export async function readCurrentFxRate(filesystem: WorkspaceFileSystem) {
|
|
476
|
-
return new PricingRepository(filesystem).readLatestFxRate();
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export function isUnfilteredCostRequest(context: Context): boolean {
|
|
480
|
-
return ["provider", "model", "agent", "phase", "status"].every(
|
|
481
|
-
(name) => (context.req.queries(name)?.length ?? 0) === 0,
|
|
482
|
-
);
|
|
483
|
-
}
|
|
438
|
+
export { filterCostRunRows, pageCostRunRows } from "./server-cost-run-search.js";
|
|
439
|
+
export { isUnfilteredCostRequest, readCurrentFxRate } from "./server-projection-loaders.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
2
|
+
import { fxRateSchema, PricingRepository, pricingCatalogSchema } from "@zaip-ai/costs";
|
|
3
|
+
import type { Hono } from "hono";
|
|
4
|
+
import { API_PREFIX, headOrJson } from "./server-http-contracts.js";
|
|
5
|
+
|
|
6
|
+
/** Keep mutable pricing routes together behind the dashboard session middleware. */
|
|
7
|
+
export function registerPricingRoutes(
|
|
8
|
+
app: Hono,
|
|
9
|
+
filesystem: WorkspaceFileSystem,
|
|
10
|
+
clearSnapshotCache: () => void,
|
|
11
|
+
): void {
|
|
12
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/pricing`, async (context) =>
|
|
13
|
+
headOrJson(
|
|
14
|
+
context,
|
|
15
|
+
pricingCatalogSchema.parse(
|
|
16
|
+
JSON.parse(await filesystem.readText(".agents/config/pricing/models.json")),
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
app.put(`${API_PREFIX}/pricing`, async (context) => {
|
|
21
|
+
const catalog = pricingCatalogSchema.parse({
|
|
22
|
+
...(await context.req.json()),
|
|
23
|
+
updated_at: new Date().toISOString(),
|
|
24
|
+
});
|
|
25
|
+
await filesystem.writeTextAtomic(
|
|
26
|
+
".agents/config/pricing/models.json",
|
|
27
|
+
`${JSON.stringify(catalog, null, 2)}\n`,
|
|
28
|
+
);
|
|
29
|
+
clearSnapshotCache();
|
|
30
|
+
return context.json(catalog);
|
|
31
|
+
});
|
|
32
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/fx-rate`, async (context) => {
|
|
33
|
+
const rate = await new PricingRepository(filesystem).readLatestFxRate();
|
|
34
|
+
return headOrJson(context, rate);
|
|
35
|
+
});
|
|
36
|
+
app.put(`${API_PREFIX}/fx-rate`, async (context) => {
|
|
37
|
+
const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
|
|
38
|
+
const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
|
|
39
|
+
clearSnapshotCache();
|
|
40
|
+
return context.json(rate);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createDashboardApp, projectionPeriodFilter } from "./server.js";
|
|
4
|
+
|
|
5
|
+
const workspaceRoot = resolve(import.meta.dirname, "../../../../..");
|
|
6
|
+
describe("dashboard route safety and calendar filters", () => {
|
|
7
|
+
it("serves only allowlisted canonical agent portraits", async () => {
|
|
8
|
+
const app = createDashboardApp(workspaceRoot, "test-session");
|
|
9
|
+
const agentIds = [
|
|
10
|
+
"agent-orchestrator",
|
|
11
|
+
"agent-builder",
|
|
12
|
+
"agent-task-executor",
|
|
13
|
+
"agent-analyst",
|
|
14
|
+
"agent-finalizer",
|
|
15
|
+
"agent-reviewer",
|
|
16
|
+
"agent-integrator",
|
|
17
|
+
"agent-auditor",
|
|
18
|
+
"agent-text-summarizer",
|
|
19
|
+
"agent-data-analyst",
|
|
20
|
+
"agent-refactoring-specialist",
|
|
21
|
+
"agent-frontend",
|
|
22
|
+
"agent-backend",
|
|
23
|
+
"agent-website-developer",
|
|
24
|
+
"agent-designer",
|
|
25
|
+
"agent-cloud-engineer",
|
|
26
|
+
"agent-security-auditor",
|
|
27
|
+
"agent-marketing-analyst",
|
|
28
|
+
];
|
|
29
|
+
for (const agentId of agentIds) {
|
|
30
|
+
const portrait = await app.request(
|
|
31
|
+
`/api/v2/agent-catalog-assets/${agentId}?token=test-session`,
|
|
32
|
+
);
|
|
33
|
+
expect(portrait.status, agentId).toBe(200);
|
|
34
|
+
expect(portrait.headers.get("content-type"), agentId).toBe("image/png");
|
|
35
|
+
expect((await portrait.arrayBuffer()).byteLength, agentId).toBeGreaterThan(1_000);
|
|
36
|
+
}
|
|
37
|
+
const traversal = await app.request(
|
|
38
|
+
"/api/v2/agent-catalog-assets/%2E%2E%2Fproject?token=test-session",
|
|
39
|
+
);
|
|
40
|
+
expect(traversal.status).toBe(404);
|
|
41
|
+
});
|
|
42
|
+
it("treats date-only dashboard filters as whole calendar days", () => {
|
|
43
|
+
const context = {
|
|
44
|
+
req: { query: (name: string) => (name === "from" ? "2026-07-10" : "2026-07-26") },
|
|
45
|
+
};
|
|
46
|
+
expect(projectionPeriodFilter(context as never)).toEqual({
|
|
47
|
+
from: "2026-07-10T03:00:00.000Z",
|
|
48
|
+
to: "2026-07-27T02:59:59.999Z",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("uses the IANA Sao Paulo offset for historical date-only filters", () => {
|
|
52
|
+
const context = {
|
|
53
|
+
req: { query: (name: string) => (name === "from" ? "2018-02-16" : "2018-02-16") },
|
|
54
|
+
};
|
|
55
|
+
expect(projectionPeriodFilter(context as never)).toEqual({
|
|
56
|
+
from: "2018-02-16T02:00:00.000Z",
|
|
57
|
+
to: "2018-02-17T01:59:59.999Z",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { filterCompactCostRuns } from "./server-cost-run-search.js";
|
|
2
3
|
import { compactRunProjection, runTimestampInRange } from "./server-projections.js";
|
|
3
4
|
|
|
4
5
|
describe("dashboard run projections", () => {
|
|
@@ -8,7 +9,7 @@ describe("dashboard run projections", () => {
|
|
|
8
9
|
});
|
|
9
10
|
|
|
10
11
|
it("keeps a lifecycle Run with no InvocationSummary as a null-telemetry page row", () => {
|
|
11
|
-
const
|
|
12
|
+
const metric = {
|
|
12
13
|
id: "RUN-NO-SUMMARY",
|
|
13
14
|
started_at: "2026-08-07T02:59:59.999Z",
|
|
14
15
|
finished_at: null,
|
|
@@ -30,7 +31,16 @@ describe("dashboard run projections", () => {
|
|
|
30
31
|
cost: null,
|
|
31
32
|
currency: null,
|
|
32
33
|
events: null,
|
|
33
|
-
}
|
|
34
|
+
};
|
|
35
|
+
const run = compactRunProjection(metric);
|
|
36
|
+
const older = { ...metric, id: "RUN-OLDER", task_id: "TASK-OLDER" };
|
|
37
|
+
expect(
|
|
38
|
+
filterCompactCostRuns([metric, older], "older title", [
|
|
39
|
+
{ id: "TASK-OLDER", title: "Older title" },
|
|
40
|
+
]),
|
|
41
|
+
).toEqual([older]);
|
|
42
|
+
expect(filterCompactCostRuns([metric, older], "no-summary", [])).toEqual([metric]);
|
|
43
|
+
expect(filterCompactCostRuns([metric, older], "missing", [])).toEqual([]);
|
|
34
44
|
expect(run).toMatchObject({
|
|
35
45
|
id: "RUN-NO-SUMMARY",
|
|
36
46
|
duration_ms: null,
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { ActiveCodexTelemetryCapture } from "./active-codex-telemetry.js";
|
|
4
5
|
import { createDashboardApp } from "./server.js";
|
|
5
6
|
import { withSqliteReadModelFixture } from "./server.test-fixtures.js";
|
|
6
7
|
|
|
@@ -84,12 +85,13 @@ describe("recent telemetry command readiness", () => {
|
|
|
84
85
|
it("cannot advertise current after a failed capture even when canonical projection succeeds", async () => {
|
|
85
86
|
await withSqliteReadModelFixture("telemetry-failed", async ({ root, filesystem }) => {
|
|
86
87
|
const commit = vi.fn(async () => undefined);
|
|
88
|
+
const capture = vi.fn(async (): Promise<ActiveCodexTelemetryCapture> => {
|
|
89
|
+
throw Object.assign(new Error("private command output"), { code: "ETIMEDOUT" });
|
|
90
|
+
});
|
|
87
91
|
const app = createDashboardApp(root, "session", undefined, {
|
|
88
92
|
filesystem,
|
|
89
93
|
refreshReadModel: commit,
|
|
90
|
-
captureTelemetry:
|
|
91
|
-
throw new Error("transcript timed out");
|
|
92
|
-
},
|
|
94
|
+
captureTelemetry: capture,
|
|
93
95
|
});
|
|
94
96
|
const headers = { "x-zaip-session": "session" };
|
|
95
97
|
await app.request("/api/v2/capture-telemetry", { method: "POST", headers });
|
|
@@ -98,7 +100,12 @@ describe("recent telemetry command readiness", () => {
|
|
|
98
100
|
expect(await ready.json()).toMatchObject({
|
|
99
101
|
status: "degraded",
|
|
100
102
|
snapshot_state: "updating",
|
|
101
|
-
active_capture_failure: {
|
|
103
|
+
active_capture_failure: {
|
|
104
|
+
code: "active_telemetry_capture_failed",
|
|
105
|
+
failure_kind: "timeout",
|
|
106
|
+
process_code: "ETIMEDOUT",
|
|
107
|
+
exit_code: null,
|
|
108
|
+
},
|
|
102
109
|
});
|
|
103
110
|
const manual = await app.request("/api/v2/refresh-projection", { method: "POST", headers });
|
|
104
111
|
expect(manual.status).toBe(503);
|
|
@@ -106,6 +113,24 @@ describe("recent telemetry command readiness", () => {
|
|
|
106
113
|
error: "Telemetry capture is unavailable",
|
|
107
114
|
snapshot_state: "updating",
|
|
108
115
|
});
|
|
116
|
+
|
|
117
|
+
capture.mockImplementation(async () => ({
|
|
118
|
+
captured_runs: 0,
|
|
119
|
+
captured_segments: 0,
|
|
120
|
+
manifest_state: "initialized" as const,
|
|
121
|
+
skipped_reason: "no_new_segments" as const,
|
|
122
|
+
catch_up_complete: true,
|
|
123
|
+
pending_runs: 0,
|
|
124
|
+
last_capture_at: new Date().toISOString(),
|
|
125
|
+
}));
|
|
126
|
+
await app.request("/api/v2/capture-telemetry", { method: "POST", headers });
|
|
127
|
+
await vi.waitFor(async () => {
|
|
128
|
+
const response = await app.request("/api/v2/ready", { headers });
|
|
129
|
+
expect(await response.json()).toMatchObject({
|
|
130
|
+
active_capture_failure: null,
|
|
131
|
+
active_capture: { catch_up_complete: true, pending_runs: 0 },
|
|
132
|
+
});
|
|
133
|
+
});
|
|
109
134
|
});
|
|
110
135
|
});
|
|
111
136
|
});
|
|
@@ -8,6 +8,7 @@ export function createTelemetryProjectionRefresh(input: {
|
|
|
8
8
|
onCaptureFailure?: (error: unknown) => void;
|
|
9
9
|
}) {
|
|
10
10
|
let inFlight: Promise<void> | null = null;
|
|
11
|
+
let capturing = false;
|
|
11
12
|
let projecting = false;
|
|
12
13
|
const refresh = (catchUp = false): Promise<void> => {
|
|
13
14
|
if (inFlight !== null) {
|
|
@@ -19,11 +20,14 @@ export function createTelemetryProjectionRefresh(input: {
|
|
|
19
20
|
inFlight = (async () => {
|
|
20
21
|
try {
|
|
21
22
|
if (input.capture !== undefined) {
|
|
23
|
+
capturing = true;
|
|
22
24
|
const result = await input.capture();
|
|
23
25
|
input.onCaptureSuccess?.(result);
|
|
24
26
|
}
|
|
25
27
|
} catch (error) {
|
|
26
28
|
input.onCaptureFailure?.(error);
|
|
29
|
+
} finally {
|
|
30
|
+
capturing = false;
|
|
27
31
|
}
|
|
28
32
|
// Keep canonical records readable even when telemetry is unavailable.
|
|
29
33
|
// The caller retains the capture failure separately from SQLite health.
|
|
@@ -40,5 +44,8 @@ export function createTelemetryProjectionRefresh(input: {
|
|
|
40
44
|
get pending() {
|
|
41
45
|
return inFlight !== null;
|
|
42
46
|
},
|
|
47
|
+
get capturing() {
|
|
48
|
+
return capturing;
|
|
49
|
+
},
|
|
43
50
|
};
|
|
44
51
|
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
createDashboardApp,
|
|
8
8
|
dashboardWorkspaceKey,
|
|
9
9
|
manifestRun,
|
|
10
|
-
projectionPeriodFilter,
|
|
11
10
|
SqliteReadModelFileSystem,
|
|
12
11
|
} from "./server.js";
|
|
13
12
|
import { invocationSummary, withSqliteReadModelFixture } from "./server.test-fixtures.js";
|
|
@@ -564,6 +563,57 @@ describe("dashboard backend", () => {
|
|
|
564
563
|
rows: expect.any(Array),
|
|
565
564
|
});
|
|
566
565
|
});
|
|
566
|
+
it("serves Operations Run pages from the canonical summary when SQLite omits that file", async () => {
|
|
567
|
+
await withSqliteReadModelFixture(
|
|
568
|
+
"operations-canonical-summary",
|
|
569
|
+
async ({ root, filesystem }) => {
|
|
570
|
+
const summaryPath = join(root, ".agents/records/lifecycle/summary.json");
|
|
571
|
+
await mkdir(join(root, ".agents/records/lifecycle"), { recursive: true });
|
|
572
|
+
await writeFile(
|
|
573
|
+
summaryPath,
|
|
574
|
+
JSON.stringify({
|
|
575
|
+
schema_version: 1,
|
|
576
|
+
run_metrics: [
|
|
577
|
+
{
|
|
578
|
+
id: "RUN-CANONICAL",
|
|
579
|
+
started_at: "2026-10-06T03:00:00.000Z",
|
|
580
|
+
finished_at: null,
|
|
581
|
+
phase: "execution",
|
|
582
|
+
status: "completed",
|
|
583
|
+
parent_run_id: null,
|
|
584
|
+
total_tokens: null,
|
|
585
|
+
cost: null,
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
await expect(
|
|
591
|
+
filesystem.readText(".agents/records/lifecycle/summary.json"),
|
|
592
|
+
).rejects.toMatchObject({ code: "ENOENT" });
|
|
593
|
+
const headers = { "x-zaip-session": "test-session" };
|
|
594
|
+
const app = createDashboardApp(root, "test-session", undefined, { filesystem });
|
|
595
|
+
const response = await app.request("/api/v2/operations/runs?kind=runs&offset=0&limit=10", {
|
|
596
|
+
headers,
|
|
597
|
+
});
|
|
598
|
+
expect(response.status).toBe(200);
|
|
599
|
+
await expect(response.json()).resolves.toMatchObject({
|
|
600
|
+
data_source: "lifecycle_manifest_summary",
|
|
601
|
+
rows: [
|
|
602
|
+
{ id: "RUN-CANONICAL", total_tokens: null, started_at: "2026-10-06T03:00:00.000Z" },
|
|
603
|
+
],
|
|
604
|
+
});
|
|
605
|
+
await writeFile(summaryPath, "{invalid-json");
|
|
606
|
+
const invalid = createDashboardApp(root, "test-session", undefined, { filesystem });
|
|
607
|
+
const rejected = await invalid.request(
|
|
608
|
+
"/api/v2/operations/runs?kind=runs&offset=0&limit=10",
|
|
609
|
+
{
|
|
610
|
+
headers,
|
|
611
|
+
},
|
|
612
|
+
);
|
|
613
|
+
expect(rejected.status).toBe(500);
|
|
614
|
+
},
|
|
615
|
+
);
|
|
616
|
+
});
|
|
567
617
|
it("serves a Cost Run transport page without materializing aggregate totals", async () => {
|
|
568
618
|
const app = createDashboardApp(workspaceRoot, "test-session");
|
|
569
619
|
const headers = { "x-zaip-session": "test-session" };
|
|
@@ -602,57 +652,4 @@ describe("dashboard backend", () => {
|
|
|
602
652
|
expect(page.runs.length).toBeLessThanOrEqual(1);
|
|
603
653
|
expect(page.runs.every((run) => run.phase === "execution")).toBe(true);
|
|
604
654
|
});
|
|
605
|
-
it("serves only allowlisted canonical agent portraits", async () => {
|
|
606
|
-
const app = createDashboardApp(workspaceRoot, "test-session");
|
|
607
|
-
const agentIds = [
|
|
608
|
-
"agent-orchestrator",
|
|
609
|
-
"agent-builder",
|
|
610
|
-
"agent-task-executor",
|
|
611
|
-
"agent-analyst",
|
|
612
|
-
"agent-finalizer",
|
|
613
|
-
"agent-reviewer",
|
|
614
|
-
"agent-integrator",
|
|
615
|
-
"agent-auditor",
|
|
616
|
-
"agent-text-summarizer",
|
|
617
|
-
"agent-data-analyst",
|
|
618
|
-
"agent-refactoring-specialist",
|
|
619
|
-
"agent-frontend",
|
|
620
|
-
"agent-backend",
|
|
621
|
-
"agent-website-developer",
|
|
622
|
-
"agent-designer",
|
|
623
|
-
"agent-cloud-engineer",
|
|
624
|
-
"agent-security-auditor",
|
|
625
|
-
"agent-marketing-analyst",
|
|
626
|
-
];
|
|
627
|
-
for (const agentId of agentIds) {
|
|
628
|
-
const portrait = await app.request(
|
|
629
|
-
`/api/v2/agent-catalog-assets/${agentId}?token=test-session`,
|
|
630
|
-
);
|
|
631
|
-
expect(portrait.status, agentId).toBe(200);
|
|
632
|
-
expect(portrait.headers.get("content-type"), agentId).toBe("image/png");
|
|
633
|
-
expect((await portrait.arrayBuffer()).byteLength, agentId).toBeGreaterThan(1_000);
|
|
634
|
-
}
|
|
635
|
-
const traversal = await app.request(
|
|
636
|
-
"/api/v2/agent-catalog-assets/%2E%2E%2Fproject?token=test-session",
|
|
637
|
-
);
|
|
638
|
-
expect(traversal.status).toBe(404);
|
|
639
|
-
});
|
|
640
|
-
it("treats date-only dashboard filters as whole calendar days", () => {
|
|
641
|
-
const context = {
|
|
642
|
-
req: { query: (name: string) => (name === "from" ? "2026-07-10" : "2026-07-26") },
|
|
643
|
-
};
|
|
644
|
-
expect(projectionPeriodFilter(context as never)).toEqual({
|
|
645
|
-
from: "2026-07-10T03:00:00.000Z",
|
|
646
|
-
to: "2026-07-27T02:59:59.999Z",
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
it("uses the IANA Sao Paulo offset for historical date-only filters", () => {
|
|
650
|
-
const context = {
|
|
651
|
-
req: { query: (name: string) => (name === "from" ? "2018-02-16" : "2018-02-16") },
|
|
652
|
-
};
|
|
653
|
-
expect(projectionPeriodFilter(context as never)).toEqual({
|
|
654
|
-
from: "2018-02-16T02:00:00.000Z",
|
|
655
|
-
to: "2018-02-17T01:59:59.999Z",
|
|
656
|
-
});
|
|
657
|
-
});
|
|
658
655
|
});
|