@zaip-ai/cli 1.0.8 → 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/adapters.d.ts +1 -1
- package/dist/services/adapters.js +1 -1
- package/dist/services/agent-adapters.js +3 -3
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +7 -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/logger.test.js +2 -2
- 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 +12 -3
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/architecture/software-architecture.md +131 -0
- package/templates/.agents/branding/brandbook.docx +0 -0
- package/templates/.agents/branding/brandbook.md +60 -0
- 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-finalizer.toml +1 -1
- package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +8 -8
- package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -1
- package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
- package/templates/.agents/dashboard/.zaip-managed.json +457 -173
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-Z60df2r-.js → CostCalculationGuidePage-ovxurkIk.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-BgVgrfhQ.js +2310 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Coc6pA-1.js → PricingSettingsDialog-vSvvOHQp.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-zH758vfs.js +3 -0
- 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-DFBuc_qp.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-CCQOHdVI.js → react-vendor-B3TJrD4O.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-pC2_0J8x.js → traceability-7f1j5wx3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +7 -7
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/playwright.dom.config.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +199 -140
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +0 -21
- package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +20 -11
- package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +14 -7
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +4 -2
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.dom.e2e.ts +56 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +21 -2
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +89 -19
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +19 -4
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +49 -3
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +29 -10
- 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 +4 -2
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +36 -26
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +47 -6
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +59 -9
- 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 +137 -23
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +229 -35
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +61 -7
- package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +3 -6
- 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 +39 -15
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +3 -4
- package/templates/.agents/dashboard/apps/frontend/vite.config.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +89 -19
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +31 -16
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +43 -14
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +5 -5
- 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/CostModelsAuditTable.tsx +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +75 -44
- 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 +162 -40
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +221 -79
- 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 +203 -155
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +164 -125
- 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-monitor.css +5 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-page-range.ts +35 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +2 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +32 -9
- 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 +131 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +34 -17
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +0 -24
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +225 -424
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +24 -15
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +6 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +46 -26
- 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/EntityTables.tsx +31 -11
- 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 +23 -29
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +211 -184
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverviewTopPanel.tsx +127 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +60 -51
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +1 -4
- 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/PeriodSummaryTable.tsx +32 -10
- package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +9 -3
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +13 -5
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +16 -6
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +14 -4
- package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +1 -2
- 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 +393 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +484 -61
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +23 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
- package/templates/.agents/dashboard/modules/operations/src/react/index.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +4 -4
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +6 -4
- 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-global-filters.test.ts +4 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +22 -12
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.css +4 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +53 -6
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +2 -4
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +82 -6
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +2 -1
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +19 -7
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +4 -1
- package/templates/.agents/dashboard/modules/operations/src/server/metric-coverage.ts +1 -0
- package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +11 -5
- 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/branding/src/index.ts +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +533 -338
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +506 -311
- package/templates/.agents/dashboard/packages/local-runtime/gitignore.template +2 -0
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +15 -7
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +44 -13
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +8 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +472 -145
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +15 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +141 -33
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +533 -338
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +3 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +89 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +33 -16
- 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 +22 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +91 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +7 -3
- 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 +230 -132
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +224 -182
- 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-metric-contract.ts +16 -2
- 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 +1655 -567
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +2168 -1268
- 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 +255 -289
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +94 -88
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +5 -2
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- 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 +189 -214
- package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
- 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/growth/estrategia-bootstrap-90-dias.md +169 -0
- package/templates/.agents/growth/strategy.md +27 -0
- package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
- package/templates/.agents/marketing/strategy.md +29 -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/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +1142 -1041
- 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 +3 -3
- package/templates/.agents/sales/playbook.md +32 -0
- package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
- package/templates/.agents/skills/criar-pasta-obsidian/scripts/generate-obsidian-vault.test.mjs +20 -16
- package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
- 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-brand-governance/SKILL.md +9 -2
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +1 -1
- 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-enforce-architecture/SKILL.md +2 -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-growth-strategy-revops/SKILL.md +6 -0
- 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-marketing/SKILL.md +6 -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 +111 -104
- 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 +55 -9
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +145 -14
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +19 -9
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +82 -5
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -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-sales-methodology/SKILL.md +6 -0
- 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/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +28 -5
- package/templates/.agents/zaip-aios/INDEX.md +57 -22
- package/templates/.agents/zaip-aios/README.md +24 -13
- package/templates/.agents/zaip-aios/VERSION +1 -1
- 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/18-catalogo-de-agentes.md +76 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +95 -0
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +366 -0
- package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +46 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +74 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
- package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
- package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +19 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +111 -0
- package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +172 -0
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +149 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
- package/templates/template-manifest.json +756 -235
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cs6dzDmW.js +0 -1505
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +0 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.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-Cf9ctPVt.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.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
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
import { expect, type Locator, type Page, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
type Metric = {
|
|
4
|
+
surface: string;
|
|
5
|
+
period: "this-month" | "all";
|
|
6
|
+
ttfv_ms: number;
|
|
7
|
+
settled_ms: number;
|
|
8
|
+
request_count: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type SqlitePage = {
|
|
12
|
+
offset: number;
|
|
13
|
+
limit: number;
|
|
14
|
+
total_count: number;
|
|
15
|
+
has_more: boolean;
|
|
16
|
+
rows: readonly Record<string, unknown>[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type PeriodQuery = { from: string | null; to: string | null };
|
|
20
|
+
|
|
21
|
+
const costViews = [
|
|
22
|
+
["Diário", "daily", "cost_monitor_daily"],
|
|
23
|
+
["Mensal", "monthly", "cost_monitor_monthly"],
|
|
24
|
+
["Anual", "annual", "cost_monitor_annual"],
|
|
25
|
+
["Visão geral", "overview", null],
|
|
26
|
+
["Execuções", "executions", "cost_monitor_executions"],
|
|
27
|
+
["Invocações de Agentes", "agentCalls", "cost_monitor_agent_invocations"],
|
|
28
|
+
["Zaip Agentes", "agents", "cost_monitor_zaip_agents"],
|
|
29
|
+
["Modelos LLM", "models", "cost_monitor_models"],
|
|
30
|
+
["Provedores", "providers", "cost_monitor_providers"],
|
|
31
|
+
["Tarefas", "tasks", "cost_monitor_tasks"],
|
|
32
|
+
["SPEC", "specs", "cost_monitor_specs"],
|
|
33
|
+
["PRD", "prds", "cost_monitor_prds"],
|
|
34
|
+
["Conversas", "conversations", "cost_monitor_conversations"],
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
const workKinds = ["Tudo", "Tarefas", "Tarefa Avulsa", "PRDs", "SPECs"] as const;
|
|
38
|
+
const boardStages = [
|
|
39
|
+
"definition",
|
|
40
|
+
"planning",
|
|
41
|
+
"ready",
|
|
42
|
+
"executing",
|
|
43
|
+
"verifying",
|
|
44
|
+
"completed",
|
|
45
|
+
"blocked",
|
|
46
|
+
"cancelled",
|
|
47
|
+
"failed",
|
|
48
|
+
] as const;
|
|
49
|
+
const workKindQuery = {
|
|
50
|
+
Tudo: null,
|
|
51
|
+
Tarefas: "task",
|
|
52
|
+
"Tarefa Avulsa": "standalone-task",
|
|
53
|
+
PRDs: "prd",
|
|
54
|
+
SPECs: "spec",
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
type WorkKindLabel = (typeof workKinds)[number];
|
|
58
|
+
type ApiUrlMatcher = (url: string) => boolean;
|
|
59
|
+
|
|
60
|
+
test("measures Cost, Operation and Traceability surfaces without full snapshot hydration", async ({
|
|
61
|
+
page,
|
|
62
|
+
}) => {
|
|
63
|
+
test.setTimeout(600_000);
|
|
64
|
+
const metrics: Metric[] = [];
|
|
65
|
+
const apiFailures: string[] = [];
|
|
66
|
+
const idleSnapshots: string[] = [];
|
|
67
|
+
const pageErrors: string[] = [];
|
|
68
|
+
const successfulApiResponses: string[] = [];
|
|
69
|
+
const requestStartedAt = new WeakMap<import("@playwright/test").Request, number>();
|
|
70
|
+
let apiRequestCount = 0;
|
|
71
|
+
const activeApiRequests = new Map<string, number>();
|
|
72
|
+
const apiActivityAt = new Map<string, number>();
|
|
73
|
+
page.on("request", (request) => {
|
|
74
|
+
if (!request.url().includes("/api/v2/")) return;
|
|
75
|
+
if (isBackgroundReconciliation(request.url())) return;
|
|
76
|
+
apiActivityAt.set(request.url(), performance.now());
|
|
77
|
+
requestStartedAt.set(request, performance.now());
|
|
78
|
+
apiRequestCount += 1;
|
|
79
|
+
activeApiRequests.set(request.url(), (activeApiRequests.get(request.url()) ?? 0) + 1);
|
|
80
|
+
if (new URL(request.url()).pathname === "/api/v2/snapshot") idleSnapshots.push(request.url());
|
|
81
|
+
});
|
|
82
|
+
const finishApiRequest = (request: { url(): string }) => {
|
|
83
|
+
if (!request.url().includes("/api/v2/")) return;
|
|
84
|
+
if (isBackgroundReconciliation(request.url())) return;
|
|
85
|
+
apiActivityAt.set(request.url(), performance.now());
|
|
86
|
+
const count = activeApiRequests.get(request.url()) ?? 0;
|
|
87
|
+
if (count <= 1) activeApiRequests.delete(request.url());
|
|
88
|
+
else activeApiRequests.set(request.url(), count - 1);
|
|
89
|
+
};
|
|
90
|
+
page.on("requestfinished", finishApiRequest);
|
|
91
|
+
page.on("requestfailed", finishApiRequest);
|
|
92
|
+
page.on("response", (response) => {
|
|
93
|
+
if (process.env.ZAIP_E2E_LOG_TIMINGS === "1") {
|
|
94
|
+
const startedAt = requestStartedAt.get(response.request());
|
|
95
|
+
if (startedAt !== undefined) {
|
|
96
|
+
console.log(
|
|
97
|
+
`DASHBOARD_API_TIMING ${Math.round(performance.now() - startedAt)}ms ${response.status()} ${response.url()}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
response.url().includes("/api/v2/") &&
|
|
103
|
+
!isBackgroundReconciliation(response.url()) &&
|
|
104
|
+
response.status() >= 400
|
|
105
|
+
) {
|
|
106
|
+
apiFailures.push(`${response.status()} ${response.url()}`);
|
|
107
|
+
}
|
|
108
|
+
if (
|
|
109
|
+
response.url().includes("/api/v2/") &&
|
|
110
|
+
!isBackgroundReconciliation(response.url()) &&
|
|
111
|
+
response.status() < 400
|
|
112
|
+
) {
|
|
113
|
+
successfulApiResponses.push(response.url());
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
page.on("pageerror", (error) => pageErrors.push(error.message));
|
|
117
|
+
|
|
118
|
+
await installExportSink(page);
|
|
119
|
+
await openDashboard(page);
|
|
120
|
+
for (const period of ["this-month", "all"] as const) {
|
|
121
|
+
await selectPeriod(page, period);
|
|
122
|
+
const waitForApiQuiet = (matches: ApiUrlMatcher = () => true) =>
|
|
123
|
+
waitForQuietApiRequests(
|
|
124
|
+
() => [...activeApiRequests.keys()].filter(matches),
|
|
125
|
+
() => latestMatchingActivity(apiActivityAt, matches),
|
|
126
|
+
);
|
|
127
|
+
const responses = () => successfulApiResponses;
|
|
128
|
+
await exerciseCostMonitor(
|
|
129
|
+
page,
|
|
130
|
+
period,
|
|
131
|
+
metrics,
|
|
132
|
+
() => apiRequestCount,
|
|
133
|
+
waitForApiQuiet,
|
|
134
|
+
responses,
|
|
135
|
+
);
|
|
136
|
+
await exerciseOperations(
|
|
137
|
+
page,
|
|
138
|
+
period,
|
|
139
|
+
metrics,
|
|
140
|
+
() => apiRequestCount,
|
|
141
|
+
waitForApiQuiet,
|
|
142
|
+
responses,
|
|
143
|
+
);
|
|
144
|
+
await exerciseTraceability(
|
|
145
|
+
page,
|
|
146
|
+
period,
|
|
147
|
+
metrics,
|
|
148
|
+
() => apiRequestCount,
|
|
149
|
+
waitForApiQuiet,
|
|
150
|
+
responses,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await verifyCompleteCostExport(page);
|
|
155
|
+
await verifyCompleteWorkListExport(page);
|
|
156
|
+
|
|
157
|
+
expect(pageErrors).toEqual([]);
|
|
158
|
+
expect(apiFailures).toEqual([]);
|
|
159
|
+
expect(idleSnapshots).toEqual([]);
|
|
160
|
+
console.log(`DASHBOARD_PERFORMANCE_MATRIX ${JSON.stringify(metrics)}`);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
async function openDashboard(page: Page) {
|
|
164
|
+
const operations = page.waitForResponse(
|
|
165
|
+
(response) =>
|
|
166
|
+
response.status() === 200 &&
|
|
167
|
+
response.url().includes("/api/v2/operations") &&
|
|
168
|
+
response.url().includes("scope=quick"),
|
|
169
|
+
);
|
|
170
|
+
await page.goto("/?token=e2e-token");
|
|
171
|
+
await operations;
|
|
172
|
+
await expect(page.getByRole("navigation", { name: "Áreas do dashboard" })).toBeVisible();
|
|
173
|
+
await assertNoFullscreenOverlay(page);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function selectPeriod(page: Page, period: "this-month" | "all") {
|
|
177
|
+
const control = page
|
|
178
|
+
.locator(".dashboard-global-controls")
|
|
179
|
+
.getByRole("combobox", { name: "Período" });
|
|
180
|
+
if ((await control.inputValue()) !== period) {
|
|
181
|
+
await control.selectOption(period);
|
|
182
|
+
await expect(control).toHaveValue(period);
|
|
183
|
+
}
|
|
184
|
+
await assertNoFullscreenOverlay(page);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function exerciseCostMonitor(
|
|
188
|
+
page: Page,
|
|
189
|
+
period: Metric["period"],
|
|
190
|
+
metrics: Metric[],
|
|
191
|
+
requestCount: () => number,
|
|
192
|
+
waitForApiQuiet: (matches?: ApiUrlMatcher) => Promise<void>,
|
|
193
|
+
successfulResponses: () => readonly string[],
|
|
194
|
+
) {
|
|
195
|
+
const navigation = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
196
|
+
const tabs = page.locator(".cost-tabs");
|
|
197
|
+
const expectedPeriod = await selectedPeriodQuery(page, period);
|
|
198
|
+
|
|
199
|
+
let enteringCostMonitor = true;
|
|
200
|
+
for (const [tab, content, apiView] of costViews) {
|
|
201
|
+
const button = tabs.getByRole("button", { exact: true, name: tab });
|
|
202
|
+
const costSurface = page.locator(`.cost-content-${content}`);
|
|
203
|
+
const costPageResponse =
|
|
204
|
+
apiView === null ? undefined : costPageResponseMatcher(apiView, expectedPeriod);
|
|
205
|
+
await measureSurface(
|
|
206
|
+
page,
|
|
207
|
+
metrics,
|
|
208
|
+
`cost:${tab}`,
|
|
209
|
+
period,
|
|
210
|
+
requestCount,
|
|
211
|
+
async () => {
|
|
212
|
+
if (enteringCostMonitor) {
|
|
213
|
+
enteringCostMonitor = false;
|
|
214
|
+
await navigation.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
215
|
+
await expect(tabs).toBeVisible({ timeout: 10_000 });
|
|
216
|
+
await assertNoFullscreenOverlay(page);
|
|
217
|
+
}
|
|
218
|
+
await button.click();
|
|
219
|
+
await expect(button).toHaveAttribute("aria-pressed", "true");
|
|
220
|
+
await expect(costSurface).toBeVisible();
|
|
221
|
+
await expect(
|
|
222
|
+
costSurface
|
|
223
|
+
.locator(".ui-table tbody tr, .ui-empty, .cost-period-average-card, .cost-chart")
|
|
224
|
+
.first(),
|
|
225
|
+
).toBeVisible({ timeout: 10_000 });
|
|
226
|
+
},
|
|
227
|
+
async () => {
|
|
228
|
+
await expect(costSurface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
229
|
+
await waitForApiQuiet();
|
|
230
|
+
await assertCostPagination(page, costSurface, costPageResponse);
|
|
231
|
+
},
|
|
232
|
+
successfulResponses,
|
|
233
|
+
costPageResponse === undefined ? undefined : [costPageResponse],
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function exerciseOperations(
|
|
239
|
+
page: Page,
|
|
240
|
+
period: Metric["period"],
|
|
241
|
+
metrics: Metric[],
|
|
242
|
+
requestCount: () => number,
|
|
243
|
+
waitForApiQuiet: (matches?: ApiUrlMatcher) => Promise<void>,
|
|
244
|
+
successfulResponses: () => readonly string[],
|
|
245
|
+
) {
|
|
246
|
+
const navigation = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
247
|
+
const tabs = page.getByRole("navigation", { name: "Entidades do ciclo de trabalho" });
|
|
248
|
+
const expectedPeriod = await selectedPeriodQuery(page, period);
|
|
249
|
+
let enteringOperations = true;
|
|
250
|
+
|
|
251
|
+
for (const [viewLabel, content] of [
|
|
252
|
+
["Visão de Quadro (Kanban)", ".operations-work-board"],
|
|
253
|
+
["Visão de Lista", ".operations-work-list"],
|
|
254
|
+
] as const) {
|
|
255
|
+
const view = tabs.getByRole("button", { exact: true, name: viewLabel });
|
|
256
|
+
for (const kind of workKinds) {
|
|
257
|
+
const surface = page.locator(content);
|
|
258
|
+
const selectionResponse = operationSelectionResponseMatcher(content, kind, expectedPeriod);
|
|
259
|
+
await measureSurface(
|
|
260
|
+
page,
|
|
261
|
+
metrics,
|
|
262
|
+
`operations:${viewLabel}:${kind}`,
|
|
263
|
+
period,
|
|
264
|
+
requestCount,
|
|
265
|
+
async () => {
|
|
266
|
+
if (enteringOperations) {
|
|
267
|
+
enteringOperations = false;
|
|
268
|
+
await navigation.getByRole("button", { name: /Operação/ }).click();
|
|
269
|
+
await expect(tabs).toBeVisible({ timeout: 10_000 });
|
|
270
|
+
}
|
|
271
|
+
await view.click();
|
|
272
|
+
await expect(view).toHaveAttribute("aria-pressed", "true");
|
|
273
|
+
const kindButton = page
|
|
274
|
+
.getByRole("navigation", { name: "Filtrar tipo de trabalho" })
|
|
275
|
+
.getByRole("button", { exact: true, name: kind });
|
|
276
|
+
await kindButton.click();
|
|
277
|
+
await expect(surface).toBeVisible();
|
|
278
|
+
await expect(
|
|
279
|
+
surface
|
|
280
|
+
.locator(".operations-work-card, .ui-table tbody tr, .ui-empty, .operations-empty")
|
|
281
|
+
.first(),
|
|
282
|
+
).toBeVisible({ timeout: 10_000 });
|
|
283
|
+
},
|
|
284
|
+
async () => {
|
|
285
|
+
if (content === ".operations-work-list") {
|
|
286
|
+
await expect(surface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
287
|
+
await assertWorkListPagination(page, surface, selectionResponse, waitForApiQuiet);
|
|
288
|
+
}
|
|
289
|
+
await waitForApiQuiet(selectionResponse);
|
|
290
|
+
},
|
|
291
|
+
successfulResponses,
|
|
292
|
+
content === ".operations-work-board"
|
|
293
|
+
? boardStages.map((stage) =>
|
|
294
|
+
operationSelectionResponseMatcher(content, kind, expectedPeriod, stage),
|
|
295
|
+
)
|
|
296
|
+
: [selectionResponse],
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function exerciseTraceability(
|
|
303
|
+
page: Page,
|
|
304
|
+
period: Metric["period"],
|
|
305
|
+
metrics: Metric[],
|
|
306
|
+
requestCount: () => number,
|
|
307
|
+
waitForApiQuiet: (matches?: ApiUrlMatcher) => Promise<void>,
|
|
308
|
+
successfulResponses: () => readonly string[],
|
|
309
|
+
) {
|
|
310
|
+
const navigation = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
311
|
+
let selectedTaskId = "";
|
|
312
|
+
await measureSurface(
|
|
313
|
+
page,
|
|
314
|
+
metrics,
|
|
315
|
+
"traceability",
|
|
316
|
+
period,
|
|
317
|
+
requestCount,
|
|
318
|
+
async () => {
|
|
319
|
+
await navigation.getByRole("button", { name: /Rastreabilidade/ }).click();
|
|
320
|
+
const selector = page.getByRole("combobox", { name: "Buscar task por nome ou ID" });
|
|
321
|
+
await expect(selector).toBeVisible();
|
|
322
|
+
await selector.click();
|
|
323
|
+
const listbox = page.getByRole("listbox", { name: "Buscar task por nome ou ID" });
|
|
324
|
+
await expect(listbox).toBeVisible();
|
|
325
|
+
const task = listbox.getByRole("option").first();
|
|
326
|
+
await expect(task).toBeVisible();
|
|
327
|
+
selectedTaskId = traceTaskIdFromOption(await task.getAttribute("id"));
|
|
328
|
+
await task.click();
|
|
329
|
+
await expect(page.locator('section[aria-label="Rastreabilidade da task"]')).toBeVisible({
|
|
330
|
+
timeout: 10_000,
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
waitForApiQuiet,
|
|
334
|
+
successfulResponses,
|
|
335
|
+
[
|
|
336
|
+
(url) =>
|
|
337
|
+
new URL(url).pathname === `/api/v2/tasks/${encodeURIComponent(selectedTaskId)}/trace`,
|
|
338
|
+
],
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function assertWorkListPagination(
|
|
343
|
+
page: Page,
|
|
344
|
+
surface: Locator,
|
|
345
|
+
matchesSelection: ApiUrlMatcher,
|
|
346
|
+
waitForApiQuiet: (matches?: ApiUrlMatcher) => Promise<void>,
|
|
347
|
+
) {
|
|
348
|
+
const rows = surface.locator("table.ui-table tbody tr");
|
|
349
|
+
expect(await rows.count()).toBeLessThanOrEqual(10);
|
|
350
|
+
const pager = surface.getByRole("navigation", { name: "Paginação da lista de trabalho" });
|
|
351
|
+
await expect(pager).toBeVisible();
|
|
352
|
+
const totalText = await pager.locator("output").textContent();
|
|
353
|
+
const total = Number(totalText?.match(/de\s+(\d+)/)?.[1] ?? 0);
|
|
354
|
+
if (total > 10) {
|
|
355
|
+
const firstPageRows = await rowSignatures(rows);
|
|
356
|
+
const next = pager.getByRole("button", { name: "Próxima" });
|
|
357
|
+
await expect(next).toBeEnabled();
|
|
358
|
+
await assertNoFullscreenOverlay(page);
|
|
359
|
+
const nextResponse = waitForWorkListPageResponse(page, matchesSelection, 10);
|
|
360
|
+
await next.click();
|
|
361
|
+
await nextResponse;
|
|
362
|
+
await expect(pager.locator("output")).toContainText("11–", { timeout: 10_000 });
|
|
363
|
+
await expect(rows).toHaveCount(Math.min(10, total - 10), { timeout: 10_000 });
|
|
364
|
+
await expect(surface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
365
|
+
expect(await rowSignatures(rows)).not.toEqual(firstPageRows);
|
|
366
|
+
// Filter hydration must not reset an already loaded page after navigation.
|
|
367
|
+
await waitForApiQuiet(matchesSelection);
|
|
368
|
+
await expect(pager.locator("output")).toContainText("11–", { timeout: 10_000 });
|
|
369
|
+
await expect(pager.getByRole("button", { name: "Anterior" })).toBeEnabled();
|
|
370
|
+
await assertNoFullscreenOverlay(page);
|
|
371
|
+
const firstPageResponse = waitForWorkListPageResponse(page, matchesSelection, 0);
|
|
372
|
+
await pager.getByRole("button", { name: "Anterior" }).click();
|
|
373
|
+
await firstPageResponse;
|
|
374
|
+
await expect(pager.locator("output")).toContainText("1–", { timeout: 10_000 });
|
|
375
|
+
await expect(rows).toHaveCount(Math.min(10, total), { timeout: 10_000 });
|
|
376
|
+
await expect(surface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
377
|
+
expect(await rowSignatures(rows)).toEqual(firstPageRows);
|
|
378
|
+
await assertNoFullscreenOverlay(page);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function waitForWorkListPageResponse(page: Page, matchesSelection: ApiUrlMatcher, offset: number) {
|
|
383
|
+
return page.waitForResponse(
|
|
384
|
+
(response) =>
|
|
385
|
+
response.status() === 200 &&
|
|
386
|
+
matchesSelection(response.url()) &&
|
|
387
|
+
new URL(response.url()).searchParams.get("offset") === String(offset),
|
|
388
|
+
{ timeout: 10_000 },
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function measureSurface(
|
|
393
|
+
page: Page,
|
|
394
|
+
metrics: Metric[],
|
|
395
|
+
surface: string,
|
|
396
|
+
period: Metric["period"],
|
|
397
|
+
requestCount: () => number,
|
|
398
|
+
action: () => Promise<void>,
|
|
399
|
+
settle: () => Promise<void>,
|
|
400
|
+
successfulResponses: () => readonly string[],
|
|
401
|
+
expectedApiResponses?: readonly ApiUrlMatcher[],
|
|
402
|
+
) {
|
|
403
|
+
const startedAt = Date.now();
|
|
404
|
+
const requestsBefore = requestCount();
|
|
405
|
+
const responsesBefore = successfulResponses().length;
|
|
406
|
+
await action();
|
|
407
|
+
const ttfv = Date.now() - startedAt;
|
|
408
|
+
expect(ttfv, `${surface} first visual feedback`).toBeLessThan(10_000);
|
|
409
|
+
if (expectedApiResponses !== undefined) {
|
|
410
|
+
await expect
|
|
411
|
+
.poll(
|
|
412
|
+
() =>
|
|
413
|
+
expectedApiResponses.every((matches) =>
|
|
414
|
+
successfulResponses()
|
|
415
|
+
.slice(responsesBefore)
|
|
416
|
+
.some((url) => matches(url)),
|
|
417
|
+
),
|
|
418
|
+
{ timeout: 10_000, message: `${surface} responses for the selected surface` },
|
|
419
|
+
)
|
|
420
|
+
.toBe(true);
|
|
421
|
+
}
|
|
422
|
+
await settle();
|
|
423
|
+
await assertNoFullscreenOverlay(page);
|
|
424
|
+
const settled = Date.now() - startedAt;
|
|
425
|
+
expect(settled, `${surface} settled`).toBeLessThan(10_000);
|
|
426
|
+
metrics.push({
|
|
427
|
+
surface,
|
|
428
|
+
period,
|
|
429
|
+
ttfv_ms: ttfv,
|
|
430
|
+
settled_ms: settled,
|
|
431
|
+
request_count: requestCount() - requestsBefore,
|
|
432
|
+
});
|
|
433
|
+
if (process.env.ZAIP_E2E_LOG_TIMINGS === "1") {
|
|
434
|
+
console.log(`DASHBOARD_SURFACE_TIMING ${surface} ${period} ${ttfv}ms ${settled}ms`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function operationSelectionResponseMatcher(
|
|
439
|
+
content: ".operations-work-board" | ".operations-work-list",
|
|
440
|
+
kind: WorkKindLabel,
|
|
441
|
+
period: PeriodQuery,
|
|
442
|
+
stage?: (typeof boardStages)[number],
|
|
443
|
+
): ApiUrlMatcher {
|
|
444
|
+
return (url) => {
|
|
445
|
+
const parsed = new URL(url);
|
|
446
|
+
if (!matchesPeriodQuery(parsed, period) || !matchesWorkKindQuery(parsed, kind)) return false;
|
|
447
|
+
if (content === ".operations-work-list") {
|
|
448
|
+
return (
|
|
449
|
+
parsed.pathname === "/api/v2/sqlite-views" &&
|
|
450
|
+
parsed.searchParams.get("view") === "operations_work_list"
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return (
|
|
454
|
+
parsed.pathname === "/api/v2/operations" &&
|
|
455
|
+
parsed.searchParams.get("scope") === "board" &&
|
|
456
|
+
(stage === undefined || parsed.searchParams.get("stage") === stage)
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function costPageResponseMatcher(view: string, period: PeriodQuery): ApiUrlMatcher {
|
|
462
|
+
return (url) => {
|
|
463
|
+
const parsed = new URL(url);
|
|
464
|
+
return (
|
|
465
|
+
parsed.pathname === "/api/v2/sqlite-views" &&
|
|
466
|
+
parsed.searchParams.get("view") === view &&
|
|
467
|
+
matchesPeriodQuery(parsed, period)
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function matchesWorkKindQuery(url: URL, kind: WorkKindLabel): boolean {
|
|
473
|
+
const expected = workKindQuery[kind];
|
|
474
|
+
return expected === null
|
|
475
|
+
? !url.searchParams.has("kind")
|
|
476
|
+
: url.searchParams.get("kind") === expected;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function latestMatchingActivity(
|
|
480
|
+
activityAt: ReadonlyMap<string, number>,
|
|
481
|
+
matches: ApiUrlMatcher,
|
|
482
|
+
): number | null {
|
|
483
|
+
let latest: number | null = null;
|
|
484
|
+
for (const [url, timestamp] of activityAt) {
|
|
485
|
+
if (matches(url) && (latest === null || timestamp > latest)) latest = timestamp;
|
|
486
|
+
}
|
|
487
|
+
return latest;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function waitForQuietApiRequests(
|
|
491
|
+
getActiveApiRequests: () => readonly string[],
|
|
492
|
+
getLatestActivityAt: () => number | null,
|
|
493
|
+
) {
|
|
494
|
+
await expect
|
|
495
|
+
.poll(
|
|
496
|
+
() => {
|
|
497
|
+
const latestActivityAt = getLatestActivityAt();
|
|
498
|
+
return (
|
|
499
|
+
getActiveApiRequests().length === 0 &&
|
|
500
|
+
latestActivityAt !== null &&
|
|
501
|
+
performance.now() - latestActivityAt >= 500
|
|
502
|
+
);
|
|
503
|
+
},
|
|
504
|
+
{ timeout: 10_000 },
|
|
505
|
+
)
|
|
506
|
+
.toBe(true);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async function selectedPeriodQuery(page: Page, period: Metric["period"]): Promise<PeriodQuery> {
|
|
510
|
+
if (period === "all") return { from: null, to: null };
|
|
511
|
+
const controls = page.locator(".dashboard-global-controls");
|
|
512
|
+
return {
|
|
513
|
+
from: await controls.getByLabel("Data inicial").inputValue(),
|
|
514
|
+
to: await controls.getByLabel("Data final").inputValue(),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function matchesPeriodQuery(url: URL, period: PeriodQuery): boolean {
|
|
519
|
+
const from = url.searchParams.get("from");
|
|
520
|
+
const to = url.searchParams.get("to");
|
|
521
|
+
return from === period.from && to === period.to;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function traceTaskIdFromOption(optionId: string | null): string {
|
|
525
|
+
const prefix = "traceability-task-option-";
|
|
526
|
+
if (optionId === null || !optionId.startsWith(prefix)) {
|
|
527
|
+
throw new Error("Traceability option did not expose its task ID.");
|
|
528
|
+
}
|
|
529
|
+
return optionId.slice(prefix.length);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Startup reconciliation is intentionally non-blocking and is asserted separately. */
|
|
533
|
+
function isBackgroundReconciliation(url: string): boolean {
|
|
534
|
+
return new URL(url).pathname === "/api/v2/reconcile-open-work";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function assertCostPagination(
|
|
538
|
+
page: Page,
|
|
539
|
+
surface: Locator,
|
|
540
|
+
matchesPageResponse: ApiUrlMatcher | undefined,
|
|
541
|
+
) {
|
|
542
|
+
for (const body of await surface.locator("table.ui-table tbody").all()) {
|
|
543
|
+
expect(await body.locator("tr").count()).toBeLessThanOrEqual(10);
|
|
544
|
+
}
|
|
545
|
+
const pager = surface
|
|
546
|
+
.getByRole("navigation", {
|
|
547
|
+
name: /Paginação (?:de grupos|das invocações|dos dados agregados)/,
|
|
548
|
+
})
|
|
549
|
+
.first();
|
|
550
|
+
if ((await pager.count()) === 0) return;
|
|
551
|
+
await expect(pager).toBeVisible();
|
|
552
|
+
const range = pager.locator("output, [aria-live='polite']").first();
|
|
553
|
+
const totalText = await range.textContent();
|
|
554
|
+
const total = Number(totalText?.match(/de\s+(\d+)/)?.[1] ?? 0);
|
|
555
|
+
const rows = surface.locator("table.ui-table tbody tr");
|
|
556
|
+
if (total <= 10) return;
|
|
557
|
+
const firstPageRows = await rowSignatures(rows);
|
|
558
|
+
const next = pager.getByRole("button", { name: "Próxima" });
|
|
559
|
+
await expect(next).toBeEnabled();
|
|
560
|
+
const nextResponse =
|
|
561
|
+
matchesPageResponse === undefined
|
|
562
|
+
? undefined
|
|
563
|
+
: waitForCostPageResponse(page, matchesPageResponse, 10);
|
|
564
|
+
await next.click();
|
|
565
|
+
await nextResponse;
|
|
566
|
+
await expect(range).toContainText("11–", { timeout: 10_000 });
|
|
567
|
+
await expect(surface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
568
|
+
expect(await rows.count()).toBeLessThanOrEqual(10);
|
|
569
|
+
expect(await rowSignatures(rows)).not.toEqual(firstPageRows);
|
|
570
|
+
await assertNoFullscreenOverlay(page);
|
|
571
|
+
const firstPageResponse =
|
|
572
|
+
matchesPageResponse === undefined
|
|
573
|
+
? undefined
|
|
574
|
+
: waitForCostPageResponse(page, matchesPageResponse, 0);
|
|
575
|
+
await pager.getByRole("button", { name: "Anterior" }).click();
|
|
576
|
+
await firstPageResponse;
|
|
577
|
+
await expect(range).toContainText("1–", { timeout: 10_000 });
|
|
578
|
+
await expect(surface).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
579
|
+
expect(await rowSignatures(rows)).toEqual(firstPageRows);
|
|
580
|
+
await assertNoFullscreenOverlay(page);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function waitForCostPageResponse(page: Page, matchesPageResponse: ApiUrlMatcher, offset: number) {
|
|
584
|
+
return page.waitForResponse(
|
|
585
|
+
(response) =>
|
|
586
|
+
response.status() === 200 &&
|
|
587
|
+
matchesPageResponse(response.url()) &&
|
|
588
|
+
new URL(response.url()).searchParams.get("offset") === String(offset),
|
|
589
|
+
{ timeout: 10_000 },
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function rowSignatures(rows: Locator): Promise<string[]> {
|
|
594
|
+
return rows.evaluateAll((entries) =>
|
|
595
|
+
entries.map((entry) => (entry.textContent ?? "").replace(/\s+/g, " ").trim()),
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function assertNoFullscreenOverlay(page: Page) {
|
|
600
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 10_000 });
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function verifyCompleteCostExport(page: Page) {
|
|
604
|
+
await selectPeriod(page, "all");
|
|
605
|
+
await page
|
|
606
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
607
|
+
.getByRole("button", { name: /Monitor de Custos/ })
|
|
608
|
+
.click();
|
|
609
|
+
for (const item of [
|
|
610
|
+
{ tab: "Diário", content: "daily", view: "cost_monitor_daily", limit: 50 },
|
|
611
|
+
{ tab: "Execuções", content: "executions", view: "cost_monitor_executions", limit: 50 },
|
|
612
|
+
{
|
|
613
|
+
tab: "Invocações de Agentes",
|
|
614
|
+
content: "agentCalls",
|
|
615
|
+
view: "cost_monitor_agent_invocations",
|
|
616
|
+
limit: 100,
|
|
617
|
+
},
|
|
618
|
+
] as const) {
|
|
619
|
+
await page.locator(".cost-tabs").getByRole("button", { exact: true, name: item.tab }).click();
|
|
620
|
+
const content = page.locator(`.cost-content-${item.content}`);
|
|
621
|
+
await expect(content).not.toHaveAttribute("aria-busy", "true", { timeout: 10_000 });
|
|
622
|
+
expect(await content.locator("table.ui-table tbody tr").count()).toBeLessThanOrEqual(10);
|
|
623
|
+
const pages = await captureExportPages(page, item.view, item.limit, async () => {
|
|
624
|
+
await page.getByRole("button", { name: "Exportar Excel" }).click();
|
|
625
|
+
});
|
|
626
|
+
assertCompleteExport(pages);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function verifyCompleteWorkListExport(page: Page) {
|
|
631
|
+
await page
|
|
632
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
633
|
+
.getByRole("button", { name: /Operação/ })
|
|
634
|
+
.click();
|
|
635
|
+
await page
|
|
636
|
+
.getByRole("navigation", { name: "Entidades do ciclo de trabalho" })
|
|
637
|
+
.getByRole("button", { exact: true, name: "Visão de Lista" })
|
|
638
|
+
.click();
|
|
639
|
+
const list = page.locator(".operations-work-list");
|
|
640
|
+
await expect(list).toBeVisible();
|
|
641
|
+
await expect(list.locator("table.ui-table tbody tr")).toHaveCount(10, { timeout: 10_000 });
|
|
642
|
+
const pages = await captureExportPages(page, "operations_work_list", 100, async () => {
|
|
643
|
+
await page.getByRole("button", { name: "Exportar Excel" }).click();
|
|
644
|
+
});
|
|
645
|
+
assertCompleteExport(pages);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function captureExportPages(
|
|
649
|
+
page: Page,
|
|
650
|
+
view: string,
|
|
651
|
+
limit: number,
|
|
652
|
+
action: () => Promise<void>,
|
|
653
|
+
): Promise<SqlitePage[]> {
|
|
654
|
+
const pages: SqlitePage[] = [];
|
|
655
|
+
const closedBefore = await exportCloseCount(page);
|
|
656
|
+
const onResponse = async (response: import("@playwright/test").Response) => {
|
|
657
|
+
const url = new URL(response.url());
|
|
658
|
+
if (response.status() !== 200 || url.pathname !== "/api/v2/sqlite-views") return;
|
|
659
|
+
if (url.searchParams.get("view") !== view || url.searchParams.get("limit") !== String(limit))
|
|
660
|
+
return;
|
|
661
|
+
pages.push((await response.json()) as SqlitePage);
|
|
662
|
+
};
|
|
663
|
+
page.on("response", onResponse);
|
|
664
|
+
try {
|
|
665
|
+
await action();
|
|
666
|
+
await expect.poll(() => pages.some((entry) => !entry.has_more), { timeout: 60_000 }).toBe(true);
|
|
667
|
+
await expect
|
|
668
|
+
.poll(() => exportCloseCount(page), { timeout: 60_000 })
|
|
669
|
+
.toBeGreaterThan(closedBefore);
|
|
670
|
+
return pages;
|
|
671
|
+
} finally {
|
|
672
|
+
page.off("response", onResponse);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function assertCompleteExport(pages: SqlitePage[]) {
|
|
677
|
+
expect(pages.length).toBeGreaterThan(0);
|
|
678
|
+
const [firstPage] = pages;
|
|
679
|
+
if (firstPage === undefined) throw new Error("Export did not return a page.");
|
|
680
|
+
const total = firstPage.total_count;
|
|
681
|
+
expect(pages.every((page) => page.total_count === total)).toBe(true);
|
|
682
|
+
let expectedOffset = 0;
|
|
683
|
+
const identities = new Set<string>();
|
|
684
|
+
for (const page of pages) {
|
|
685
|
+
expect(page.offset).toBe(expectedOffset);
|
|
686
|
+
expectedOffset += page.rows.length;
|
|
687
|
+
for (const row of page.rows) {
|
|
688
|
+
const identity = exportRowIdentity(row);
|
|
689
|
+
expect(identities.has(identity), `duplicate exported row ${identity}`).toBe(false);
|
|
690
|
+
identities.add(identity);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
expect(pages.reduce((sum, page) => sum + page.rows.length, 0)).toBe(total);
|
|
694
|
+
expect(identities.size).toBe(total);
|
|
695
|
+
expect(pages.at(-1)?.has_more).toBe(false);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function exportRowIdentity(row: Record<string, unknown>): string {
|
|
699
|
+
for (const key of [
|
|
700
|
+
"agent_invocation_id",
|
|
701
|
+
"execution_id",
|
|
702
|
+
"conversation_id",
|
|
703
|
+
"task_id",
|
|
704
|
+
"spec_id",
|
|
705
|
+
"prd_id",
|
|
706
|
+
"period_key",
|
|
707
|
+
"key",
|
|
708
|
+
"id",
|
|
709
|
+
]) {
|
|
710
|
+
const value = row[key];
|
|
711
|
+
if (typeof value === "string" && value.length > 0) return `${key}:${value}`;
|
|
712
|
+
}
|
|
713
|
+
return JSON.stringify(row);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function exportCloseCount(page: Page): Promise<number> {
|
|
717
|
+
return page.evaluate(
|
|
718
|
+
() => (window as Window & { __zaipExportCloseCount?: number }).__zaipExportCloseCount ?? 0,
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async function installExportSink(page: Page) {
|
|
723
|
+
await page.addInitScript(() => {
|
|
724
|
+
(window as Window & { __zaipExportCloseCount?: number }).__zaipExportCloseCount = 0;
|
|
725
|
+
Object.assign(window, {
|
|
726
|
+
showSaveFilePicker: async ({ suggestedName }: { suggestedName: string }) => {
|
|
727
|
+
const writer = {
|
|
728
|
+
abort: async () => undefined,
|
|
729
|
+
close: async () => {
|
|
730
|
+
const state = window as Window & { __zaipExportCloseCount?: number };
|
|
731
|
+
state.__zaipExportCloseCount = (state.__zaipExportCloseCount ?? 0) + 1;
|
|
732
|
+
},
|
|
733
|
+
ready: Promise.resolve(),
|
|
734
|
+
write: async () => undefined,
|
|
735
|
+
};
|
|
736
|
+
return {
|
|
737
|
+
name: suggestedName,
|
|
738
|
+
createWritable: async () => ({ ...writer, getWriter: () => writer }),
|
|
739
|
+
};
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
}
|