@zaip-ai/cli 1.0.7 → 1.0.8
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 +6 -4
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.d.ts.map +1 -1
- package/dist/services/adapters.js +15 -5
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/adapters.test.js +1 -1
- package/dist/services/agent-adapters.d.ts +9 -0
- package/dist/services/agent-adapters.d.ts.map +1 -1
- package/dist/services/agent-adapters.js +38 -3
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +8 -1
- package/dist/services/agent-adapters.test.js.map +1 -1
- package/dist/services/codex-config-template.d.ts +2 -1
- package/dist/services/codex-config-template.d.ts.map +1 -1
- package/dist/services/codex-config-template.js +13 -2
- package/dist/services/codex-config-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 +31 -7
- package/dist/services/codex-rules.js.map +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
- package/dist/services/codex-subagent-hook-template.js +4 -1
- package/dist/services/codex-subagent-hook-template.js.map +1 -1
- package/dist/services/doctor.d.ts.map +1 -1
- package/dist/services/doctor.js +3 -1
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/global-codex-config.d.ts +6 -3
- package/dist/services/global-codex-config.d.ts.map +1 -1
- package/dist/services/global-codex-config.js +82 -13
- package/dist/services/global-codex-config.js.map +1 -1
- package/dist/services/global-codex-config.test.js +57 -10
- package/dist/services/global-codex-config.test.js.map +1 -1
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +15 -2
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/logger.js +1 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +3 -3
- package/dist/services/logger.test.js.map +1 -1
- package/dist/services/runtime-template.test.js +21 -5
- 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 +2 -0
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/config/agents/catalog.yaml +310 -10
- package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
- package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
- package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
- package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
- package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
- package/templates/.agents/config/project.yaml +1 -1
- package/templates/.agents/dashboard/.zaip-managed.json +659 -163
- package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
- package/templates/.agents/dashboard/README.md +48 -97
- package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-Z60df2r-.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-Cv-JcoxP.js → DashboardHelpDialog-Cs6dzDmW.js} +294 -109
- package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-Coc6pA-1.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-DFBuc_qp.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-CCQOHdVI.js +9 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-pC2_0J8x.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +952 -595
- package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
- package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
- package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
- package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
- package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
- package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
- package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
- package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
- package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +257 -313
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +149 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +35 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +111 -12
- package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +70 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +111 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +432 -13
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
- package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +24 -0
- package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +471 -2
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +44 -0
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +196 -39
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
- package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +187 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +190 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +121 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +398 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
- package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +37 -0
- package/templates/.agents/dashboard/apps/frontend/vite.config.ts +55 -5
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
- package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +64 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +275 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +100 -15
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +131 -40
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +303 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +226 -6
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +479 -402
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +312 -72
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +373 -321
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +52 -14
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +32 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +108 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +60 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +106 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -137
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +956 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +775 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +95 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
- package/templates/.agents/dashboard/modules/operations/package.json +2 -1
- package/templates/.agents/dashboard/modules/operations/src/contracts.ts +182 -0
- package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
- package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
- package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
- package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +325 -18
- package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
- package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +365 -14
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +532 -131
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +314 -358
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +68 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +51 -8
- package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
- package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
- package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
- package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +99 -42
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +255 -34
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +163 -8
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +101 -68
- package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
- package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -16
- package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +50 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +143 -9
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +29 -4
- package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
- package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
- package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
- package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +65 -5
- package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +19 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +136 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +24 -7
- package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +165 -6
- package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
- package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
- package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
- package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
- package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +5 -3
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +4 -83
- package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
- package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
- package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
- package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
- package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
- package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
- package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +27 -2
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +22 -13
- package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
- package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
- package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
- package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
- package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
- package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +324 -0
- package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
- package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
- package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
- package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -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 +1756 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1608 -0
- package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +12 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +83 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +28 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +262 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +41 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +105 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1643 -107
- package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +335 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +71 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +179 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +163 -84
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +145 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +551 -266
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1145 -308
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +708 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +2232 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +6151 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +437 -190
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +299 -24
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
- package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
- package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
- package/templates/.agents/dashboard/read-model.dbml +2201 -0
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +214 -41
- package/templates/.agents/dashboard/work-priority.json +34 -0
- package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
- package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
- 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/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +24 -0
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +4 -3
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +9 -6
- 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 +9 -3
- package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
- package/templates/.agents/rules/autonomy-decision.md +15 -0
- package/templates/.agents/rules/core.md +1 -1
- package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
- package/templates/.agents/rules/specialist-routing.md +19 -0
- package/templates/.agents/rules/workflow.md +1 -1
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +114 -64
- package/templates/.agents/runtime/checksums.sha256 +6 -4
- package/templates/.agents/runtime/manifest.json +3 -1
- package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
- package/templates/.agents/runtime/provenance.json +11 -0
- package/templates/.agents/runtime/sbom.cdx.json +1929 -39
- package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
- package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
- package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
- package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
- package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
- package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
- package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
- package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
- package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
- package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
- package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
- package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
- package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
- package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +3 -3
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +76 -27
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +128 -24
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +122 -4
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +66 -19
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +193 -7
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +14 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +18 -0
- package/templates/.agents/zaip-aios/INDEX.md +1 -0
- package/templates/.agents/zaip-aios/README.md +37 -8
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
- package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
- package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
- package/templates/template-manifest.json +945 -214
- package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
- package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
- package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
- package/templates/.agents/dashboard/dashboard.config.json +0 -4
- package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
|
@@ -1,49 +1,125 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
2
|
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { resolve } from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
5
|
import { serve } from "@hono/node-server";
|
|
8
6
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
9
7
|
import { loadWorkspaceLayout, WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
|
|
10
8
|
import { fxRateSchema, PricingRepository, pricingCatalogSchema } from "@zaip-ai/costs";
|
|
11
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
buildCostSummaryProjection,
|
|
11
|
+
type CostSummarySource,
|
|
12
|
+
} from "@zaip-ai/dashboard-cost-monitor/server";
|
|
13
|
+
import type { OperationsProjection } from "@zaip-ai/dashboard-operations/contracts";
|
|
12
14
|
import {
|
|
13
15
|
buildOperationsProjection,
|
|
14
|
-
|
|
16
|
+
buildQuickOperationsProjection,
|
|
15
17
|
readPrdDetail,
|
|
16
18
|
readSpecDetail,
|
|
17
19
|
readTaskDetail,
|
|
18
|
-
recalculateOperationsRunSummary,
|
|
19
20
|
} from "@zaip-ai/dashboard-operations/server";
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
buildTaskTrace,
|
|
23
|
+
buildTraceStageRecords,
|
|
24
|
+
classifyTraceDocument,
|
|
25
|
+
timestampsFromContent,
|
|
26
|
+
} from "@zaip-ai/dashboard-traceability/server";
|
|
27
|
+
import type {
|
|
28
|
+
TaskTrace,
|
|
29
|
+
TraceDocument,
|
|
30
|
+
} from "../../../modules/traceability/src/contracts.js";
|
|
31
|
+
import { type CompactRunMetric, LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
|
|
21
32
|
import { type Context, Hono } from "hono";
|
|
22
33
|
import { getCookie, setCookie } from "hono/cookie";
|
|
23
34
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
35
|
+
import { runAgentAdapterSync } from "./agent-adapter-sync.js";
|
|
24
36
|
import {
|
|
25
37
|
type AgentAdapterSyncResult,
|
|
26
38
|
AgentCatalogUpdateError,
|
|
27
39
|
persistAgentCatalogUpdate,
|
|
28
40
|
readAgentCatalogSettings,
|
|
29
41
|
} from "./agent-catalog-settings.js";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
42
|
+
import { persistProjectSettings, readProjectOverview, readProjectSettings } from "./project-settings.js";
|
|
43
|
+
import {
|
|
44
|
+
type BoardClassification,
|
|
45
|
+
boardFilters,
|
|
46
|
+
boardPage,
|
|
47
|
+
boardStage,
|
|
48
|
+
classificationPage,
|
|
49
|
+
} from "./server-board.js";
|
|
50
|
+
import { registerDailyReadModelRoute } from "./server-daily-read-models-route.js";
|
|
51
|
+
import {
|
|
52
|
+
changeEntityPageRows,
|
|
53
|
+
decisionEntityPageRows,
|
|
54
|
+
filterLifecyclePageRows,
|
|
55
|
+
operationsPageFilters,
|
|
56
|
+
} from "./server-operations-pages.js";
|
|
57
|
+
import { registerOperationsRunsRoute } from "./server-operations-runs-route.js";
|
|
58
|
+
import { projectionPeriodFilter } from "./server-period.js";
|
|
32
59
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
60
|
+
buildCanonicalOperationsProjection,
|
|
61
|
+
buildCurrentCostProjection,
|
|
62
|
+
buildManifestCostRunPage,
|
|
63
|
+
buildManifestFirstCostProjection,
|
|
64
|
+
filterCostRunRows,
|
|
65
|
+
isUnfilteredCostRequest,
|
|
66
|
+
knownWorkIds,
|
|
67
|
+
normalizeWorkPriority,
|
|
68
|
+
pageCostRunRows,
|
|
69
|
+
readCurrentFxRate,
|
|
70
|
+
readWorkPriority,
|
|
71
|
+
} from "./server-projections.js";
|
|
72
|
+
import { queryValues, selectedCurrency } from "./server-summary-helpers.js";
|
|
73
|
+
import { dashboardIdentity, dashboardWorkspaceKey, reconcileOpenWork } from "./server-workspace.js";
|
|
74
|
+
import {
|
|
75
|
+
rebuildSqliteReadModel,
|
|
76
|
+
SqliteReadModelFileSystem,
|
|
77
|
+
sqliteViewMetadata,
|
|
78
|
+
} from "./sqlite-read-model.js";
|
|
38
79
|
|
|
39
80
|
export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
|
|
81
|
+
export { projectionPeriodFilter } from "./server-period.js";
|
|
82
|
+
export {
|
|
83
|
+
dashboardWorkspaceKey,
|
|
84
|
+
githubRepositoryFromRemote,
|
|
85
|
+
type ReconciliationItem,
|
|
86
|
+
type ReconciliationResult,
|
|
87
|
+
reconciliationResult,
|
|
88
|
+
} from "./server-workspace.js";
|
|
89
|
+
export {
|
|
90
|
+
rebuildSqliteReadModel,
|
|
91
|
+
SqliteReadModelFileSystem,
|
|
92
|
+
validateSqliteReadModelSchema,
|
|
93
|
+
} from "./sqlite-read-model.js";
|
|
94
|
+
|
|
95
|
+
type AllTimeTopTaskFact = {
|
|
96
|
+
task_id: string;
|
|
97
|
+
total_duration_ms: number | null;
|
|
98
|
+
total_tokens: number | null;
|
|
99
|
+
total_cost_usd: number | null;
|
|
100
|
+
total_cost_brl: number | null;
|
|
101
|
+
total_run_count: number;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export function projectAllTimeTopTasks(
|
|
105
|
+
tasks: readonly AllTimeTopTaskFact[],
|
|
106
|
+
): NonNullable<NonNullable<OperationsProjection["summary"]["overview_metrics"]>["top_tasks"]> {
|
|
107
|
+
return tasks.map((task) => ({
|
|
108
|
+
task_id: task.task_id,
|
|
109
|
+
duration_ms: task.total_duration_ms,
|
|
110
|
+
total_tokens: task.total_tokens,
|
|
111
|
+
total_cost_usd: task.total_cost_usd,
|
|
112
|
+
cost_brl: task.total_cost_brl,
|
|
113
|
+
run_count: task.total_run_count,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
40
116
|
|
|
41
117
|
const API_PREFIX = "/api/v2";
|
|
42
118
|
const HOST = "127.0.0.1";
|
|
43
|
-
const RUNTIME_KIND = "zaip-dashboard-
|
|
44
|
-
const SESSION_COOKIE = "zaip-dashboard-session";
|
|
45
|
-
const
|
|
46
|
-
const
|
|
119
|
+
const RUNTIME_KIND = "zaip-dashboard-sqlite-runtime";
|
|
120
|
+
const SESSION_COOKIE = "zaip-dashboard-sqlite-session";
|
|
121
|
+
const WORK_PRIORITY_PATH = ".agents/dashboard/work-priority.json";
|
|
122
|
+
const SNAPSHOT_CACHE_TTL_MS = 5_000;
|
|
47
123
|
const agentPortraitFiles: Record<string, string> = {
|
|
48
124
|
"agent-orchestrator": "1 - Dave - Product Owner.png",
|
|
49
125
|
"agent-builder": "2 - Rafa - Tech Lead.png",
|
|
@@ -87,6 +163,13 @@ export function openWorkIds(items: readonly ActiveWorkItem[]): string[] {
|
|
|
87
163
|
|
|
88
164
|
interface DashboardAppOptions {
|
|
89
165
|
syncAgentAdapters?: () => Promise<AgentAdapterSyncResult>;
|
|
166
|
+
filesystem?: WorkspaceFileSystem;
|
|
167
|
+
canonicalFilesystem?: WorkspaceFileSystem;
|
|
168
|
+
/** Test seam for the disposable SQLite serving mode. */
|
|
169
|
+
snapshotReadOnly?: boolean;
|
|
170
|
+
reconcileOpenWork?: typeof reconcileOpenWork;
|
|
171
|
+
refreshReadModel?: () => Promise<void>;
|
|
172
|
+
snapshotState?: "current" | "updating";
|
|
90
173
|
}
|
|
91
174
|
|
|
92
175
|
class V2WorkspaceRequiredError extends Error {
|
|
@@ -126,164 +209,521 @@ function shouldBootstrapBrowserSession(context: Context): boolean {
|
|
|
126
209
|
return context.req.header("sec-fetch-site") !== "cross-site";
|
|
127
210
|
}
|
|
128
211
|
|
|
129
|
-
async function
|
|
212
|
+
async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
|
|
213
|
+
const layout = await loadWorkspaceLayout(filesystem);
|
|
214
|
+
if (layout.version === 1) {
|
|
215
|
+
throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
|
|
216
|
+
}
|
|
217
|
+
return layout;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function headOrJson(
|
|
221
|
+
context: Context,
|
|
222
|
+
payload: unknown,
|
|
223
|
+
status: ContentfulStatusCode = 200,
|
|
224
|
+
cacheable = false,
|
|
225
|
+
): Response {
|
|
226
|
+
if (cacheable) {
|
|
227
|
+
const etag = `"${createHash("sha256").update(JSON.stringify(payload)).digest("base64url")}"`;
|
|
228
|
+
context.header("ETag", etag);
|
|
229
|
+
context.header("Cache-Control", "private, no-cache");
|
|
230
|
+
if (context.req.header("if-none-match") === etag) return context.body(null, 304);
|
|
231
|
+
}
|
|
232
|
+
if (context.req.method === "HEAD") return context.body(null, status);
|
|
233
|
+
return context.json(payload, status);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Keep the entity/macro details on the snapshot filesystem, while making the
|
|
238
|
+
* first-fold counters and token/cost facts come from SQL.
|
|
239
|
+
* Duration intentionally retains the shared hybrid legacy policy in the
|
|
240
|
+
* Operations module; its underlying daily rows are still SQLite-backed.
|
|
241
|
+
*/
|
|
242
|
+
async function buildFirstFoldQuickProjection(
|
|
130
243
|
filesystem: WorkspaceFileSystem,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
244
|
+
layout: WorkspaceLayout,
|
|
245
|
+
filter: { from: string | null; to: string | null },
|
|
246
|
+
options: {
|
|
247
|
+
includeBoardTasks?: boolean;
|
|
248
|
+
includeExtended?: boolean;
|
|
249
|
+
includePeriods?: boolean;
|
|
250
|
+
includeTopTasks?: boolean;
|
|
251
|
+
} = {},
|
|
252
|
+
): Promise<OperationsProjection> {
|
|
253
|
+
// The comparison runtime deliberately has no lifecycle daily aggregates in
|
|
254
|
+
// its snapshot. Its immutable filesystem contains only canonical records,
|
|
255
|
+
// while the SQL views supply the first-fold facts. Never enter the legacy
|
|
256
|
+
// quick projection here: it requires lifecycle daily read models and would
|
|
257
|
+
// make an otherwise valid SQLite dashboard fail on first paint.
|
|
258
|
+
const projection =
|
|
259
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
260
|
+
? buildSqliteOperationsProjection(filesystem, layout, filter, options)
|
|
261
|
+
: await buildQuickOperationsProjection(filesystem, layout, filter, options);
|
|
262
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem)) return projection;
|
|
263
|
+
const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
|
|
264
|
+
const aggregate =
|
|
265
|
+
filter.from === null && filter.to === null
|
|
266
|
+
? filesystem.firstFoldViewportAllTime().aggregate
|
|
267
|
+
: cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
|
|
268
|
+
return {
|
|
269
|
+
...projection,
|
|
270
|
+
summary: {
|
|
271
|
+
...projection.summary,
|
|
272
|
+
run_count: aggregate.total_run_count,
|
|
273
|
+
overview_metrics: {
|
|
274
|
+
...projection.summary.overview_metrics,
|
|
275
|
+
duration_ms: aggregate.duration_ms,
|
|
276
|
+
tokens: aggregate.total_tokens,
|
|
277
|
+
cost_brl: aggregate.cost_brl,
|
|
278
|
+
execution_day_count: aggregate.execution_day_count,
|
|
279
|
+
data_source: "run_records",
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* SQLite's files snapshot intentionally excludes derived lifecycle manifests.
|
|
287
|
+
* Build the Operations first fold from its canonical SQL views instead of
|
|
288
|
+
* treating a missing manifest as a broken workspace.
|
|
289
|
+
*/
|
|
290
|
+
function buildSqliteOperationsProjection(
|
|
291
|
+
filesystem: SqliteReadModelFileSystem,
|
|
292
|
+
layout: WorkspaceLayout,
|
|
293
|
+
filter: { from: string | null; to: string | null },
|
|
294
|
+
options: {
|
|
295
|
+
includeBoardTasks?: boolean;
|
|
296
|
+
includeExtended?: boolean;
|
|
297
|
+
includePeriods?: boolean;
|
|
298
|
+
includeTopTasks?: boolean;
|
|
299
|
+
},
|
|
300
|
+
): OperationsProjection {
|
|
301
|
+
const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
|
|
302
|
+
const fullAllTime =
|
|
303
|
+
filter.from === null && filter.to === null
|
|
304
|
+
? options.includeExtended === true
|
|
305
|
+
? filesystem.firstFoldAllTime()
|
|
306
|
+
: null
|
|
307
|
+
: null;
|
|
308
|
+
const allTime =
|
|
309
|
+
fullAllTime ??
|
|
310
|
+
(filter.from === null && filter.to === null ? filesystem.firstFoldViewportAllTime() : null);
|
|
311
|
+
const page = <T extends "task" | "spec" | "prd">(kind: T) =>
|
|
312
|
+
cachedMonthlyQuick?.pages[kind] ?? filesystem.summaryPage(kind, filter, 0, 10_000).rows;
|
|
313
|
+
const includeEntities = allTime === null || options.includeBoardTasks === true;
|
|
314
|
+
const tasks = includeEntities ? page("task") : [];
|
|
315
|
+
const specs = includeEntities ? page("spec") : [];
|
|
316
|
+
const prds = includeEntities ? page("prd") : [];
|
|
317
|
+
const aggregate = allTime?.aggregate ?? cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
|
|
318
|
+
const taskCount = allTime?.aggregate.task_count ?? tasks.length;
|
|
319
|
+
const allTimeTopTasks =
|
|
320
|
+
filter.from === null && filter.to === null &&
|
|
321
|
+
(fullAllTime !== null || options.includeTopTasks === true)
|
|
322
|
+
? (fullAllTime?.top_tasks ?? filesystem.firstFoldTopTasksAllTime())
|
|
323
|
+
: null;
|
|
324
|
+
const topTasks =
|
|
325
|
+
allTimeTopTasks === null
|
|
326
|
+
? (cachedMonthlyQuick?.top_tasks ?? filesystem.topTasks(filter)).map((task) => ({
|
|
327
|
+
task_id: task.task_id,
|
|
328
|
+
duration_ms: task.duration_ms,
|
|
329
|
+
total_tokens: task.total_tokens,
|
|
330
|
+
cost_brl: task.cost_brl,
|
|
331
|
+
run_count: task.run_count,
|
|
332
|
+
}))
|
|
333
|
+
: projectAllTimeTopTasks(allTimeTopTasks);
|
|
334
|
+
const macroMetrics =
|
|
335
|
+
fullAllTime === null
|
|
336
|
+
? undefined
|
|
337
|
+
: Object.fromEntries(
|
|
338
|
+
(["specs", "prds"] as const).map((kind) => {
|
|
339
|
+
const metric = fullAllTime.macro_metrics[kind];
|
|
340
|
+
return [
|
|
341
|
+
kind,
|
|
342
|
+
{
|
|
343
|
+
status_counts: metric.status_counts,
|
|
344
|
+
classification_counts: metric.classification_counts,
|
|
345
|
+
entity_count: metric.entity_count,
|
|
346
|
+
total_run_count: metric.total_run_count,
|
|
347
|
+
total_duration_ms: metric.total_duration_ms,
|
|
348
|
+
total_tokens: metric.total_tokens,
|
|
349
|
+
total_cost_usd: metric.total_cost_usd,
|
|
350
|
+
total_cost_brl: metric.total_cost_brl,
|
|
351
|
+
average_duration_ms: metric.average_duration_ms,
|
|
352
|
+
average_tokens: metric.average_tokens,
|
|
353
|
+
average_cost_usd: metric.average_cost_usd,
|
|
354
|
+
average_cost_brl: metric.average_cost_brl,
|
|
355
|
+
latest_created_at: metric.latest_created_at,
|
|
356
|
+
top_entities: metric.top_entities.map((entity) => ({
|
|
357
|
+
entity_id: entity.entity_id,
|
|
358
|
+
title: entity.title,
|
|
359
|
+
duration_ms: entity.total_duration_ms,
|
|
360
|
+
total_tokens: entity.total_tokens,
|
|
361
|
+
total_cost_usd: entity.total_cost_usd,
|
|
362
|
+
cost_brl: entity.total_cost_brl,
|
|
363
|
+
run_count: entity.total_run_count,
|
|
364
|
+
duration_share: entity.duration_share,
|
|
365
|
+
})),
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
}),
|
|
369
|
+
) as NonNullable<
|
|
370
|
+
NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
|
|
371
|
+
>;
|
|
372
|
+
const statuses: Record<string, number> = allTime?.task_status_counts ?? {};
|
|
373
|
+
const risks: Record<string, number> = allTime?.task_risk_counts ?? {};
|
|
374
|
+
if (allTime === null) {
|
|
375
|
+
for (const task of tasks) {
|
|
376
|
+
const status = typeof task.status === "string" ? task.status : "draft";
|
|
377
|
+
const risk = typeof task.risk === "string" ? task.risk : "standard";
|
|
378
|
+
statuses[status] = (statuses[status] ?? 0) + 1;
|
|
379
|
+
risks[risk] = (risks[risk] ?? 0) + 1;
|
|
145
380
|
}
|
|
146
|
-
} catch {
|
|
147
|
-
// A configuração visual nunca deve impedir a inicialização do dashboard.
|
|
148
381
|
}
|
|
149
|
-
|
|
382
|
+
const latest =
|
|
383
|
+
allTime?.aggregate.latest_task_created_at ??
|
|
384
|
+
[...tasks, ...specs, ...prds]
|
|
385
|
+
.map((row) => (typeof row.occurred_at === "string" ? row.occurred_at : null))
|
|
386
|
+
.filter((value): value is string => value !== null)
|
|
387
|
+
.sort()
|
|
388
|
+
.at(-1) ??
|
|
389
|
+
null;
|
|
390
|
+
const unavailableUsage = {
|
|
391
|
+
events: 0,
|
|
392
|
+
tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
|
|
393
|
+
cost: { usd: null, brl: null },
|
|
394
|
+
coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
|
|
395
|
+
};
|
|
396
|
+
const totalUsage = {
|
|
397
|
+
...unavailableUsage,
|
|
398
|
+
tokens: { ...unavailableUsage.tokens, total: aggregate.total_tokens },
|
|
399
|
+
cost: { ...unavailableUsage.cost, brl: aggregate.cost_brl },
|
|
400
|
+
};
|
|
401
|
+
const usage = {
|
|
402
|
+
conversation: unavailableUsage,
|
|
403
|
+
planning: unavailableUsage,
|
|
404
|
+
execution: unavailableUsage,
|
|
405
|
+
total: totalUsage,
|
|
406
|
+
new_work: unavailableUsage,
|
|
407
|
+
rework: unavailableUsage,
|
|
408
|
+
};
|
|
409
|
+
const dailyPeriods = options.includePeriods === true ? filesystem.dailyPeriods(filter) : {};
|
|
410
|
+
const hasUnknownUsage = Object.values(dailyPeriods).some(
|
|
411
|
+
(period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
|
|
412
|
+
);
|
|
413
|
+
const taskProjection = (row: Record<string, unknown>) => ({
|
|
414
|
+
schema_version: 2 as const,
|
|
415
|
+
id: String(row.id),
|
|
416
|
+
revision: 1,
|
|
417
|
+
change_id: "",
|
|
418
|
+
title: String(row.title ?? row.id),
|
|
419
|
+
short_name: String(row.title ?? row.id),
|
|
420
|
+
objective: "",
|
|
421
|
+
status: normalizeTaskStatus(row.status),
|
|
422
|
+
attempt_count: null,
|
|
423
|
+
risk: normalizeRisk(row.risk),
|
|
424
|
+
priority: "medium" as const,
|
|
425
|
+
owner: null,
|
|
426
|
+
requirement_ids: [],
|
|
427
|
+
acceptance_ids: [],
|
|
428
|
+
depends_on: [],
|
|
429
|
+
write_scopes: [],
|
|
430
|
+
created_at: String(row.occurred_at),
|
|
431
|
+
updated_at: String(row.occurred_at),
|
|
432
|
+
started_at: null,
|
|
433
|
+
finished_at: null,
|
|
434
|
+
result: null,
|
|
435
|
+
artifact_path: ".agents/records",
|
|
436
|
+
progress: 0,
|
|
437
|
+
spec_id: null,
|
|
438
|
+
execution_started: false,
|
|
439
|
+
telemetry: {
|
|
440
|
+
duration_ms: nullableMetric(row.total_duration),
|
|
441
|
+
tokens: nullableMetric(row.total_tokens),
|
|
442
|
+
cost_brl: nullableMetric(row.total_cost_brl),
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
const macroProjection = (row: Record<string, unknown>, kind: "prd" | "spec") => {
|
|
446
|
+
const entityUsage = {
|
|
447
|
+
...unavailableUsage,
|
|
448
|
+
tokens: { ...unavailableUsage.tokens, total: nullableMetric(row.total_tokens) },
|
|
449
|
+
cost: { ...unavailableUsage.cost, brl: nullableMetric(row.total_cost_brl) },
|
|
450
|
+
};
|
|
150
451
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
452
|
+
id: String(row.id),
|
|
453
|
+
title: String(row.title ?? row.id),
|
|
454
|
+
created_at: String(row.occurred_at),
|
|
455
|
+
updated_at: String(row.occurred_at),
|
|
456
|
+
kind,
|
|
457
|
+
parent_id: null,
|
|
458
|
+
artifact_path: ".agents/records",
|
|
459
|
+
lifecycle_status: String(row.status ?? "draft"),
|
|
460
|
+
work_status: normalizeMacroStatus(row.status),
|
|
461
|
+
execution_ids: [],
|
|
462
|
+
task_ids: [],
|
|
463
|
+
planned_tasks: [],
|
|
464
|
+
canonical_run_ids: [],
|
|
465
|
+
run_ids: [],
|
|
466
|
+
root_execution_run_ids: [],
|
|
467
|
+
execution_count: 0,
|
|
468
|
+
task_count: 0,
|
|
469
|
+
run_count: nullableMetric(row.invocation_count) ?? 0,
|
|
470
|
+
duration_ms: nullableMetric(row.total_duration),
|
|
471
|
+
decisions: { human: 0, agent: 0 },
|
|
472
|
+
rework_run_count: 0,
|
|
473
|
+
quality: {
|
|
474
|
+
completed_tasks: 0,
|
|
475
|
+
task_completion_rate: 0,
|
|
476
|
+
terminal_runs: 0,
|
|
477
|
+
successful_runs: 0,
|
|
478
|
+
terminal_success_rate: 0,
|
|
479
|
+
rework_rate: 0,
|
|
480
|
+
},
|
|
481
|
+
usage: {
|
|
482
|
+
conversation: unavailableUsage,
|
|
483
|
+
planning: unavailableUsage,
|
|
484
|
+
execution: unavailableUsage,
|
|
485
|
+
total: entityUsage,
|
|
486
|
+
new_work: unavailableUsage,
|
|
487
|
+
rework: unavailableUsage,
|
|
488
|
+
},
|
|
153
489
|
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
schema_version: 2,
|
|
493
|
+
layout_version: layout.version as 2 | 3,
|
|
494
|
+
readonly: true,
|
|
495
|
+
source: ".agents/records",
|
|
496
|
+
filters: filter,
|
|
497
|
+
summary: {
|
|
498
|
+
prd_count: allTime?.aggregate.prd_count ?? prds.length,
|
|
499
|
+
spec_count: allTime?.aggregate.spec_count ?? specs.length,
|
|
500
|
+
execution_count: 0,
|
|
501
|
+
task_count: taskCount,
|
|
502
|
+
run_count: aggregate.total_run_count,
|
|
503
|
+
overview_metrics: {
|
|
504
|
+
duration_ms: aggregate.duration_ms,
|
|
505
|
+
execution_day_count: aggregate.execution_day_count,
|
|
506
|
+
tokens: aggregate.total_tokens,
|
|
507
|
+
cost_brl: aggregate.cost_brl,
|
|
508
|
+
prd_count: allTime?.aggregate.prd_count ?? prds.length,
|
|
509
|
+
spec_count: allTime?.aggregate.spec_count ?? specs.length,
|
|
510
|
+
selected_counts: {
|
|
511
|
+
prds: allTime?.aggregate.prd_count ?? prds.length,
|
|
512
|
+
specs: allTime?.aggregate.spec_count ?? specs.length,
|
|
513
|
+
},
|
|
514
|
+
task_count: taskCount,
|
|
515
|
+
completed_prd_count:
|
|
516
|
+
allTime?.aggregate.completed_prd_count ??
|
|
517
|
+
prds.filter((row) => row.status === "completed").length,
|
|
518
|
+
completed_spec_count:
|
|
519
|
+
allTime?.aggregate.completed_spec_count ??
|
|
520
|
+
specs.filter((row) => row.status === "completed").length,
|
|
521
|
+
completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
|
|
522
|
+
task_status_counts: statuses,
|
|
523
|
+
task_risk_counts: risks,
|
|
524
|
+
top_tasks: topTasks,
|
|
525
|
+
...(macroMetrics === undefined ? {} : { macro_metrics: macroMetrics }),
|
|
526
|
+
latest_task_created_at: latest,
|
|
527
|
+
coverage: { duration: 1, tokens: hasUnknownUsage ? 0 : 1, cost: hasUnknownUsage ? 0 : 1 },
|
|
528
|
+
data_source: "run_records",
|
|
529
|
+
partial: true,
|
|
530
|
+
},
|
|
531
|
+
decisions: { human: 0, agent: 0 },
|
|
532
|
+
source_latest_at: latest,
|
|
533
|
+
quality: {
|
|
534
|
+
completed_tasks: statuses.completed ?? 0,
|
|
535
|
+
task_completion_rate:
|
|
536
|
+
taskCount === 0
|
|
537
|
+
? 0
|
|
538
|
+
: (allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0) /
|
|
539
|
+
taskCount,
|
|
540
|
+
terminal_runs: 0,
|
|
541
|
+
successful_runs: 0,
|
|
542
|
+
terminal_success_rate: 0,
|
|
543
|
+
rework_rate: 0,
|
|
544
|
+
},
|
|
545
|
+
reconciliation: {
|
|
546
|
+
phase_tokens_match_total: true,
|
|
547
|
+
phase_usd_match_total: true,
|
|
548
|
+
phase_brl_match_total: true,
|
|
549
|
+
origin_tokens_match_total: true,
|
|
550
|
+
},
|
|
551
|
+
usage,
|
|
552
|
+
// The period tabs are part of the first-fold contract too. Supplying an
|
|
553
|
+
// empty object for the all-time fast path made Daily, Monthly and Annual
|
|
554
|
+
// look empty even though the SQLite daily aggregate already existed.
|
|
555
|
+
periods: { daily: options.includePeriods === true ? dailyPeriods : {} },
|
|
556
|
+
},
|
|
557
|
+
origins: { conversation: usage, task: usage, spec: usage, prd: usage },
|
|
558
|
+
prds: prds.map((row) => macroProjection(row, "prd")),
|
|
559
|
+
specs: specs.map((row) => macroProjection(row, "spec")),
|
|
560
|
+
tasks: options.includeBoardTasks ? tasks.map(taskProjection) : [],
|
|
561
|
+
changes: [],
|
|
562
|
+
executions: [],
|
|
563
|
+
runs: [],
|
|
564
|
+
decisions: [],
|
|
565
|
+
evidences: [],
|
|
566
|
+
validations: [],
|
|
567
|
+
};
|
|
166
568
|
}
|
|
167
569
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const match =
|
|
171
|
-
trimmed.match(/github\.com(?::|\/)([^/\s]+)\/([^/\s]+?)(?:\.git)?$/i) ??
|
|
172
|
-
trimmed.match(/^([^/:\s]+)\/([^/\s]+?)(?:\.git)?$/);
|
|
173
|
-
return match ? `${match[1]}/${match[2]}` : null;
|
|
570
|
+
function nullableMetric(value: unknown): number | null {
|
|
571
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
174
572
|
}
|
|
175
573
|
|
|
176
|
-
|
|
177
|
-
|
|
574
|
+
function normalizeTaskStatus(
|
|
575
|
+
value: unknown,
|
|
576
|
+
):
|
|
577
|
+
| "draft"
|
|
578
|
+
| "planned"
|
|
579
|
+
| "executing"
|
|
580
|
+
| "verifying"
|
|
581
|
+
| "completed"
|
|
582
|
+
| "failed"
|
|
583
|
+
| "blocked"
|
|
584
|
+
| "cancelled" {
|
|
585
|
+
return [
|
|
586
|
+
"draft",
|
|
587
|
+
"planned",
|
|
588
|
+
"executing",
|
|
589
|
+
"verifying",
|
|
590
|
+
"completed",
|
|
591
|
+
"failed",
|
|
592
|
+
"blocked",
|
|
593
|
+
"cancelled",
|
|
594
|
+
].includes(String(value))
|
|
595
|
+
? (String(value) as
|
|
596
|
+
| "draft"
|
|
597
|
+
| "planned"
|
|
598
|
+
| "executing"
|
|
599
|
+
| "verifying"
|
|
600
|
+
| "completed"
|
|
601
|
+
| "failed"
|
|
602
|
+
| "blocked"
|
|
603
|
+
| "cancelled")
|
|
604
|
+
: "draft";
|
|
178
605
|
}
|
|
179
606
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
607
|
+
function normalizeRisk(value: unknown): "lite" | "standard" | "critical" {
|
|
608
|
+
return ["lite", "standard", "critical"].includes(String(value))
|
|
609
|
+
? (String(value) as "lite" | "standard" | "critical")
|
|
610
|
+
: "standard";
|
|
183
611
|
}
|
|
184
612
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
613
|
+
function normalizeMacroStatus(
|
|
614
|
+
value: unknown,
|
|
615
|
+
): "planning" | "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled" {
|
|
616
|
+
const status = String(value);
|
|
617
|
+
if (["executing", "verifying", "completed", "blocked", "failed", "cancelled"].includes(status))
|
|
618
|
+
return status as "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled";
|
|
619
|
+
return "planning";
|
|
189
620
|
}
|
|
190
621
|
|
|
191
|
-
async function
|
|
622
|
+
async function firstFoldCostSource(
|
|
192
623
|
filesystem: WorkspaceFileSystem,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
624
|
+
filters: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0] = {
|
|
625
|
+
from: null,
|
|
626
|
+
to: null,
|
|
627
|
+
},
|
|
628
|
+
): Promise<CostSummarySource> {
|
|
629
|
+
if (filesystem instanceof SqliteReadModelFileSystem) {
|
|
630
|
+
return filesystem.costMonitorSummaryAggregate(filters) as CostSummarySource;
|
|
197
631
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
632
|
+
const daily = await new LifecycleManifestStore(filesystem).readDailyReadModels("overview");
|
|
633
|
+
if (daily === null) throw new Error("Lifecycle daily overview is not initialized");
|
|
634
|
+
return {
|
|
635
|
+
periods: {
|
|
636
|
+
daily: Object.fromEntries(
|
|
637
|
+
Object.entries(daily.days).map(([day, model]) => [day, model.overview]),
|
|
638
|
+
),
|
|
639
|
+
},
|
|
640
|
+
} satisfies CostSummarySource;
|
|
205
641
|
}
|
|
206
642
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
643
|
+
function buildSqliteTaskTrace(
|
|
644
|
+
filesystem: SqliteReadModelFileSystem,
|
|
645
|
+
layout: WorkspaceLayout,
|
|
646
|
+
taskId: string,
|
|
647
|
+
): TaskTrace {
|
|
648
|
+
const initial = filesystem.canonicalTraceFiles([taskId]);
|
|
649
|
+
const taskFile = initial.find(
|
|
650
|
+
(row) => row.path.includes("/.agents/records/tasks/") || row.path.includes(".agents/records/tasks/"),
|
|
651
|
+
);
|
|
652
|
+
if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
|
|
653
|
+
const scalar = (key: string): string | null => {
|
|
654
|
+
const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(taskFile.content)?.[1] ?? "";
|
|
655
|
+
const value = new RegExp(`^${key}:\\s*(.+?)\\s*$`, "m").exec(frontMatter)?.[1]?.trim();
|
|
656
|
+
if (value === undefined || value === "null") return null;
|
|
657
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
214
658
|
};
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
errors.forEach((candidate) => {
|
|
249
|
-
if (typeof candidate !== "object" || candidate === null) return;
|
|
250
|
-
const item = candidate as Record<string, unknown>;
|
|
251
|
-
remember(notReconciled, item.run_id, item.error);
|
|
252
|
-
});
|
|
253
|
-
const unresolved = Array.isArray(records.unresolved_running_run_ids)
|
|
254
|
-
? records.unresolved_running_run_ids
|
|
255
|
-
: [];
|
|
256
|
-
unresolved.forEach((id) => {
|
|
257
|
-
remember(notReconciled, id, "Invocation is still running.");
|
|
258
|
-
});
|
|
259
|
-
const entries = (items: Map<string, string | null>): ReconciliationItem[] =>
|
|
260
|
-
[...items.entries()]
|
|
261
|
-
.map(([id, reason]) => ({ id, reason }))
|
|
262
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
659
|
+
const relatedId = (key: string, prefix: string): string | null =>
|
|
660
|
+
scalar(key) ?? new RegExp(`\\b${prefix}[A-Z0-9-]+`).exec(taskFile.content)?.[0] ?? null;
|
|
661
|
+
const changeId = relatedId("change_id", "CHG-") ?? taskId;
|
|
662
|
+
const specId = relatedId("spec_id", "SPEC-");
|
|
663
|
+
const prdId = relatedId("prd_id", "PRD-");
|
|
664
|
+
const markers = [taskId, changeId, specId, prdId].filter(
|
|
665
|
+
(value): value is string => value !== null,
|
|
666
|
+
);
|
|
667
|
+
const documents = filesystem
|
|
668
|
+
.canonicalTraceFiles(markers)
|
|
669
|
+
.filter(
|
|
670
|
+
(row) =>
|
|
671
|
+
/\.(?:md|json|jsonl|txt|log)$/i.test(row.path) && row.content.length <= 250_000,
|
|
672
|
+
)
|
|
673
|
+
.map((row) => {
|
|
674
|
+
const classification = classifyTraceDocument(row.path);
|
|
675
|
+
const timestamps = timestampsFromContent(row.content);
|
|
676
|
+
return {
|
|
677
|
+
id: createHash("sha256").update(row.path).digest("hex").slice(0, 16),
|
|
678
|
+
path: row.path,
|
|
679
|
+
title:
|
|
680
|
+
row.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ??
|
|
681
|
+
row.path.split("/").at(-1) ??
|
|
682
|
+
row.path,
|
|
683
|
+
...classification,
|
|
684
|
+
content: row.content,
|
|
685
|
+
...timestamps,
|
|
686
|
+
} satisfies TraceDocument;
|
|
687
|
+
});
|
|
688
|
+
const stages = buildTraceStageRecords(documents);
|
|
689
|
+
const coveredStages = new Set(documents.map((document) => document.stage));
|
|
690
|
+
const risk = scalar("risk");
|
|
691
|
+
const status = scalar("status");
|
|
263
692
|
return {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
693
|
+
schema_version: 2,
|
|
694
|
+
layout_version: layout.version as 2 | 3,
|
|
695
|
+
readonly: true,
|
|
696
|
+
source: ".agents/records",
|
|
697
|
+
task_id: taskId,
|
|
698
|
+
task_title: scalar("title") ?? taskFile.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? taskId,
|
|
699
|
+
task_risk: risk === "lite" || risk === "standard" || risk === "critical" ? risk : null,
|
|
700
|
+
task_created_at: scalar("created_at"),
|
|
701
|
+
task_progress:
|
|
702
|
+
status === "completed"
|
|
703
|
+
? 100
|
|
704
|
+
: status === "verifying"
|
|
705
|
+
? 80
|
|
706
|
+
: status === "executing"
|
|
707
|
+
? 50
|
|
708
|
+
: status === "blocked" || status === "failed" || status === "cancelled"
|
|
709
|
+
? 0
|
|
710
|
+
: 10,
|
|
711
|
+
spec_id: specId,
|
|
712
|
+
prd_id: prdId,
|
|
713
|
+
change_id: changeId,
|
|
714
|
+
usage: filesystem.taskTraceUsage(taskId),
|
|
715
|
+
documents,
|
|
716
|
+
stages,
|
|
717
|
+
coverage: {
|
|
718
|
+
stages_with_documents: coveredStages.size,
|
|
719
|
+
stages_with_timestamps: stages.filter((stage) => stage.status === "recorded").length,
|
|
720
|
+
total_stages: 6,
|
|
721
|
+
evidence_count: documents.filter((document) => document.kind === "evidence").length,
|
|
722
|
+
test_count: documents.filter((document) => document.kind === "test").length,
|
|
723
|
+
},
|
|
267
724
|
};
|
|
268
725
|
}
|
|
269
726
|
|
|
270
|
-
async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
|
|
271
|
-
const layout = await loadWorkspaceLayout(filesystem);
|
|
272
|
-
if (layout.version === 1) {
|
|
273
|
-
throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
|
|
274
|
-
}
|
|
275
|
-
return layout;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function headOrJson(
|
|
279
|
-
context: Context,
|
|
280
|
-
payload: unknown,
|
|
281
|
-
status: ContentfulStatusCode = 200,
|
|
282
|
-
): Response {
|
|
283
|
-
if (context.req.method === "HEAD") return context.body(null, status);
|
|
284
|
-
return context.json(payload, status);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
727
|
export function createDashboardApp(
|
|
288
728
|
workspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd(),
|
|
289
729
|
sessionToken = defaultSessionToken(),
|
|
@@ -291,34 +731,92 @@ export function createDashboardApp(
|
|
|
291
731
|
options: DashboardAppOptions = {},
|
|
292
732
|
): Hono {
|
|
293
733
|
const app = new Hono();
|
|
294
|
-
const filesystem = new WorkspaceFileSystem(workspaceRoot);
|
|
734
|
+
const filesystem = options.filesystem ?? new WorkspaceFileSystem(workspaceRoot);
|
|
735
|
+
const isSqliteSnapshot = filesystem instanceof SqliteReadModelFileSystem;
|
|
736
|
+
// A private SQLite generation is immutable for this app instance. Loading
|
|
737
|
+
// its V2 layout repeatedly costs hundreds of milliseconds per navigation;
|
|
738
|
+
// canonical filesystems remain deliberately uncached.
|
|
739
|
+
const sqliteLayout = isSqliteSnapshot ? requireV2Layout(filesystem) : null;
|
|
740
|
+
const readLayout = () => sqliteLayout ?? requireV2Layout(filesystem);
|
|
741
|
+
const snapshotReadOnly = options.snapshotReadOnly ?? isSqliteSnapshot;
|
|
742
|
+
const snapshotState = options.snapshotState ?? "current";
|
|
743
|
+
const canonicalFilesystem = options.canonicalFilesystem ?? new WorkspaceFileSystem(workspaceRoot);
|
|
744
|
+
const reconcileOpen = options.reconcileOpenWork ?? reconcileOpenWork;
|
|
745
|
+
const refreshReadModel = options.refreshReadModel ?? (async () => {
|
|
746
|
+
if (!isSqliteSnapshot) return;
|
|
747
|
+
await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(workspaceRoot));
|
|
748
|
+
});
|
|
295
749
|
const workspaceKey = dashboardWorkspaceKey(workspaceRoot);
|
|
296
750
|
const syncAgentAdapters = options.syncAgentAdapters ?? (() => runAgentAdapterSync(workspaceRoot));
|
|
297
751
|
let catalogWriteTail = Promise.resolve();
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
752
|
+
type DashboardSnapshot = Awaited<ReturnType<typeof dashboardSnapshot>>;
|
|
753
|
+
const snapshotInFlight = new Map<string, Promise<DashboardSnapshot>>();
|
|
754
|
+
const cachedSnapshots = new Map<string, { snapshot: DashboardSnapshot; cachedAt: number }>();
|
|
755
|
+
const aggregatePayloads = new Map<string, { payload: unknown; cachedAt: number }>();
|
|
756
|
+
const aggregateInFlight = new Map<string, Promise<unknown>>();
|
|
757
|
+
const readAggregate = async <T>(
|
|
758
|
+
key: string,
|
|
759
|
+
build: () => Promise<T>,
|
|
760
|
+
fresh = false,
|
|
761
|
+
): Promise<T> => {
|
|
762
|
+
const cached = aggregatePayloads.get(key);
|
|
763
|
+
// Kanban cards are canonical projections. SQLite generations are immutable,
|
|
764
|
+
// so retain their expensive board projection until an explicit refresh
|
|
765
|
+
// clears this map rather than expiring it on the short snapshot TTL.
|
|
304
766
|
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
Date.now() - cachedSnapshotAt < 2_000
|
|
767
|
+
!fresh && cached !== undefined &&
|
|
768
|
+
(key.startsWith("operations:board:") || Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS)
|
|
308
769
|
)
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
770
|
+
return cached.payload as T;
|
|
771
|
+
const pending = aggregateInFlight.get(key);
|
|
772
|
+
// A bootstrap fresh read must not observe a normal request that began
|
|
773
|
+
// before reload. Wait for it to settle, then rebuild and replace cache.
|
|
774
|
+
if (pending !== undefined) {
|
|
775
|
+
if (!fresh) return pending as Promise<T>;
|
|
776
|
+
await pending;
|
|
777
|
+
return readAggregate(key, build, true);
|
|
778
|
+
}
|
|
779
|
+
const pendingBuild = build()
|
|
780
|
+
.then((payload) => {
|
|
781
|
+
aggregatePayloads.set(key, { payload, cachedAt: Date.now() });
|
|
782
|
+
return payload;
|
|
783
|
+
})
|
|
784
|
+
.finally(() => aggregateInFlight.delete(key));
|
|
785
|
+
aggregateInFlight.set(key, pendingBuild);
|
|
786
|
+
return pendingBuild;
|
|
787
|
+
};
|
|
788
|
+
const snapshotKey = (context: Context) => {
|
|
789
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
790
|
+
return `${selectedCurrency(context)}:${from ?? ""}:${to ?? ""}`;
|
|
791
|
+
};
|
|
792
|
+
const clearSnapshotCache = () => {
|
|
793
|
+
cachedSnapshots.clear();
|
|
794
|
+
aggregatePayloads.clear();
|
|
795
|
+
aggregateInFlight.clear();
|
|
796
|
+
};
|
|
797
|
+
const readSnapshot = (context: Context) => {
|
|
798
|
+
const key = snapshotKey(context);
|
|
799
|
+
const cached = cachedSnapshots.get(key);
|
|
800
|
+
if (cached !== undefined && Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS) {
|
|
801
|
+
context.header("X-ZAIP-Snapshot-Cache", "hit");
|
|
802
|
+
return Promise.resolve(cached.snapshot);
|
|
803
|
+
}
|
|
804
|
+
const pending = snapshotInFlight.get(key);
|
|
805
|
+
if (pending !== undefined) {
|
|
806
|
+
context.header("X-ZAIP-Snapshot-Cache", "inflight");
|
|
807
|
+
return pending;
|
|
808
|
+
}
|
|
809
|
+
context.header("X-ZAIP-Snapshot-Cache", "miss");
|
|
810
|
+
const inFlight = readLayout().then((layout) => dashboardSnapshot(filesystem, workspaceRoot, context, layout))
|
|
312
811
|
.then((snapshot) => {
|
|
313
|
-
|
|
314
|
-
cachedSnapshotKey = key;
|
|
315
|
-
cachedSnapshotAt = Date.now();
|
|
812
|
+
cachedSnapshots.set(key, { snapshot, cachedAt: Date.now() });
|
|
316
813
|
return snapshot;
|
|
317
814
|
})
|
|
318
815
|
.finally(() => {
|
|
319
|
-
snapshotInFlight
|
|
816
|
+
snapshotInFlight.delete(key);
|
|
320
817
|
});
|
|
321
|
-
|
|
818
|
+
snapshotInFlight.set(key, inFlight);
|
|
819
|
+
return inFlight;
|
|
322
820
|
};
|
|
323
821
|
|
|
324
822
|
app.use("*", async (context, next) => {
|
|
@@ -342,12 +840,18 @@ export function createDashboardApp(
|
|
|
342
840
|
app.use(`${API_PREFIX}/*`, async (context, next) => {
|
|
343
841
|
const settingsWrite =
|
|
344
842
|
context.req.method === "PUT" &&
|
|
345
|
-
(context.req.path === `${API_PREFIX}/
|
|
346
|
-
context.req.path === `${API_PREFIX}/
|
|
347
|
-
|
|
348
|
-
|
|
843
|
+
(context.req.path === `${API_PREFIX}/work-priority` ||
|
|
844
|
+
context.req.path === `${API_PREFIX}/project-settings` ||
|
|
845
|
+
(!snapshotReadOnly &&
|
|
846
|
+
(context.req.path === `${API_PREFIX}/pricing` ||
|
|
847
|
+
context.req.path === `${API_PREFIX}/fx-rate` ||
|
|
848
|
+
context.req.path === `${API_PREFIX}/agent-catalog`)));
|
|
849
|
+
// Reconciliation updates canonical lifecycle records through the supported
|
|
850
|
+
// CLI. It does not mutate the disposable SQLite read model, so it remains
|
|
851
|
+
// available while the dashboard serves a read-only snapshot.
|
|
349
852
|
const reconcileWrite =
|
|
350
|
-
context.req.method === "POST" &&
|
|
853
|
+
context.req.method === "POST" &&
|
|
854
|
+
context.req.path === `${API_PREFIX}/reconcile-open-work`;
|
|
351
855
|
if (
|
|
352
856
|
context.req.method !== "GET" &&
|
|
353
857
|
context.req.method !== "HEAD" &&
|
|
@@ -359,7 +863,8 @@ export function createDashboardApp(
|
|
|
359
863
|
context.req.path === `${API_PREFIX}/pricing` ||
|
|
360
864
|
context.req.path === `${API_PREFIX}/fx-rate` ||
|
|
361
865
|
context.req.path === `${API_PREFIX}/agent-catalog` ||
|
|
362
|
-
context.req.path === `${API_PREFIX}/project-settings`
|
|
866
|
+
context.req.path === `${API_PREFIX}/project-settings` ||
|
|
867
|
+
context.req.path === `${API_PREFIX}/work-priority`
|
|
363
868
|
? "GET, HEAD, PUT"
|
|
364
869
|
: context.req.path === `${API_PREFIX}/reconcile-open-work`
|
|
365
870
|
? "POST"
|
|
@@ -367,7 +872,8 @@ export function createDashboardApp(
|
|
|
367
872
|
);
|
|
368
873
|
return context.json({ error: "Dashboard API is read-only" }, 405);
|
|
369
874
|
}
|
|
370
|
-
if (context.req.path === `${API_PREFIX}/health`
|
|
875
|
+
if (context.req.path === `${API_PREFIX}/health` || context.req.path === `${API_PREFIX}/ready`)
|
|
876
|
+
return next();
|
|
371
877
|
|
|
372
878
|
const origin = context.req.header("origin");
|
|
373
879
|
if (origin !== undefined && !isAllowedOrigin(origin)) {
|
|
@@ -392,7 +898,7 @@ export function createDashboardApp(
|
|
|
392
898
|
|
|
393
899
|
app.on(["GET", "HEAD"], `${API_PREFIX}/health`, async (context) => {
|
|
394
900
|
try {
|
|
395
|
-
const layout = await
|
|
901
|
+
const layout = await readLayout();
|
|
396
902
|
return headOrJson(context, {
|
|
397
903
|
kind: RUNTIME_KIND,
|
|
398
904
|
workspaceKey,
|
|
@@ -401,6 +907,8 @@ export function createDashboardApp(
|
|
|
401
907
|
api_version: 2,
|
|
402
908
|
workspace_layout: layout.version,
|
|
403
909
|
read_only: true,
|
|
910
|
+
storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
|
|
911
|
+
snapshot_state: snapshotState,
|
|
404
912
|
});
|
|
405
913
|
} catch (error) {
|
|
406
914
|
return headOrJson(
|
|
@@ -419,17 +927,151 @@ export function createDashboardApp(
|
|
|
419
927
|
}
|
|
420
928
|
});
|
|
421
929
|
|
|
930
|
+
// Launcher readiness proves a canonical projection can be read; health alone
|
|
931
|
+
// intentionally remains a lightweight process/layout probe.
|
|
932
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/ready`, async (context) => {
|
|
933
|
+
try {
|
|
934
|
+
const layout = await readLayout();
|
|
935
|
+
if (isSqliteSnapshot) {
|
|
936
|
+
const metadata = filesystem.metadata();
|
|
937
|
+
return headOrJson(context, {
|
|
938
|
+
kind: RUNTIME_KIND,
|
|
939
|
+
workspaceKey,
|
|
940
|
+
status: "ready",
|
|
941
|
+
workspace_layout: layout.version,
|
|
942
|
+
storage_mode: metadata.storage_mode,
|
|
943
|
+
activity_count: metadata.activity_count,
|
|
944
|
+
// Readiness is only an immutable-generation probe. The default
|
|
945
|
+
// monthly Operations projection was materialized during rebuild;
|
|
946
|
+
// /ready must never start a competing projection in the background.
|
|
947
|
+
day_count: metadata.daily_aggregate_count,
|
|
948
|
+
snapshot_state: snapshotState,
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
const projection = await buildFirstFoldQuickProjection(filesystem, layout, {
|
|
952
|
+
from: null,
|
|
953
|
+
to: null,
|
|
954
|
+
});
|
|
955
|
+
return headOrJson(context, {
|
|
956
|
+
kind: RUNTIME_KIND,
|
|
957
|
+
workspaceKey,
|
|
958
|
+
status: "ready",
|
|
959
|
+
source_latest_at: projection.summary.source_latest_at,
|
|
960
|
+
});
|
|
961
|
+
} catch (error) {
|
|
962
|
+
return headOrJson(
|
|
963
|
+
context,
|
|
964
|
+
{
|
|
965
|
+
kind: RUNTIME_KIND,
|
|
966
|
+
workspaceKey,
|
|
967
|
+
status: "unavailable",
|
|
968
|
+
error: error instanceof Error ? error.message : String(error),
|
|
969
|
+
},
|
|
970
|
+
503,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
|
|
422
975
|
app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
|
|
423
976
|
try {
|
|
424
|
-
const
|
|
977
|
+
const requestStartedAt = performance.now();
|
|
978
|
+
const scope = context.req.query("scope");
|
|
979
|
+
if (scope !== "overview" && scope !== "quick" && scope !== "detail" && scope !== "board" && scope !== "classification")
|
|
980
|
+
return headOrJson(context, (await readSnapshot(context)).operations);
|
|
981
|
+
const layout = await readLayout();
|
|
982
|
+
const layoutMs = performance.now() - requestStartedAt;
|
|
425
983
|
const { from, to } = projectionPeriodFilter(context);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
984
|
+
if (scope === "board") {
|
|
985
|
+
const stage = boardStage(context.req.query("stage"));
|
|
986
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
987
|
+
const limit = Math.min(
|
|
988
|
+
50,
|
|
989
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "10", 10) || 10),
|
|
990
|
+
);
|
|
991
|
+
const board = await readAggregate(`operations:board:${from ?? ""}:${to ?? ""}`, async () => {
|
|
992
|
+
const projection = await buildFirstFoldQuickProjection(
|
|
993
|
+
filesystem,
|
|
994
|
+
layout,
|
|
995
|
+
{ from, to },
|
|
996
|
+
{ includeBoardTasks: true },
|
|
997
|
+
);
|
|
998
|
+
return {
|
|
999
|
+
projection,
|
|
1000
|
+
priority: await readWorkPriority(canonicalFilesystem, knownWorkIds(projection)),
|
|
1001
|
+
};
|
|
1002
|
+
});
|
|
1003
|
+
const filters = boardFilters(context);
|
|
1004
|
+
const pageKey = JSON.stringify({ from, to, stage, offset, limit, ...filters });
|
|
1005
|
+
const page = await readAggregate(`operations:board-page:${pageKey}`, async () =>
|
|
1006
|
+
boardPage(board.projection, stage, offset, limit, board.priority.item_ids, filters),
|
|
1007
|
+
);
|
|
1008
|
+
return headOrJson(
|
|
1009
|
+
context,
|
|
1010
|
+
page,
|
|
1011
|
+
200,
|
|
1012
|
+
true,
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
if (scope === "classification") {
|
|
1016
|
+
const kind = context.req.query("kind");
|
|
1017
|
+
const classification = context.req.query("classification");
|
|
1018
|
+
if (kind !== "task" && kind !== "spec" && kind !== "prd")
|
|
1019
|
+
return headOrJson(context, { error: "A valid work kind is required" }, 400);
|
|
1020
|
+
if (!['lite', 'standard', 'critical', 'empty', 'structured', 'rework'].includes(classification ?? ''))
|
|
1021
|
+
return headOrJson(context, { error: "A valid classification is required" }, 400);
|
|
1022
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1023
|
+
const limit = Math.min(50, Math.max(1, Number.parseInt(context.req.query("limit") ?? "50", 10) || 50));
|
|
1024
|
+
const projection = await readAggregate(`operations:classification:${from ?? ""}:${to ?? ""}`, () =>
|
|
1025
|
+
buildFirstFoldQuickProjection(filesystem, layout, { from, to }, { includeBoardTasks: true }),
|
|
1026
|
+
);
|
|
1027
|
+
return headOrJson(context, classificationPage(projection, kind, classification as BoardClassification, offset, limit), 200, true);
|
|
1028
|
+
}
|
|
1029
|
+
if (scope === "quick") {
|
|
1030
|
+
const projectionStartedAt = performance.now();
|
|
1031
|
+
const quick = await readAggregate(
|
|
1032
|
+
`operations:quick:${from ?? ""}:${to ?? ""}`,
|
|
1033
|
+
() =>
|
|
1034
|
+
buildFirstFoldQuickProjection(filesystem, layout, { from, to }, {
|
|
1035
|
+
includeTopTasks: from === null && to === null,
|
|
1036
|
+
}),
|
|
1037
|
+
context.req.query("fresh") === "1",
|
|
1038
|
+
);
|
|
1039
|
+
if (
|
|
1040
|
+
process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1" &&
|
|
1041
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1042
|
+
) {
|
|
1043
|
+
const timings = filesystem.firstFoldViewportProfile();
|
|
1044
|
+
context.header(
|
|
1045
|
+
"Server-Timing",
|
|
1046
|
+
[
|
|
1047
|
+
`layout;dur=${Math.round(layoutMs * 10) / 10}`,
|
|
1048
|
+
`projection;dur=${Math.round((performance.now() - projectionStartedAt) * 10) / 10}`,
|
|
1049
|
+
...Object.entries(timings)
|
|
1050
|
+
.map(([name, duration]) => `${name};dur=${duration}`)
|
|
1051
|
+
].join(", "),
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
return headOrJson(context, quick, 200, true);
|
|
1055
|
+
}
|
|
1056
|
+
const projection =
|
|
1057
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1058
|
+
? await buildFirstFoldQuickProjection(
|
|
1059
|
+
filesystem,
|
|
1060
|
+
layout,
|
|
1061
|
+
{ from, to },
|
|
1062
|
+
{
|
|
1063
|
+
includeBoardTasks: scope === "detail",
|
|
1064
|
+
includeExtended: scope === "detail" || scope === "overview",
|
|
1065
|
+
includePeriods: scope === "detail" || scope === "overview",
|
|
1066
|
+
},
|
|
1067
|
+
)
|
|
1068
|
+
: await buildCanonicalOperationsProjection(
|
|
1069
|
+
filesystem,
|
|
1070
|
+
layout,
|
|
1071
|
+
{ from, to },
|
|
1072
|
+
scope === "overview",
|
|
1073
|
+
);
|
|
1074
|
+
return headOrJson(context, projection, 200, scope === "overview");
|
|
433
1075
|
} catch (error) {
|
|
434
1076
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
435
1077
|
return headOrJson(
|
|
@@ -440,6 +1082,146 @@ export function createDashboardApp(
|
|
|
440
1082
|
}
|
|
441
1083
|
});
|
|
442
1084
|
|
|
1085
|
+
registerOperationsRunsRoute(app, filesystem, readAggregate, headOrJson);
|
|
1086
|
+
|
|
1087
|
+
// Explicit SQL read-model API for the comparison dashboard. It never falls
|
|
1088
|
+
// back to filesystem pagination, making first-fold/entity query plans
|
|
1089
|
+
// inspectable while preserving the established Operations contracts.
|
|
1090
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-read-models`, async (context) => {
|
|
1091
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem))
|
|
1092
|
+
return headOrJson(context, { error: "SQLite read model is not active" }, 409);
|
|
1093
|
+
const kind = context.req.query("kind");
|
|
1094
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1095
|
+
const limit = Math.min(
|
|
1096
|
+
200,
|
|
1097
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "20", 10) || 20),
|
|
1098
|
+
);
|
|
1099
|
+
if (kind === "overview") {
|
|
1100
|
+
const period = context.req.query("period");
|
|
1101
|
+
if (period !== "day" && period !== "month" && period !== "year")
|
|
1102
|
+
return headOrJson(context, { error: "Unsupported SQLite overview period" }, 400);
|
|
1103
|
+
return headOrJson(context, filesystem.overviewPage(period, offset, limit), 200, true);
|
|
1104
|
+
}
|
|
1105
|
+
if (kind !== "task" && kind !== "spec" && kind !== "prd")
|
|
1106
|
+
return headOrJson(context, { error: "Unsupported SQLite read model" }, 400);
|
|
1107
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1108
|
+
const filters = operationsPageFilters(context);
|
|
1109
|
+
return headOrJson(
|
|
1110
|
+
context,
|
|
1111
|
+
filesystem.summaryPage(
|
|
1112
|
+
kind,
|
|
1113
|
+
{ from, to, ...filters },
|
|
1114
|
+
offset,
|
|
1115
|
+
limit,
|
|
1116
|
+
context.req.query("cursor") ?? null,
|
|
1117
|
+
),
|
|
1118
|
+
200,
|
|
1119
|
+
true,
|
|
1120
|
+
);
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-views`, async (context) => {
|
|
1124
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem))
|
|
1125
|
+
return headOrJson(context, { error: "SQLite read model is not active" }, 409);
|
|
1126
|
+
const view = context.req.query("view");
|
|
1127
|
+
if (view === undefined || !(view in sqliteViewMetadata))
|
|
1128
|
+
return headOrJson(context, { error: "Unsupported SQLite view" }, 400);
|
|
1129
|
+
const parsed = Number.parseInt(context.req.query("limit") ?? "", 10);
|
|
1130
|
+
const defaultLimit = view.includes("agent_invocations")
|
|
1131
|
+
? 100
|
|
1132
|
+
: view === "first_fold_top_tasks"
|
|
1133
|
+
? 5
|
|
1134
|
+
: 50;
|
|
1135
|
+
const limit = Math.min(200, Math.max(1, Number.isFinite(parsed) ? parsed : defaultLimit));
|
|
1136
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1137
|
+
const optional = (name: string) => {
|
|
1138
|
+
const value = context.req.query(name);
|
|
1139
|
+
return value === undefined ? {} : { [name]: value };
|
|
1140
|
+
};
|
|
1141
|
+
return headOrJson(
|
|
1142
|
+
context,
|
|
1143
|
+
filesystem.viewPage(view as keyof typeof sqliteViewMetadata, {
|
|
1144
|
+
from,
|
|
1145
|
+
to,
|
|
1146
|
+
...optional("query"),
|
|
1147
|
+
...optional("status"),
|
|
1148
|
+
...optional("risk"),
|
|
1149
|
+
...optional("provider"),
|
|
1150
|
+
...optional("model"),
|
|
1151
|
+
...optional("agent"),
|
|
1152
|
+
...optional("phase"),
|
|
1153
|
+
...optional("origin"),
|
|
1154
|
+
...optional("workType"),
|
|
1155
|
+
...optional("priority"),
|
|
1156
|
+
offset: Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0),
|
|
1157
|
+
limit,
|
|
1158
|
+
}),
|
|
1159
|
+
200,
|
|
1160
|
+
true,
|
|
1161
|
+
);
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/work-priority`, async (context) => {
|
|
1165
|
+
const layout = await requireV2Layout(canonicalFilesystem);
|
|
1166
|
+
const operations = await buildOperationsProjection(canonicalFilesystem, layout, {
|
|
1167
|
+
from: null,
|
|
1168
|
+
to: null,
|
|
1169
|
+
});
|
|
1170
|
+
return headOrJson(
|
|
1171
|
+
context,
|
|
1172
|
+
await readWorkPriority(canonicalFilesystem, knownWorkIds(operations)),
|
|
1173
|
+
);
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/operations/entities`, async (context) => {
|
|
1177
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1178
|
+
const limit = Math.min(
|
|
1179
|
+
500,
|
|
1180
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
|
|
1181
|
+
);
|
|
1182
|
+
const kind = context.req.query("kind");
|
|
1183
|
+
if (kind !== "changes" && kind !== "decisions")
|
|
1184
|
+
return headOrJson(context, { error: "Unsupported Operations entity page" }, 400);
|
|
1185
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1186
|
+
const pageFilters = operationsPageFilters(context);
|
|
1187
|
+
const payload = await readAggregate(
|
|
1188
|
+
`operations:entities:${kind}:${from ?? ""}:${to ?? ""}:${offset}:${limit}:${pageFilters.query}:${pageFilters.status}:${pageFilters.risk}`,
|
|
1189
|
+
async () => {
|
|
1190
|
+
const layout = await readLayout();
|
|
1191
|
+
const entities = await new LifecycleManifestStore(filesystem).readEntities(
|
|
1192
|
+
kind === "changes" ? "change" : "decision",
|
|
1193
|
+
);
|
|
1194
|
+
const entityRows =
|
|
1195
|
+
kind === "changes"
|
|
1196
|
+
? changeEntityPageRows(entities, layout, from, to)
|
|
1197
|
+
: decisionEntityPageRows(entities, layout, from, to);
|
|
1198
|
+
const rows = filterLifecyclePageRows(entityRows, pageFilters);
|
|
1199
|
+
return {
|
|
1200
|
+
data_source: "lifecycle_manifest_shards",
|
|
1201
|
+
kind,
|
|
1202
|
+
offset,
|
|
1203
|
+
limit,
|
|
1204
|
+
total: rows.length,
|
|
1205
|
+
rows: rows.slice(offset, offset + limit),
|
|
1206
|
+
};
|
|
1207
|
+
},
|
|
1208
|
+
);
|
|
1209
|
+
return headOrJson(context, payload, 200, true);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
registerDailyReadModelRoute(app, filesystem, readAggregate, headOrJson);
|
|
1213
|
+
app.put(`${API_PREFIX}/work-priority`, async (context) => {
|
|
1214
|
+
const candidate = normalizeWorkPriority(await context.req.json());
|
|
1215
|
+
// Priority is canonical queue configuration, never snapshot content.
|
|
1216
|
+
// Keep every other settings mutation blocked in snapshot-serving mode.
|
|
1217
|
+
await canonicalFilesystem.writeTextAtomic(
|
|
1218
|
+
WORK_PRIORITY_PATH,
|
|
1219
|
+
`${JSON.stringify(candidate, null, 2)}\n`,
|
|
1220
|
+
);
|
|
1221
|
+
clearSnapshotCache();
|
|
1222
|
+
return context.json(candidate);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
443
1225
|
app.on(["GET", "HEAD"], `${API_PREFIX}/snapshot`, async (context) => {
|
|
444
1226
|
try {
|
|
445
1227
|
return headOrJson(context, await readSnapshot(context));
|
|
@@ -455,10 +1237,15 @@ export function createDashboardApp(
|
|
|
455
1237
|
|
|
456
1238
|
app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
|
|
457
1239
|
try {
|
|
458
|
-
await
|
|
459
|
-
|
|
1240
|
+
await readLayout();
|
|
1241
|
+
// The served snapshot is immutable by design. Reconciliation must use
|
|
1242
|
+
// canonical records, then atomically rebuild and reopen the snapshot
|
|
1243
|
+
// before the response exposes fresh projections.
|
|
1244
|
+
const reconciledIds = await reconcileOpen(canonicalFilesystem, workspaceRoot);
|
|
1245
|
+
await refreshReadModel();
|
|
1246
|
+
clearSnapshotCache();
|
|
460
1247
|
return context.json({
|
|
461
|
-
...(await
|
|
1248
|
+
...(await readSnapshot(context)),
|
|
462
1249
|
reconciliation: reconciledIds,
|
|
463
1250
|
});
|
|
464
1251
|
} catch (error) {
|
|
@@ -469,7 +1256,74 @@ export function createDashboardApp(
|
|
|
469
1256
|
|
|
470
1257
|
app.on(["GET", "HEAD"], `${API_PREFIX}/cost-monitor`, async (context) => {
|
|
471
1258
|
try {
|
|
472
|
-
const
|
|
1259
|
+
const scope = context.req.query("scope") ?? "summary";
|
|
1260
|
+
if (scope === "summary") {
|
|
1261
|
+
const layout = await readLayout();
|
|
1262
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1263
|
+
const currency = selectedCurrency(context);
|
|
1264
|
+
const aggregateStartedAt = performance.now();
|
|
1265
|
+
const [source, rate] = await Promise.all([
|
|
1266
|
+
firstFoldCostSource(filesystem, {
|
|
1267
|
+
from,
|
|
1268
|
+
to,
|
|
1269
|
+
providers: queryValues(context, "provider"),
|
|
1270
|
+
models: queryValues(context, "model"),
|
|
1271
|
+
agents: queryValues(context, "agent"),
|
|
1272
|
+
phases: queryValues(context, "phase"),
|
|
1273
|
+
statuses: queryValues(context, "status"),
|
|
1274
|
+
tasks: queryValues(context, "task"),
|
|
1275
|
+
specs: queryValues(context, "spec"),
|
|
1276
|
+
prds: queryValues(context, "prd"),
|
|
1277
|
+
executions: queryValues(context, "execution"),
|
|
1278
|
+
changes: queryValues(context, "change"),
|
|
1279
|
+
}),
|
|
1280
|
+
currency === "BRL" ? readCurrentFxRate(filesystem) : Promise.resolve(null),
|
|
1281
|
+
]);
|
|
1282
|
+
// First fold receives only aggregate facts. Detailed Runs and entity
|
|
1283
|
+
// arrays remain on their explicit paginated endpoints.
|
|
1284
|
+
const aggregateMs = performance.now() - aggregateStartedAt;
|
|
1285
|
+
const costOperations = {
|
|
1286
|
+
schema_version: 1,
|
|
1287
|
+
layout_version: layout.version,
|
|
1288
|
+
readonly: true,
|
|
1289
|
+
source: ".agents/records",
|
|
1290
|
+
filters: { from, to },
|
|
1291
|
+
summary: { task_count: source.task_count ?? 0 },
|
|
1292
|
+
prds: [],
|
|
1293
|
+
specs: [],
|
|
1294
|
+
runs: [],
|
|
1295
|
+
} as unknown as OperationsProjection;
|
|
1296
|
+
const projectionStartedAt = performance.now();
|
|
1297
|
+
const payload = buildCostSummaryProjection(
|
|
1298
|
+
source,
|
|
1299
|
+
costOperations,
|
|
1300
|
+
currency,
|
|
1301
|
+
rate?.rate ?? null,
|
|
1302
|
+
);
|
|
1303
|
+
context.header(
|
|
1304
|
+
"Server-Timing",
|
|
1305
|
+
`cost_sql;dur=${aggregateMs.toFixed(1)},cost_projection;dur=${(performance.now() - projectionStartedAt).toFixed(1)}`,
|
|
1306
|
+
);
|
|
1307
|
+
return headOrJson(context, payload, 200, true);
|
|
1308
|
+
}
|
|
1309
|
+
if (scope === "runs") {
|
|
1310
|
+
const layout = await readLayout();
|
|
1311
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1312
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1313
|
+
const limit = Math.min(
|
|
1314
|
+
500,
|
|
1315
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
|
|
1316
|
+
);
|
|
1317
|
+
return headOrJson(
|
|
1318
|
+
context,
|
|
1319
|
+
await buildManifestCostRunPage(filesystem, layout, context, { from, to }, offset, limit),
|
|
1320
|
+
200,
|
|
1321
|
+
true,
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
if (isUnfilteredCostRequest(context))
|
|
1325
|
+
return headOrJson(context, (await readSnapshot(context)).costs);
|
|
1326
|
+
const layout = await readLayout();
|
|
473
1327
|
const { from, to } = projectionPeriodFilter(context);
|
|
474
1328
|
// Cost and Operations must start from the same manifest-corrected Run
|
|
475
1329
|
// population; financial availability is represented on each row, never
|
|
@@ -507,6 +1361,7 @@ export function createDashboardApp(
|
|
|
507
1361
|
".agents/config/pricing/models.json",
|
|
508
1362
|
`${JSON.stringify(catalog, null, 2)}\n`,
|
|
509
1363
|
);
|
|
1364
|
+
clearSnapshotCache();
|
|
510
1365
|
return context.json(catalog);
|
|
511
1366
|
});
|
|
512
1367
|
|
|
@@ -518,6 +1373,7 @@ export function createDashboardApp(
|
|
|
518
1373
|
app.put(`${API_PREFIX}/fx-rate`, async (context) => {
|
|
519
1374
|
const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
|
|
520
1375
|
const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
|
|
1376
|
+
clearSnapshotCache();
|
|
521
1377
|
return context.json(rate);
|
|
522
1378
|
});
|
|
523
1379
|
|
|
@@ -553,9 +1409,13 @@ export function createDashboardApp(
|
|
|
553
1409
|
app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
|
|
554
1410
|
const update = async () => {
|
|
555
1411
|
try {
|
|
556
|
-
|
|
557
|
-
|
|
1412
|
+
const persisted = await persistAgentCatalogUpdate(
|
|
1413
|
+
filesystem,
|
|
1414
|
+
await context.req.json(),
|
|
1415
|
+
syncAgentAdapters,
|
|
558
1416
|
);
|
|
1417
|
+
clearSnapshotCache();
|
|
1418
|
+
return context.json(persisted);
|
|
559
1419
|
} catch (error) {
|
|
560
1420
|
if (error instanceof AgentCatalogUpdateError) {
|
|
561
1421
|
return context.json({ error: error.message }, error.status);
|
|
@@ -573,7 +1433,7 @@ export function createDashboardApp(
|
|
|
573
1433
|
|
|
574
1434
|
app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
|
|
575
1435
|
try {
|
|
576
|
-
return headOrJson(context, await readProjectSettings(
|
|
1436
|
+
return headOrJson(context, await readProjectSettings(canonicalFilesystem));
|
|
577
1437
|
} catch (error) {
|
|
578
1438
|
return headOrJson(
|
|
579
1439
|
context,
|
|
@@ -583,9 +1443,15 @@ export function createDashboardApp(
|
|
|
583
1443
|
}
|
|
584
1444
|
});
|
|
585
1445
|
|
|
1446
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings/overview`, async (context) => {
|
|
1447
|
+
return headOrJson(context, await readProjectOverview(canonicalFilesystem));
|
|
1448
|
+
});
|
|
1449
|
+
|
|
586
1450
|
app.put(`${API_PREFIX}/project-settings`, async (context) => {
|
|
587
1451
|
try {
|
|
588
|
-
|
|
1452
|
+
const persisted = await persistProjectSettings(canonicalFilesystem, await context.req.json());
|
|
1453
|
+
clearSnapshotCache();
|
|
1454
|
+
return context.json(persisted);
|
|
589
1455
|
} catch (error) {
|
|
590
1456
|
return context.json(
|
|
591
1457
|
{ error: error instanceof Error ? error.message : "Project settings update failed" },
|
|
@@ -596,7 +1462,7 @@ export function createDashboardApp(
|
|
|
596
1462
|
|
|
597
1463
|
app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id`, async (context) => {
|
|
598
1464
|
try {
|
|
599
|
-
const layout = await
|
|
1465
|
+
const layout = await readLayout();
|
|
600
1466
|
return headOrJson(context, await readTaskDetail(filesystem, layout, context.req.param("id")));
|
|
601
1467
|
} catch (error) {
|
|
602
1468
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -606,7 +1472,7 @@ export function createDashboardApp(
|
|
|
606
1472
|
|
|
607
1473
|
app.on(["GET", "HEAD"], `${API_PREFIX}/prds/:id`, async (context) => {
|
|
608
1474
|
try {
|
|
609
|
-
const layout = await
|
|
1475
|
+
const layout = await readLayout();
|
|
610
1476
|
return headOrJson(context, await readPrdDetail(filesystem, layout, context.req.param("id")));
|
|
611
1477
|
} catch (error) {
|
|
612
1478
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -616,7 +1482,7 @@ export function createDashboardApp(
|
|
|
616
1482
|
|
|
617
1483
|
app.on(["GET", "HEAD"], `${API_PREFIX}/specs/:id`, async (context) => {
|
|
618
1484
|
try {
|
|
619
|
-
const layout = await
|
|
1485
|
+
const layout = await readLayout();
|
|
620
1486
|
return headOrJson(context, await readSpecDetail(filesystem, layout, context.req.param("id")));
|
|
621
1487
|
} catch (error) {
|
|
622
1488
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -626,8 +1492,14 @@ export function createDashboardApp(
|
|
|
626
1492
|
|
|
627
1493
|
app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id/trace`, async (context) => {
|
|
628
1494
|
try {
|
|
629
|
-
const layout = await
|
|
630
|
-
|
|
1495
|
+
const layout = await readLayout();
|
|
1496
|
+
const taskId = context.req.param("id");
|
|
1497
|
+
return headOrJson(
|
|
1498
|
+
context,
|
|
1499
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1500
|
+
? buildSqliteTaskTrace(filesystem, layout, taskId)
|
|
1501
|
+
: await buildTaskTrace(filesystem, layout, taskId),
|
|
1502
|
+
);
|
|
631
1503
|
} catch (error) {
|
|
632
1504
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
633
1505
|
return headOrJson(context, { error: "Task trace not found" }, 404);
|
|
@@ -657,57 +1529,28 @@ export function createDashboardApp(
|
|
|
657
1529
|
return app;
|
|
658
1530
|
}
|
|
659
1531
|
|
|
660
|
-
async function runAgentAdapterSync(workspaceRoot: string): Promise<AgentAdapterSyncResult> {
|
|
661
|
-
const cliPath = resolve(workspaceRoot, ".agents", "runtime", "bin", "zaip.mjs");
|
|
662
|
-
const commandOptions = {
|
|
663
|
-
cwd: workspaceRoot,
|
|
664
|
-
encoding: "utf8" as const,
|
|
665
|
-
windowsHide: true,
|
|
666
|
-
timeout: 120_000,
|
|
667
|
-
maxBuffer: 10 * 1_024 * 1_024,
|
|
668
|
-
};
|
|
669
|
-
await execFileAsync(process.execPath, [cliPath, "adapters", "build", "--json"], commandOptions);
|
|
670
|
-
const { stdout } = await execFileAsync(
|
|
671
|
-
process.execPath,
|
|
672
|
-
[cliPath, "adapters", "check", "--json"],
|
|
673
|
-
commandOptions,
|
|
674
|
-
);
|
|
675
|
-
const result = JSON.parse(stdout) as { valid?: unknown; drifted?: unknown };
|
|
676
|
-
return {
|
|
677
|
-
valid: result.valid === true,
|
|
678
|
-
drifted: Array.isArray(result.drifted)
|
|
679
|
-
? result.drifted.filter((value): value is string => typeof value === "string")
|
|
680
|
-
: [],
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/** Date-only filters in the dashboard represent complete calendar days. */
|
|
685
|
-
export function projectionPeriodFilter(context: Pick<Context, "req">): {
|
|
686
|
-
from: string | null;
|
|
687
|
-
to: string | null;
|
|
688
|
-
} {
|
|
689
|
-
const from = context.req.query("from") ?? null;
|
|
690
|
-
const to = context.req.query("to") ?? null;
|
|
691
|
-
return {
|
|
692
|
-
from: dateOnlyBoundary(from, "start"),
|
|
693
|
-
to: dateOnlyBoundary(to, "end"),
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function dateOnlyBoundary(value: string | null, boundary: "start" | "end"): string | null {
|
|
698
|
-
if (value === null || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
|
699
|
-
return boundary === "start" ? `${value}T00:00:00.000Z` : `${value}T23:59:59.999Z`;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
1532
|
async function dashboardSnapshot(
|
|
703
1533
|
filesystem: WorkspaceFileSystem,
|
|
704
1534
|
workspaceRoot: string,
|
|
705
1535
|
context: Context,
|
|
1536
|
+
cachedLayout?: WorkspaceLayout,
|
|
706
1537
|
) {
|
|
707
|
-
const layout = await requireV2Layout(filesystem);
|
|
1538
|
+
const layout = cachedLayout ?? await requireV2Layout(filesystem);
|
|
708
1539
|
const { from, to } = projectionPeriodFilter(context);
|
|
709
|
-
const
|
|
710
|
-
const
|
|
1540
|
+
const currency = selectedCurrency(context);
|
|
1541
|
+
const operations =
|
|
1542
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1543
|
+
? await buildFirstFoldQuickProjection(filesystem, layout, { from, to })
|
|
1544
|
+
: await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
|
|
1545
|
+
const costs =
|
|
1546
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1547
|
+
? buildCostSummaryProjection(
|
|
1548
|
+
await firstFoldCostSource(filesystem, { from, to }),
|
|
1549
|
+
operations,
|
|
1550
|
+
currency,
|
|
1551
|
+
currency === "BRL" ? ((await readCurrentFxRate(filesystem))?.rate ?? null) : null,
|
|
1552
|
+
)
|
|
1553
|
+
: await buildCurrentCostProjection(filesystem, operations, currency);
|
|
711
1554
|
return {
|
|
712
1555
|
operations,
|
|
713
1556
|
costs,
|
|
@@ -719,81 +1562,75 @@ async function dashboardSnapshot(
|
|
|
719
1562
|
};
|
|
720
1563
|
}
|
|
721
1564
|
|
|
1565
|
+
const moduleStartedAt = performance.now();
|
|
1566
|
+
const sessionToken = defaultSessionToken();
|
|
1567
|
+
const runtimeWorkspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd();
|
|
1568
|
+
|
|
1569
|
+
let defaultApp: Hono | undefined;
|
|
1570
|
+
|
|
722
1571
|
/**
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
1572
|
+
* Compatibility export for direct module consumers. The executable must not
|
|
1573
|
+
* construct this filesystem-backed app: `main` creates the SQLite app after
|
|
1574
|
+
* its private read model is ready. Construct on first consumer access instead.
|
|
726
1575
|
*/
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
filter: { from: string | null; to: string | null },
|
|
731
|
-
overviewOnly = false,
|
|
732
|
-
) {
|
|
733
|
-
const operations = await buildOperationsProjection(filesystem, layout, filter, { overviewOnly });
|
|
734
|
-
if (overviewOnly || filter.from === null || filter.to === null) return operations;
|
|
735
|
-
const summaries = await invocationSummariesForPeriod(filesystem, {
|
|
736
|
-
from: filter.from,
|
|
737
|
-
to: filter.to,
|
|
738
|
-
});
|
|
739
|
-
return summaries.length === 0
|
|
740
|
-
? operations
|
|
741
|
-
: recalculateOperationsRunSummary({
|
|
742
|
-
...operations,
|
|
743
|
-
runs: projectExecutionDurations(mergeManifestRuns(operations.runs, summaries)),
|
|
744
|
-
});
|
|
1576
|
+
export function defaultDashboardApp(): Hono {
|
|
1577
|
+
defaultApp ??= createDashboardApp(runtimeWorkspaceRoot, sessionToken);
|
|
1578
|
+
return defaultApp;
|
|
745
1579
|
}
|
|
746
1580
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const [catalog, fxRate] = await Promise.all([
|
|
754
|
-
pricing.readCatalog(),
|
|
755
|
-
currency === "BRL" ? pricing.readLatestFxRate() : Promise.resolve(null),
|
|
756
|
-
]);
|
|
757
|
-
const agentNames = await configuredAgentNames(filesystem);
|
|
758
|
-
return buildCostMonitorProjection(operations, { catalog, fxRate, currency }, agentNames);
|
|
759
|
-
}
|
|
1581
|
+
export const app = new Proxy({} as Hono, {
|
|
1582
|
+
get(_target, property) {
|
|
1583
|
+
const value = Reflect.get(defaultDashboardApp(), property);
|
|
1584
|
+
return typeof value === "function" ? value.bind(defaultDashboardApp()) : value;
|
|
1585
|
+
},
|
|
1586
|
+
});
|
|
760
1587
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
return buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
|
|
770
|
-
}
|
|
771
|
-
const filters = {
|
|
772
|
-
providers: queryValues(context, "provider"),
|
|
773
|
-
models: queryValues(context, "model"),
|
|
774
|
-
zaip_agents: queryValues(context, "agent"),
|
|
775
|
-
phases: queryValues(context, "phase"),
|
|
776
|
-
statuses: queryValues(context, "status"),
|
|
777
|
-
};
|
|
778
|
-
const summaries = await invocationSummariesForPeriod(filesystem, { from, to, filters });
|
|
779
|
-
if (summaries.length === 0)
|
|
780
|
-
return buildCurrentCostProjection(filesystem, operations, selectedCurrency(context));
|
|
781
|
-
const runs = projectExecutionDurations(mergeManifestRuns(operations.runs, summaries));
|
|
782
|
-
return buildCurrentCostProjection(filesystem, { ...operations, runs }, selectedCurrency(context));
|
|
1588
|
+
function startupPhase(phase: string): void {
|
|
1589
|
+
process.stderr.write(
|
|
1590
|
+
`${JSON.stringify({
|
|
1591
|
+
event: "dashboard_sqlite_startup_phase",
|
|
1592
|
+
phase,
|
|
1593
|
+
elapsed_ms: Math.round(performance.now() - moduleStartedAt),
|
|
1594
|
+
})}\n`,
|
|
1595
|
+
);
|
|
783
1596
|
}
|
|
784
1597
|
|
|
785
|
-
|
|
786
|
-
export const app = createDashboardApp(process.env.ZAIP_WORKSPACE ?? process.cwd(), sessionToken);
|
|
787
|
-
|
|
788
|
-
const isMain = process.argv[1]
|
|
789
|
-
? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
790
|
-
: false;
|
|
791
|
-
|
|
792
|
-
if (isMain) {
|
|
1598
|
+
async function main(): Promise<void> {
|
|
793
1599
|
const port = Number.parseInt(process.env.ZAIP_SERVER_PORT ?? "4317", 10);
|
|
794
|
-
|
|
1600
|
+
startupPhase("main_entered");
|
|
1601
|
+
const startedAt = performance.now();
|
|
1602
|
+
const readModel = await rebuildSqliteReadModel(runtimeWorkspaceRoot);
|
|
1603
|
+
// The parent launcher captures stderr even when stdout is intentionally
|
|
1604
|
+
// hidden. This makes seed hit/miss and private-copy cost observable without
|
|
1605
|
+
// exposing a shared handle or workspace payload.
|
|
1606
|
+
process.stderr.write(
|
|
1607
|
+
`${JSON.stringify({
|
|
1608
|
+
event: "dashboard_sqlite_read_model",
|
|
1609
|
+
elapsed_ms: Math.round(performance.now() - startedAt),
|
|
1610
|
+
cache: readModel.cache,
|
|
1611
|
+
fingerprint_ms: readModel.fingerprint_ms,
|
|
1612
|
+
materialize_ms: readModel.materialize_ms,
|
|
1613
|
+
source_files: readModel.files,
|
|
1614
|
+
})}\n`,
|
|
1615
|
+
);
|
|
1616
|
+
startupPhase("read_model_ready");
|
|
1617
|
+
const runtimeApp = createDashboardApp(runtimeWorkspaceRoot, sessionToken, undefined, {
|
|
1618
|
+
filesystem: new SqliteReadModelFileSystem(runtimeWorkspaceRoot),
|
|
1619
|
+
snapshotState: readModel.cache === "stale_seed" ? "updating" : "current",
|
|
1620
|
+
});
|
|
1621
|
+
startupPhase("runtime_app_ready");
|
|
1622
|
+
serve({ fetch: runtimeApp.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
|
|
1623
|
+
startupPhase("listening");
|
|
795
1624
|
process.stdout.write(
|
|
796
1625
|
`ZAIP-AIOS dashboard API: http://${HOST}:${boundPort}${API_PREFIX}/health\n`,
|
|
797
1626
|
);
|
|
798
1627
|
});
|
|
799
1628
|
}
|
|
1629
|
+
|
|
1630
|
+
if (
|
|
1631
|
+
process.argv[1]?.endsWith("server.cjs") ||
|
|
1632
|
+
process.argv[1]?.endsWith("server.js") ||
|
|
1633
|
+
process.argv[1]?.endsWith("server.mjs")
|
|
1634
|
+
) {
|
|
1635
|
+
void main();
|
|
1636
|
+
}
|