@zaip-ai/cli 1.3.0 → 1.3.1
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 +1 -1
- package/dist/services/agent-adapters.js +1 -1
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +2 -1
- package/dist/services/agent-adapters.test.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 +89 -7
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-rules.d.ts +1 -1
- package/dist/services/codex-rules.d.ts.map +1 -1
- package/dist/services/codex-rules.js +7 -0
- package/dist/services/codex-rules.js.map +1 -1
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +1 -0
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/runtime-template.test.js +31 -7
- package/dist/services/runtime-template.test.js.map +1 -1
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +7 -3
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/config/agents/catalog.yaml +7 -7
- package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +2 -2
- package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +2 -2
- package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +8 -8
- package/templates/.agents/dashboard/.zaip-managed.json +373 -205
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DxR_pgSV.js → CostCalculationGuidePage-ovxurkIk.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-4jZLTWE_.js → DashboardHelpDialog-BgVgrfhQ.js} +240 -86
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CKcsqb-r.js → PricingSettingsDialog-vSvvOHQp.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-DmmJD2Th.js → ProjectSettingsPage-zH758vfs.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-DLn-Mz2M.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CaKwQP43.css → TraceabilityWorkspace-DvvavDLY.css} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-PoJA2PI1.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-C0fj2uU9.js → icons-vendor-B_lDDHn1.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CZQlsLV9.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-BEsJ-FYH.js → react-vendor-B3TJrD4O.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B6y6Gw12.js → traceability-7f1j5wx3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +7 -7
- package/templates/.agents/dashboard/apps/frontend/playwright.dom.config.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +132 -245
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +0 -21
- package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +9 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.dom.e2e.ts +56 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +18 -1
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +75 -8
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +25 -1
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -9
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +4 -4
- package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +7 -0
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +20 -8
- package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.test.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/sqlite-page-export.ts +21 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +19 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.test.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-task-pagination.ts +36 -0
- package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +6 -2
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +48 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +31 -27
- package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +7 -1
- package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.test.ts +163 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-work-list-page.ts +136 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-operations-detail-export.ts +76 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.test.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-load-dedup.ts +27 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +38 -14
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +60 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionTable.tsx +191 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.loading.test.ts +14 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.run-page.test.ts +18 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +18 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +8 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorLoadErrors.tsx +35 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +47 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +50 -58
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.test.ts +132 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableLoading.tsx +70 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +128 -138
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +17 -67
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +2 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +3 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-page-range.ts +35 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +16 -4
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +37 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +44 -8
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +6 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +20 -3
- package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.test.ts +54 -0
- package/templates/.agents/dashboard/modules/operations/src/react/DeveloperAttributionPanel.tsx +247 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +39 -13
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +107 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +55 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +20 -28
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +162 -165
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +127 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +13 -46
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +3 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.test.tsx +43 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PagedWorkList.tsx +181 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +4 -2
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +13 -0
- package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.test.ts +18 -0
- package/templates/.agents/dashboard/modules/operations/src/react/developer-identities.ts +30 -0
- package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +8 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +3 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +14 -5
- package/templates/.agents/dashboard/modules/operations/src/react/index.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +4 -3
- package/templates/.agents/dashboard/modules/operations/src/react/operations-date-filters.css +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +21 -11
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.css +4 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +8 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/server/metric-coverage.ts +1 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +527 -335
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +501 -309
- package/templates/.agents/dashboard/packages/local-runtime/gitignore.template +2 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +3 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +3 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +25 -15
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +527 -335
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +34 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +15 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/server-classification-page.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.test.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-period-macro-metric.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +66 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.test.ts +61 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-request-authority.ts +18 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-run-projections.test.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +97 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +3 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +91 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +36 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +98 -123
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +111 -135
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-artifact-counts.ts +28 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +63 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +109 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +497 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-fast-page-parity.test-support.ts +65 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.test.ts +176 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-page.ts +119 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +41 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.test.ts +16 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-page.ts +29 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-cache.ts +355 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +121 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-timing.ts +27 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +15 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.test.ts +223 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-work-list.ts +304 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +23 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-first-fold-types.ts +89 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +227 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +691 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +81 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +440 -863
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1120 -1086
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +129 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.test.ts +26 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-install.ts +28 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +116 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +146 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +165 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +140 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +188 -299
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +51 -70
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +5 -2
- package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +4 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +142 -0
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +232 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +743 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +183 -210
- package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
- package/templates/.agents/requirements/autonomous-assurance.md +6 -6
- 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 +3 -3
- package/templates/.agents/requirements/kanban-dashboard.md +10 -2
- 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 +4 -4
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/dashboard-ui-consistency.md +14 -0
- package/templates/.agents/rules/frontend-lite.md +4 -4
- package/templates/.agents/rules/run-lifecycle-closure.md +4 -4
- package/templates/.agents/runtime/bin/zaip.mjs +1137 -1037
- package/templates/.agents/runtime/checksums.sha256 +2 -2
- package/templates/.agents/runtime/provenance.json +2 -2
- package/templates/.agents/skills/criar-pasta-obsidian/scripts/generate-obsidian-vault.test.mjs +20 -16
- package/templates/.agents/skills/zaip-skill-agent-catalog/SKILL.md +22 -0
- package/templates/.agents/skills/zaip-skill-agent-catalog/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-author-skills/SKILL.md +3 -2
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/audit-skill-catalog.mjs +49 -0
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/author-skill-scripts.test.mjs +27 -0
- package/templates/.agents/skills/zaip-skill-author-skills/scripts/run-skill-tests.mjs +37 -0
- package/templates/.agents/skills/zaip-skill-backend/SKILL.md +1 -0
- package/templates/.agents/skills/zaip-skill-change/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +83 -0
- package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/SKILL.md +22 -0
- package/templates/.agents/skills/zaip-skill-dashboard-diagnostics/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-frontend/SKILL.md +4 -3
- package/templates/.agents/skills/zaip-skill-frontend/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +10 -10
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +27 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +72 -22
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-explicit-recovery.mjs +223 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +42 -1
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +101 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +53 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +20 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-review/SKILL.md +5 -0
- package/templates/.agents/skills/zaip-skill-rules/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +22 -90
- package/templates/.agents/zaip-aios/INDEX.md +57 -32
- package/templates/.agents/zaip-aios/README.md +22 -12
- package/templates/.agents/zaip-aios/guides/02-requirements.md +5 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +13 -4
- package/templates/.agents/zaip-aios/guides/07-revisao-e-validacao.md +7 -0
- package/templates/.agents/zaip-aios/guides/10-seguranca.md +23 -0
- package/templates/.agents/zaip-aios/guides/11-observabilidade-e-custos.md +6 -6
- package/templates/.agents/zaip-aios/guides/13-boas-praticas.md +3 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +12 -3
- package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +5 -5
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +23 -0
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +44 -8
- package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +8 -14
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +5 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +6 -6
- package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +8 -8
- package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +3 -2
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +77 -10
- package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +8 -3
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +5 -0
- package/templates/template-manifest.json +555 -170
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +0 -2
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +0 -45
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +0 -7333
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { mkdir, mkdtemp, readFile, rm, utimes } from "node:fs/promises";
|
|
3
|
-
import {
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { afterEach, describe, expect, it } from "vitest";
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
recordPortStatus,
|
|
22
22
|
registeredListenerMatches,
|
|
23
23
|
runtimeKind,
|
|
24
|
-
startDashboardLaunchPage,
|
|
25
24
|
stopOwnedDashboardProcess,
|
|
26
25
|
stripTerminalControlCodes,
|
|
27
26
|
teardownWarning,
|
|
@@ -95,7 +94,7 @@ describe("dashboard executable session", () => {
|
|
|
95
94
|
|
|
96
95
|
it("opens preparation before checking freshness and keeps it through server health", async () => {
|
|
97
96
|
const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
|
|
98
|
-
const lock = source.indexOf("await withLauncherLock(
|
|
97
|
+
const lock = source.indexOf("await withLauncherLock(");
|
|
99
98
|
const preparation = source.indexOf("launchPage = await startDashboardLaunchPage(0);");
|
|
100
99
|
const freshness = source.indexOf("const buildSignature = await ensureFreshDashboardBuild();");
|
|
101
100
|
const server = source.indexOf(
|
|
@@ -118,6 +117,7 @@ describe("dashboard executable session", () => {
|
|
|
118
117
|
);
|
|
119
118
|
expect(wait).toContain("Dashboard data readiness returned ${readiness.status}");
|
|
120
119
|
expect(wait).toContain("unavailable.retryable = false;");
|
|
120
|
+
expect(wait).toContain("timedOut.retryable = false;");
|
|
121
121
|
expect(wait).toContain("if (error?.retryable === false) throw error;");
|
|
122
122
|
expect(wait).toContain("await delay(100);");
|
|
123
123
|
});
|
|
@@ -135,27 +135,49 @@ describe("dashboard executable session", () => {
|
|
|
135
135
|
|
|
136
136
|
it("uses one durable cache and an append-only worker log for a stale snapshot refresh", async () => {
|
|
137
137
|
const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
|
|
138
|
-
|
|
138
|
+
const refresh = await readFile(
|
|
139
|
+
new URL("./start-dashboard-refresh.mjs", import.meta.url),
|
|
140
|
+
"utf8",
|
|
141
|
+
);
|
|
139
142
|
expect(source).toContain(
|
|
140
143
|
'const sqliteSnapshotCacheDirectory = join(sqliteReadModelDirectory, "cache");',
|
|
141
144
|
);
|
|
142
|
-
expect(
|
|
145
|
+
expect(refresh).toContain("ZAIP_WORKSPACE: workspaceRoot,");
|
|
146
|
+
expect(refresh).toContain("ZAIP_SQLITE_CACHE_DIR: cacheDirectory,");
|
|
143
147
|
expect(source).toContain(
|
|
144
148
|
'const refreshLogPath = join(sessionDirectory, "dashboard-sqlite-refresh.log");',
|
|
145
149
|
);
|
|
146
|
-
expect(
|
|
147
|
-
expect(
|
|
150
|
+
expect(refresh).toContain('stdio: ["ignore", descriptor, descriptor]');
|
|
151
|
+
expect(refresh).toContain("worker log ${refreshLogPath}");
|
|
148
152
|
});
|
|
149
153
|
|
|
150
|
-
it("
|
|
151
|
-
const
|
|
154
|
+
it("persists the refresh worker PID before another launcher click may coalesce it", async () => {
|
|
155
|
+
const refresh = await readFile(
|
|
156
|
+
new URL("./start-dashboard-refresh.mjs", import.meta.url),
|
|
157
|
+
"utf8",
|
|
158
|
+
);
|
|
152
159
|
|
|
153
|
-
expect(
|
|
160
|
+
expect(refresh).toContain("await acquireRefreshLock(lockPath, isProcessAlive)");
|
|
161
|
+
expect(refresh).toContain("if (await isRunning())");
|
|
162
|
+
expect(refresh).toContain("await writeJsonAtomic(refreshStatusPath, {");
|
|
163
|
+
expect(refresh).toContain("await stopProcessTree(worker.pid).catch(() => {});");
|
|
164
|
+
const runningBarrier = refresh.slice(0, refresh.indexOf('worker.once("error"'));
|
|
165
|
+
expect(runningBarrier).not.toContain("void writeJsonAtomic(refreshStatusPath, {");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("keeps the automatic handoff status visible instead of offering an unreliable local link", async () => {
|
|
169
|
+
const [source, launcherSource] = await Promise.all([
|
|
170
|
+
readFile(new URL("./start-dashboard-launch-page.mjs", import.meta.url), "utf8"),
|
|
171
|
+
readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8"),
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
expect(source).toContain("O dashboard está pronto. Abrindo a tela principal automaticamente…");
|
|
175
|
+
expect(source).not.toContain('id="handoff-link"');
|
|
154
176
|
expect(source).toContain("function showHandoff(target)");
|
|
155
177
|
expect(source).toContain("setTimeout(()=>location.replace(next.target),0)");
|
|
156
178
|
expect(source).toContain("closeAfterHandoffGrace: (graceMs = 12_000)");
|
|
157
|
-
expect(
|
|
158
|
-
expect(
|
|
179
|
+
expect(launcherSource).toContain("launchPage.closeAfterHandoffGrace();");
|
|
180
|
+
expect(launcherSource).not.toContain("await launchPage.close();\n if (!redirected");
|
|
159
181
|
});
|
|
160
182
|
|
|
161
183
|
it("tears down prior owned sessions only after the new browser handoff", async () => {
|
|
@@ -279,13 +301,22 @@ describe("dashboard executable session", () => {
|
|
|
279
301
|
]);
|
|
280
302
|
});
|
|
281
303
|
|
|
304
|
+
it("keeps a Linux browser fallback when xdg-open is unavailable", () => {
|
|
305
|
+
const target = "http://127.0.0.1:4317/?token=local-session-token";
|
|
306
|
+
|
|
307
|
+
expect(browserLaunchCommands(target, "linux")).toEqual([
|
|
308
|
+
["xdg-open", [target]],
|
|
309
|
+
["gio", ["open", target]],
|
|
310
|
+
]);
|
|
311
|
+
});
|
|
312
|
+
|
|
282
313
|
it("falls back to another browser command when the first option fails", async () => {
|
|
283
314
|
let calls = 0;
|
|
284
315
|
const spawnImpl = () => {
|
|
285
|
-
|
|
316
|
+
const attempt = ++calls;
|
|
286
317
|
const child = new EventEmitter();
|
|
287
318
|
child.unref = () => {};
|
|
288
|
-
queueMicrotask(() => child.emit(
|
|
319
|
+
queueMicrotask(() => child.emit(attempt === 1 ? "error" : "spawn", new Error("failed")));
|
|
289
320
|
return child;
|
|
290
321
|
};
|
|
291
322
|
|
|
@@ -293,58 +324,6 @@ describe("dashboard executable session", () => {
|
|
|
293
324
|
expect(calls).toBe(2);
|
|
294
325
|
});
|
|
295
326
|
|
|
296
|
-
it("serves a viewport-covering centered progress overlay while the runtime starts", async () => {
|
|
297
|
-
const launchPage = await startDashboardLaunchPage(0);
|
|
298
|
-
try {
|
|
299
|
-
const page = await fetch(`http://${host}:${launchPage.port}/?token=temporary`);
|
|
300
|
-
const health = await fetch(`http://${host}:${launchPage.port}/api/v2/health`);
|
|
301
|
-
|
|
302
|
-
expect(page.status).toBe(200);
|
|
303
|
-
const html = await page.text();
|
|
304
|
-
expect(html).toContain('class="launch-overlay"');
|
|
305
|
-
expect(html).toContain("position:fixed;inset:0;width:100vw;height:100vh");
|
|
306
|
-
expect(html).toContain("display:grid;place-items:center");
|
|
307
|
-
expect(html).toContain('aria-live="polite" role="status"');
|
|
308
|
-
expect(html).toContain("abra o dashboard agora");
|
|
309
|
-
expect(html).toContain("function showHandoff(target)");
|
|
310
|
-
const progress = await fetch(`http://${host}:${launchPage.port}/api/v2/progress`);
|
|
311
|
-
await expect(progress.json()).resolves.toMatchObject({
|
|
312
|
-
phase: "Iniciando o launcher local",
|
|
313
|
-
percent: expect.any(Number),
|
|
314
|
-
});
|
|
315
|
-
expect(health.status).toBe(503);
|
|
316
|
-
await expect(health.json()).resolves.toEqual({
|
|
317
|
-
kind: "zaip-dashboard-launcher",
|
|
318
|
-
status: "starting",
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
launchPage.redirect("http://127.0.0.1:4317/?token=reused-session");
|
|
322
|
-
const ready = await fetch(`http://${host}:${launchPage.port}/api/v2/ready`);
|
|
323
|
-
await expect(ready.json()).resolves.toEqual({
|
|
324
|
-
target: "http://127.0.0.1:4317/?token=reused-session",
|
|
325
|
-
});
|
|
326
|
-
await expect(launchPage.waitForRedirect(10)).resolves.toBe(true);
|
|
327
|
-
} finally {
|
|
328
|
-
await launchPage.close();
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it("bounds launch-page shutdown when a browser connection never finishes polling", async () => {
|
|
333
|
-
const launchPage = await startDashboardLaunchPage(0);
|
|
334
|
-
const connection = connect({ host, port: launchPage.port });
|
|
335
|
-
await new Promise((resolveConnected, rejectConnected) => {
|
|
336
|
-
connection.once("connect", resolveConnected);
|
|
337
|
-
connection.once("error", rejectConnected);
|
|
338
|
-
});
|
|
339
|
-
const closed = new Promise((resolveClosed) => connection.once("close", resolveClosed));
|
|
340
|
-
const startedAt = Date.now();
|
|
341
|
-
await launchPage.close({ forceAfterMs: 20 });
|
|
342
|
-
await closed;
|
|
343
|
-
|
|
344
|
-
expect(Date.now() - startedAt).toBeLessThan(500);
|
|
345
|
-
expect(connection.destroyed).toBe(true);
|
|
346
|
-
});
|
|
347
|
-
|
|
348
327
|
it("reuses a healthy session owned by the same workspace", async () => {
|
|
349
328
|
const directory = await temporaryDirectory();
|
|
350
329
|
const path = join(directory, "dashboard-session.json");
|
|
@@ -630,7 +609,7 @@ describe("dashboard executable session", () => {
|
|
|
630
609
|
it("opens preparation before waiting and only reuses after checking the current build", async () => {
|
|
631
610
|
const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
|
|
632
611
|
const preparation = source.indexOf("launchPage = await startDashboardLaunchPage(0);");
|
|
633
|
-
const lock = source.indexOf("await withLauncherLock(
|
|
612
|
+
const lock = source.indexOf("await withLauncherLock(");
|
|
634
613
|
const buildSignature = source.indexOf(
|
|
635
614
|
"const buildSignature = await ensureFreshDashboardBuild();",
|
|
636
615
|
);
|
|
@@ -645,7 +624,8 @@ describe("dashboard executable session", () => {
|
|
|
645
624
|
expect(source).toContain(
|
|
646
625
|
'snapshotState: snapshotCache === "stale_seed" ? "updating" : "current",',
|
|
647
626
|
);
|
|
648
|
-
expect(source).toContain("
|
|
627
|
+
expect(source).toContain("launchPage.setFailure({");
|
|
628
|
+
expect(source).toContain("launchPage.closeAfterFailureObservation();");
|
|
649
629
|
expect(source).toContain('await writeLauncherStatus("ok");');
|
|
650
630
|
});
|
|
651
631
|
|
|
@@ -666,8 +646,9 @@ describe("dashboard executable session", () => {
|
|
|
666
646
|
"utf8",
|
|
667
647
|
);
|
|
668
648
|
|
|
669
|
-
expect(source).toContain("DateTime.UtcNow.AddSeconds(
|
|
670
|
-
expect(source).toContain("
|
|
649
|
+
expect(source).toContain("DateTime.UtcNow.AddSeconds(420)");
|
|
650
|
+
expect(source).toContain("lock window (210 s)");
|
|
651
|
+
expect(source).toContain("bounded 180 s server");
|
|
671
652
|
});
|
|
672
653
|
});
|
|
673
654
|
|
|
@@ -82,7 +82,10 @@ namespace ZaipDashboardLauncher
|
|
|
82
82
|
// The Node launcher coalesces concurrent clicks and may wait for a
|
|
83
83
|
// healthy build/handoff. Keep this wrapper alive for that bounded
|
|
84
84
|
// handoff instead of showing a second failure dialog.
|
|
85
|
-
|
|
85
|
+
// A waiting click can spend the Node lock window (210 s) behind
|
|
86
|
+
// the current leader, then still needs the bounded 180 s server
|
|
87
|
+
// startup window if that leader fails before publishing a session.
|
|
88
|
+
var deadline = DateTime.UtcNow.AddSeconds(420);
|
|
86
89
|
while (DateTime.UtcNow < deadline)
|
|
87
90
|
{
|
|
88
91
|
try
|
|
@@ -122,7 +125,7 @@ namespace ZaipDashboardLauncher
|
|
|
122
125
|
}
|
|
123
126
|
Thread.Sleep(100);
|
|
124
127
|
}
|
|
125
|
-
return ReportFailure("O dashboard
|
|
128
|
+
return ReportFailure("O dashboard ainda não publicou uma sessão pronta após 420 segundos. O processo local pode continuar registrando detalhes no log temporário; feche esta mensagem e tente novamente após verificar o log.");
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
private static bool VerifyDashboardLaunch(string workspaceRoot, out string error)
|
|
@@ -22,12 +22,15 @@ export default defineConfig({
|
|
|
22
22
|
cwd: workspaceRoot,
|
|
23
23
|
url: `${baseURL}/api/v2/health`,
|
|
24
24
|
reuseExistingServer: false,
|
|
25
|
-
timeout:
|
|
25
|
+
timeout: 120_000,
|
|
26
26
|
env: {
|
|
27
27
|
ZAIP_UI_TOKEN: "e2e-token",
|
|
28
28
|
ZAIP_SERVER_PORT: serverPort,
|
|
29
29
|
ZAIP_WORKSPACE: workspaceRoot,
|
|
30
30
|
ZAIP_UI_ROOT: resolve(workspaceRoot, ".agents/dashboard/apps/frontend/dist"),
|
|
31
|
+
// Match the Windows launcher: first paint uses the latest validated
|
|
32
|
+
// immutable seed while canonical refresh work stays off the served path.
|
|
33
|
+
ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "1",
|
|
31
34
|
},
|
|
32
35
|
},
|
|
33
36
|
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { expect, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
__costAbortRaceProbe: {
|
|
6
|
+
abortObserved: boolean;
|
|
7
|
+
overlayPresentAtSwitch: boolean | null;
|
|
8
|
+
shellInertAtSwitch: boolean | null;
|
|
9
|
+
switched: boolean;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("releases an aborted Cost group load and keeps Operation reachable", async ({ page }) => {
|
|
15
|
+
await page.addInitScript(() => {
|
|
16
|
+
const probe = {
|
|
17
|
+
abortObserved: false,
|
|
18
|
+
overlayPresentAtSwitch: null as boolean | null,
|
|
19
|
+
shellInertAtSwitch: null as boolean | null,
|
|
20
|
+
switched: false,
|
|
21
|
+
};
|
|
22
|
+
const originalFetch = window.fetch.bind(window);
|
|
23
|
+
Object.assign(window, { __costAbortRaceProbe: probe });
|
|
24
|
+
window.fetch = (input, init) => {
|
|
25
|
+
const requestUrl = new URL(
|
|
26
|
+
typeof input === "string" ? input : input instanceof URL ? input.href : input.url,
|
|
27
|
+
window.location.href,
|
|
28
|
+
);
|
|
29
|
+
if (
|
|
30
|
+
!probe.switched &&
|
|
31
|
+
requestUrl.pathname === "/api/v2/sqlite-views" &&
|
|
32
|
+
requestUrl.searchParams.get("view") === "cost_monitor_daily"
|
|
33
|
+
) {
|
|
34
|
+
const executionsTab = Array.from(
|
|
35
|
+
document.querySelectorAll<HTMLButtonElement>(".cost-tabs button"),
|
|
36
|
+
).find((button) => button.textContent?.trim() === "Execuções");
|
|
37
|
+
if (executionsTab !== undefined) {
|
|
38
|
+
probe.overlayPresentAtSwitch =
|
|
39
|
+
document.querySelector(".period-transition-overlay") !== null;
|
|
40
|
+
probe.shellInertAtSwitch = executionsTab.closest("[inert]") !== null;
|
|
41
|
+
init?.signal?.addEventListener("abort", () => {
|
|
42
|
+
probe.abortObserved = true;
|
|
43
|
+
});
|
|
44
|
+
probe.switched = true;
|
|
45
|
+
executionsTab.click();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return originalFetch(input, init);
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await openDashboard(page);
|
|
53
|
+
await expectGlobalMonthPreset(page);
|
|
54
|
+
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
55
|
+
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
56
|
+
const costTabs = page.locator(".cost-tabs");
|
|
57
|
+
await expect(costTabs).toBeVisible();
|
|
58
|
+
await expect
|
|
59
|
+
.poll(() => page.evaluate(() => window.__costAbortRaceProbe))
|
|
60
|
+
.toMatchObject({
|
|
61
|
+
abortObserved: true,
|
|
62
|
+
overlayPresentAtSwitch: false,
|
|
63
|
+
shellInertAtSwitch: false,
|
|
64
|
+
switched: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const executionsTab = costTabs.getByRole("button", { exact: true, name: "Execuções" });
|
|
68
|
+
await expect(executionsTab).toHaveAttribute("aria-pressed", "true");
|
|
69
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 15_000 });
|
|
70
|
+
|
|
71
|
+
await capabilityNav.getByRole("button", { name: /Operação/ }).click();
|
|
72
|
+
await expect(page.getByLabel("Kanban de trabalho")).toBeVisible();
|
|
73
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("loads every Cost Monitor tab before returning to Operation", async ({ page }) => {
|
|
77
|
+
const pageErrors: string[] = [];
|
|
78
|
+
const failedApiResponses: string[] = [];
|
|
79
|
+
page.on("pageerror", (error) => pageErrors.push(error.message));
|
|
80
|
+
page.on("response", (response) => {
|
|
81
|
+
if (
|
|
82
|
+
response.url().includes("/api/v2/") &&
|
|
83
|
+
!response.url().includes("/api/v2/reconcile-open-work") &&
|
|
84
|
+
response.status() >= 400
|
|
85
|
+
)
|
|
86
|
+
failedApiResponses.push(`${response.status()} ${response.url()}`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await openDashboard(page);
|
|
90
|
+
await expectGlobalMonthPreset(page);
|
|
91
|
+
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
92
|
+
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
93
|
+
const costTabs = page.locator(".cost-tabs");
|
|
94
|
+
await expect(costTabs).toBeVisible();
|
|
95
|
+
|
|
96
|
+
for (const [tab, view] of [
|
|
97
|
+
["Diário", "daily"],
|
|
98
|
+
["Mensal", "monthly"],
|
|
99
|
+
["Anual", "annual"],
|
|
100
|
+
["Visão geral", "overview"],
|
|
101
|
+
["Execuções", "executions"],
|
|
102
|
+
["Invocações de Agentes", "agentCalls"],
|
|
103
|
+
["Zaip Agentes", "agents"],
|
|
104
|
+
["Modelos LLM", "models"],
|
|
105
|
+
["Provedores", "providers"],
|
|
106
|
+
["Tarefas", "tasks"],
|
|
107
|
+
["SPEC", "specs"],
|
|
108
|
+
["PRD", "prds"],
|
|
109
|
+
["Conversas", "conversations"],
|
|
110
|
+
] as const) {
|
|
111
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 30_000 });
|
|
112
|
+
const button = costTabs.getByRole("button", { exact: true, name: tab });
|
|
113
|
+
await button.click();
|
|
114
|
+
await expect(button).toHaveAttribute("aria-pressed", "true");
|
|
115
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 30_000 });
|
|
116
|
+
await expect(page.locator(`.cost-content-${view}`)).toBeVisible();
|
|
117
|
+
await expect(page.locator(".cost-period-error")).toHaveCount(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await capabilityNav.getByRole("button", { name: /Operação/ }).click();
|
|
121
|
+
await expect(page.getByLabel("Kanban de trabalho")).toBeVisible();
|
|
122
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0);
|
|
123
|
+
expect(pageErrors).toEqual([]);
|
|
124
|
+
expect(failedApiResponses).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
async function expectGlobalMonthPreset(page: import("@playwright/test").Page) {
|
|
128
|
+
await expect(
|
|
129
|
+
page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" }),
|
|
130
|
+
).toHaveValue("this-month");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function openDashboard(page: import("@playwright/test").Page) {
|
|
134
|
+
const operations = page.waitForResponse(
|
|
135
|
+
(response) =>
|
|
136
|
+
response.status() === 200 &&
|
|
137
|
+
response.url().includes("/api/v2/operations") &&
|
|
138
|
+
response.url().includes("scope=quick"),
|
|
139
|
+
);
|
|
140
|
+
await page.goto("/?token=e2e-token");
|
|
141
|
+
return operations;
|
|
142
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { expect, type Page, type Response, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
__paginationExportProbe: { closes: number; writes: number };
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type TargetView = {
|
|
10
|
+
tab: string | RegExp;
|
|
11
|
+
content: string;
|
|
12
|
+
sqliteView: string;
|
|
13
|
+
pagerName: string;
|
|
14
|
+
exportLimit: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type SqlitePage = {
|
|
18
|
+
offset: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
total_count: number;
|
|
21
|
+
has_more: boolean;
|
|
22
|
+
rows: readonly Record<string, unknown>[];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const targets: readonly TargetView[] = [
|
|
26
|
+
{
|
|
27
|
+
tab: "Execuções",
|
|
28
|
+
content: "executions",
|
|
29
|
+
sqliteView: "cost_monitor_executions",
|
|
30
|
+
pagerName: "Paginação dos dados agregados",
|
|
31
|
+
exportLimit: 50,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
tab: "Invocações de Agentes",
|
|
35
|
+
content: "agentCalls",
|
|
36
|
+
sqliteView: "cost_monitor_agent_invocations",
|
|
37
|
+
pagerName: "Paginação das invocações",
|
|
38
|
+
exportLimit: 100,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
tab: /Zaip (?:Agentes|Agents)/,
|
|
42
|
+
content: "agents",
|
|
43
|
+
sqliteView: "cost_monitor_zaip_agents",
|
|
44
|
+
pagerName: "Paginação de grupos",
|
|
45
|
+
exportLimit: 50,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
tab: "Tarefas",
|
|
49
|
+
content: "tasks",
|
|
50
|
+
sqliteView: "cost_monitor_tasks",
|
|
51
|
+
pagerName: "Paginação dos dados agregados",
|
|
52
|
+
exportLimit: 50,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
tab: "Conversas",
|
|
56
|
+
content: "conversations",
|
|
57
|
+
sqliteView: "cost_monitor_conversations",
|
|
58
|
+
pagerName: "Paginação dos dados agregados",
|
|
59
|
+
exportLimit: 50,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
test("serves ten rows and exercises Next/Previous on every reported slow tab", async ({ page }) => {
|
|
64
|
+
const tabs = await openAllTimeCostMonitor(page);
|
|
65
|
+
const measurements: Array<{ tab: string; first_ms: number; next_ms: number }> = [];
|
|
66
|
+
|
|
67
|
+
for (const target of targets) {
|
|
68
|
+
const button = tabs.getByRole("button", { exact: true, name: target.tab });
|
|
69
|
+
const first = await requestPage(page, target.sqliteView, 0, 10, () => button.click());
|
|
70
|
+
const content = page.locator(`.cost-content-${target.content}`);
|
|
71
|
+
await expect(button).toHaveAttribute("aria-pressed", "true");
|
|
72
|
+
await expect(content).toBeVisible();
|
|
73
|
+
await expect(content.locator("table.ui-table tbody tr")).toHaveCount(10);
|
|
74
|
+
expect(first.payload).toMatchObject({ offset: 0, limit: 10, has_more: true });
|
|
75
|
+
expect(first.payload.total_count).toBeGreaterThan(10);
|
|
76
|
+
expect(first.elapsedMs).toBeLessThan(5_000);
|
|
77
|
+
|
|
78
|
+
let pager = content.getByRole("navigation", { name: target.pagerName });
|
|
79
|
+
await expect(pager).toContainText(`1–10 de ${first.payload.total_count}`);
|
|
80
|
+
await expect(pager.getByRole("button", { name: "Anterior" })).toBeDisabled();
|
|
81
|
+
await expect(pager.getByRole("button", { name: "Próxima" })).toBeEnabled();
|
|
82
|
+
|
|
83
|
+
const next = await requestPage(page, target.sqliteView, 10, 10, () =>
|
|
84
|
+
pager.getByRole("button", { name: "Próxima" }).click(),
|
|
85
|
+
);
|
|
86
|
+
pager = content.getByRole("navigation", { name: target.pagerName });
|
|
87
|
+
await expect(content.locator("table.ui-table tbody tr")).toHaveCount(10);
|
|
88
|
+
await expect(pager).toContainText(`11–20 de ${first.payload.total_count}`);
|
|
89
|
+
await expect(pager.getByRole("button", { name: "Anterior" })).toBeEnabled();
|
|
90
|
+
expect(next.elapsedMs).toBeLessThan(5_000);
|
|
91
|
+
|
|
92
|
+
await requestPage(page, target.sqliteView, 0, 10, () =>
|
|
93
|
+
pager.getByRole("button", { name: "Anterior" }).click(),
|
|
94
|
+
);
|
|
95
|
+
await expect(content.getByRole("navigation", { name: target.pagerName })).toContainText(
|
|
96
|
+
`1–10 de ${first.payload.total_count}`,
|
|
97
|
+
);
|
|
98
|
+
measurements.push({
|
|
99
|
+
tab: typeof target.tab === "string" ? target.tab : "Zaip Agents",
|
|
100
|
+
first_ms: first.elapsedMs,
|
|
101
|
+
next_ms: next.elapsedMs,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`COST_PAGINATION_TIMINGS ${JSON.stringify(measurements)}`);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
for (const target of targets) {
|
|
109
|
+
const label = typeof target.tab === "string" ? target.tab : "Zaip Agents";
|
|
110
|
+
test(`exports the complete ${label} population independently from its ten-row visual page`, async ({
|
|
111
|
+
page,
|
|
112
|
+
}) => {
|
|
113
|
+
test.setTimeout(360_000);
|
|
114
|
+
await installExportSink(page);
|
|
115
|
+
const tabs = await openAllTimeCostMonitor(page);
|
|
116
|
+
const pages: SqlitePage[] = [];
|
|
117
|
+
page.on("response", async (response) => {
|
|
118
|
+
if (!isSqliteResponse(response, target.sqliteView)) return;
|
|
119
|
+
try {
|
|
120
|
+
const payload = (await response.json()) as SqlitePage;
|
|
121
|
+
if (payload.limit === target.exportLimit) pages.push(payload);
|
|
122
|
+
} catch {
|
|
123
|
+
// A failed/non-JSON response is asserted by the missing complete page below.
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const button = tabs.getByRole("button", { exact: true, name: target.tab });
|
|
127
|
+
await requestPage(page, target.sqliteView, 0, 10, () => button.click());
|
|
128
|
+
await page.getByRole("button", { name: "Exportar Excel" }).click();
|
|
129
|
+
await expect
|
|
130
|
+
.poll(() => page.evaluate(() => window.__paginationExportProbe.closes), { timeout: 300_000 })
|
|
131
|
+
.toBe(1);
|
|
132
|
+
await expect(page.getByText("Exportação concluída.", { exact: true })).toBeVisible({
|
|
133
|
+
timeout: 30_000,
|
|
134
|
+
});
|
|
135
|
+
await expect.poll(() => pages.some((entry) => !entry.has_more), { timeout: 30_000 }).toBe(true);
|
|
136
|
+
|
|
137
|
+
expect(pages.length).toBeGreaterThan(0);
|
|
138
|
+
expect(pages.every((entry) => entry.limit === target.exportLimit)).toBe(true);
|
|
139
|
+
expect(pages.map((entry) => entry.offset)).toEqual(
|
|
140
|
+
pages.map((_, index) => index * target.exportLimit),
|
|
141
|
+
);
|
|
142
|
+
const total = pages[0]!.total_count;
|
|
143
|
+
expect(pages.every((entry) => entry.total_count === total)).toBe(true);
|
|
144
|
+
expect(pages.reduce((sum, entry) => sum + entry.rows.length, 0)).toBe(total);
|
|
145
|
+
expect(pages.at(-1)?.has_more).toBe(false);
|
|
146
|
+
console.log(`COST_EXPORT_TOTAL ${label} ${total}`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function openAllTimeCostMonitor(page: Page) {
|
|
151
|
+
const operations = page.waitForResponse(
|
|
152
|
+
(response) =>
|
|
153
|
+
response.status() === 200 &&
|
|
154
|
+
response.url().includes("/api/v2/operations") &&
|
|
155
|
+
response.url().includes("scope=quick"),
|
|
156
|
+
);
|
|
157
|
+
await page.goto("/?token=e2e-token");
|
|
158
|
+
await operations;
|
|
159
|
+
const period = page
|
|
160
|
+
.locator(".dashboard-global-controls")
|
|
161
|
+
.getByRole("combobox", { name: "Período" });
|
|
162
|
+
if ((await period.inputValue()) !== "all") {
|
|
163
|
+
await period.selectOption("all");
|
|
164
|
+
await expect(period).toHaveValue("all");
|
|
165
|
+
}
|
|
166
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
167
|
+
await page
|
|
168
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
169
|
+
.getByRole("button", { name: /Monitor de Custos/ })
|
|
170
|
+
.click();
|
|
171
|
+
const tabs = page.locator(".cost-tabs");
|
|
172
|
+
await expect(tabs).toBeVisible();
|
|
173
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
174
|
+
return tabs;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function requestPage(
|
|
178
|
+
page: Page,
|
|
179
|
+
view: string,
|
|
180
|
+
offset: number,
|
|
181
|
+
limit: number,
|
|
182
|
+
action: () => Promise<unknown>,
|
|
183
|
+
) {
|
|
184
|
+
const startedAt = Date.now();
|
|
185
|
+
const responsePromise = page.waitForResponse(
|
|
186
|
+
(response) =>
|
|
187
|
+
isSqliteResponse(response, view) &&
|
|
188
|
+
new URL(response.url()).searchParams.get("offset") === String(offset) &&
|
|
189
|
+
new URL(response.url()).searchParams.get("limit") === String(limit),
|
|
190
|
+
);
|
|
191
|
+
await action();
|
|
192
|
+
const response = await responsePromise;
|
|
193
|
+
const elapsedMs = Date.now() - startedAt;
|
|
194
|
+
expect(response.status()).toBe(200);
|
|
195
|
+
const payload = (await response.json()) as SqlitePage;
|
|
196
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
197
|
+
return { payload, elapsedMs };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isSqliteResponse(response: Response, view: string): boolean {
|
|
201
|
+
const url = new URL(response.url());
|
|
202
|
+
return (
|
|
203
|
+
response.status() === 200 &&
|
|
204
|
+
url.pathname === "/api/v2/sqlite-views" &&
|
|
205
|
+
url.searchParams.get("view") === view
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function installExportSink(page: Page) {
|
|
210
|
+
await page.addInitScript(() => {
|
|
211
|
+
const probe = { closes: 0, writes: 0 };
|
|
212
|
+
Object.assign(window, { __paginationExportProbe: probe });
|
|
213
|
+
Object.assign(window, {
|
|
214
|
+
showSaveFilePicker: async ({ suggestedName }: { suggestedName: string }) => {
|
|
215
|
+
const writer = {
|
|
216
|
+
abort: async () => undefined,
|
|
217
|
+
close: async () => {
|
|
218
|
+
probe.closes += 1;
|
|
219
|
+
},
|
|
220
|
+
ready: Promise.resolve(),
|
|
221
|
+
write: async () => {
|
|
222
|
+
probe.writes += 1;
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
return {
|
|
226
|
+
name: suggestedName,
|
|
227
|
+
createWritable: async () => ({ ...writer, getWriter: () => writer }),
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|