@zaip-ai/cli 1.0.7 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +39 -4
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/agent-adapters.test.js +13 -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 +7 -0
- package/dist/services/template-manifest.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/architecture/software-architecture.md +131 -0
- package/templates/.agents/branding/brandbook.docx +0 -0
- package/templates/.agents/branding/brandbook.md +60 -0
- package/templates/.agents/config/agents/catalog.yaml +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 +2 -2
- 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/.tmp-run-list.json +254 -0
- package/templates/.agents/dashboard/.zaip-managed.json +775 -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-DxR_pgSV.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CKcsqb-r.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.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-N5Csr0RP.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-C0fj2uU9.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.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-BEsJ-FYH.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-B6y6Gw12.js +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +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 +1129 -600
- 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 +266 -315
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +151 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +37 -0
- package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +114 -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 +85 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +133 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +453 -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 +26 -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 +482 -3
- 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 +78 -0
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +234 -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 +253 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +380 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +175 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +395 -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 +36 -0
- package/templates/.agents/dashboard/apps/frontend/vite.config.ts +58 -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 +134 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +290 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +133 -19
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +134 -43
- 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 +328 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +302 -6
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +627 -400
- 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 +370 -72
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +467 -326
- 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 +55 -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 +33 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +119 -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 +146 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +118 -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 -161
- 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 +757 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +784 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +100 -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/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
- package/templates/.agents/dashboard/modules/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 +185 -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 +345 -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 +367 -14
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +562 -131
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +353 -355
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +65 -0
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
- package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +73 -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 +105 -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 +265 -36
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +171 -8
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +98 -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 -17
- 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 +431 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +574 -20
- package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +42 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
- 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 +67 -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 +22 -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 +176 -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 +21 -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 +241 -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 +7 -4
- package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +5 -84
- 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 +39 -2
- package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +25 -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 +330 -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 +2 -0
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1759 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1611 -0
- package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +20 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +114 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +31 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +589 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +53 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +203 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1646 -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 +339 -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 +110 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +189 -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 +178 -84
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +149 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +675 -267
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1212 -309
- 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 +722 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +3743 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +7017 -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 +514 -190
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +324 -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 +216 -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/growth/estrategia-bootstrap-90-dias.md +169 -0
- package/templates/.agents/growth/strategy.md +27 -0
- package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
- package/templates/.agents/marketing/strategy.md +29 -0
- package/templates/.agents/requirements/autonomous-assurance.md +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 +118 -67
- 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/sales/playbook.md +32 -0
- package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
- package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
- package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +22 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +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-enforce-architecture/SKILL.md +2 -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 +21 -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 +9 -3
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +99 -93
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +132 -23
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +149 -1
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +76 -19
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +217 -7
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +20 -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 +41 -0
- package/templates/.agents/zaip-aios/INDEX.md +11 -0
- package/templates/.agents/zaip-aios/README.md +38 -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/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
- package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
- package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
- package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
- package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
- package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
- package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
- package/templates/template-manifest.json +1082 -215
- 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/DashboardHelpDialog-Cv-JcoxP.js +0 -1320
- 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,126 @@
|
|
|
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 CompactRunMetric, LifecycleManifestStore } from "@zaip-ai/lifecycle-manifests";
|
|
21
28
|
import { type Context, Hono } from "hono";
|
|
22
29
|
import { getCookie, setCookie } from "hono/cookie";
|
|
23
30
|
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
31
|
+
import type { TaskTrace, TraceDocument } from "../../../modules/traceability/src/contracts.js";
|
|
32
|
+
import { runAgentAdapterSync } from "./agent-adapter-sync.js";
|
|
24
33
|
import {
|
|
25
34
|
type AgentAdapterSyncResult,
|
|
26
35
|
AgentCatalogUpdateError,
|
|
27
36
|
persistAgentCatalogUpdate,
|
|
28
37
|
readAgentCatalogSettings,
|
|
29
38
|
} from "./agent-catalog-settings.js";
|
|
30
|
-
import { mergeManifestRuns } from "./manifest-runs.js";
|
|
31
|
-
import { persistProjectSettings, readProjectSettings } from "./project-settings.js";
|
|
32
39
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
persistProjectSettings,
|
|
41
|
+
readProjectOverview,
|
|
42
|
+
readProjectSettings,
|
|
43
|
+
} from "./project-settings.js";
|
|
44
|
+
import {
|
|
45
|
+
type BoardClassification,
|
|
46
|
+
boardFilters,
|
|
47
|
+
boardPage,
|
|
48
|
+
boardStage,
|
|
49
|
+
classificationPage,
|
|
50
|
+
} from "./server-board.js";
|
|
51
|
+
import { registerDailyReadModelRoute } from "./server-daily-read-models-route.js";
|
|
52
|
+
import {
|
|
53
|
+
changeEntityPageRows,
|
|
54
|
+
decisionEntityPageRows,
|
|
55
|
+
filterLifecyclePageRows,
|
|
56
|
+
operationsPageFilters,
|
|
57
|
+
} from "./server-operations-pages.js";
|
|
58
|
+
import { registerOperationsRunsRoute } from "./server-operations-runs-route.js";
|
|
59
|
+
import { projectionPeriodFilter } from "./server-period.js";
|
|
60
|
+
import {
|
|
61
|
+
buildCanonicalOperationsProjection,
|
|
62
|
+
buildCurrentCostProjection,
|
|
63
|
+
buildManifestCostRunPage,
|
|
64
|
+
buildManifestFirstCostProjection,
|
|
65
|
+
filterCostRunRows,
|
|
66
|
+
isUnfilteredCostRequest,
|
|
67
|
+
knownWorkIds,
|
|
68
|
+
normalizeWorkPriority,
|
|
69
|
+
pageCostRunRows,
|
|
70
|
+
readCurrentFxRate,
|
|
71
|
+
readWorkPriority,
|
|
72
|
+
} from "./server-projections.js";
|
|
73
|
+
import { queryValues, selectedCurrency } from "./server-summary-helpers.js";
|
|
74
|
+
import { dashboardIdentity, dashboardWorkspaceKey, reconcileOpenWork } from "./server-workspace.js";
|
|
75
|
+
import {
|
|
76
|
+
rebuildSqliteReadModel,
|
|
77
|
+
SqliteReadModelFileSystem,
|
|
78
|
+
sqliteViewMetadata,
|
|
79
|
+
} from "./sqlite-read-model.js";
|
|
38
80
|
|
|
39
81
|
export { manifestRun, mergeManifestRuns } from "./manifest-runs.js";
|
|
82
|
+
export { projectionPeriodFilter } from "./server-period.js";
|
|
83
|
+
export {
|
|
84
|
+
dashboardWorkspaceKey,
|
|
85
|
+
githubRepositoryFromRemote,
|
|
86
|
+
type ReconciliationItem,
|
|
87
|
+
type ReconciliationResult,
|
|
88
|
+
reconciliationResult,
|
|
89
|
+
} from "./server-workspace.js";
|
|
90
|
+
export {
|
|
91
|
+
rebuildSqliteReadModel,
|
|
92
|
+
SqliteReadModelFileSystem,
|
|
93
|
+
validateSqliteReadModelSchema,
|
|
94
|
+
} from "./sqlite-read-model.js";
|
|
95
|
+
|
|
96
|
+
type AllTimeTopTaskFact = {
|
|
97
|
+
task_id: string;
|
|
98
|
+
total_duration_ms: number | null;
|
|
99
|
+
total_tokens: number | null;
|
|
100
|
+
total_cost_usd: number | null;
|
|
101
|
+
total_cost_brl: number | null;
|
|
102
|
+
total_run_count: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export function projectAllTimeTopTasks(
|
|
106
|
+
tasks: readonly AllTimeTopTaskFact[],
|
|
107
|
+
): NonNullable<NonNullable<OperationsProjection["summary"]["overview_metrics"]>["top_tasks"]> {
|
|
108
|
+
return tasks.map((task) => ({
|
|
109
|
+
task_id: task.task_id,
|
|
110
|
+
duration_ms: task.total_duration_ms,
|
|
111
|
+
total_tokens: task.total_tokens,
|
|
112
|
+
total_cost_usd: task.total_cost_usd,
|
|
113
|
+
cost_brl: task.total_cost_brl,
|
|
114
|
+
run_count: task.total_run_count,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
40
117
|
|
|
41
118
|
const API_PREFIX = "/api/v2";
|
|
42
119
|
const HOST = "127.0.0.1";
|
|
43
|
-
const RUNTIME_KIND = "zaip-dashboard-
|
|
44
|
-
const SESSION_COOKIE = "zaip-dashboard-session";
|
|
45
|
-
const
|
|
46
|
-
const
|
|
120
|
+
const RUNTIME_KIND = "zaip-dashboard-sqlite-runtime";
|
|
121
|
+
const SESSION_COOKIE = "zaip-dashboard-sqlite-session";
|
|
122
|
+
const WORK_PRIORITY_PATH = ".agents/dashboard/work-priority.json";
|
|
123
|
+
const SNAPSHOT_CACHE_TTL_MS = 5_000;
|
|
47
124
|
const agentPortraitFiles: Record<string, string> = {
|
|
48
125
|
"agent-orchestrator": "1 - Dave - Product Owner.png",
|
|
49
126
|
"agent-builder": "2 - Rafa - Tech Lead.png",
|
|
@@ -87,6 +164,13 @@ export function openWorkIds(items: readonly ActiveWorkItem[]): string[] {
|
|
|
87
164
|
|
|
88
165
|
interface DashboardAppOptions {
|
|
89
166
|
syncAgentAdapters?: () => Promise<AgentAdapterSyncResult>;
|
|
167
|
+
filesystem?: WorkspaceFileSystem;
|
|
168
|
+
canonicalFilesystem?: WorkspaceFileSystem;
|
|
169
|
+
/** Test seam for the disposable SQLite serving mode. */
|
|
170
|
+
snapshotReadOnly?: boolean;
|
|
171
|
+
reconcileOpenWork?: typeof reconcileOpenWork;
|
|
172
|
+
refreshReadModel?: () => Promise<void>;
|
|
173
|
+
snapshotState?: "current" | "updating";
|
|
90
174
|
}
|
|
91
175
|
|
|
92
176
|
class V2WorkspaceRequiredError extends Error {
|
|
@@ -126,164 +210,520 @@ function shouldBootstrapBrowserSession(context: Context): boolean {
|
|
|
126
210
|
return context.req.header("sec-fetch-site") !== "cross-site";
|
|
127
211
|
}
|
|
128
212
|
|
|
129
|
-
async function
|
|
213
|
+
async function requireV2Layout(filesystem: WorkspaceFileSystem): Promise<WorkspaceLayout> {
|
|
214
|
+
const layout = await loadWorkspaceLayout(filesystem);
|
|
215
|
+
if (layout.version === 1) {
|
|
216
|
+
throw new V2WorkspaceRequiredError("Dashboard API requires a modern ZAIP-AIOS workspace");
|
|
217
|
+
}
|
|
218
|
+
return layout;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function headOrJson(
|
|
222
|
+
context: Context,
|
|
223
|
+
payload: unknown,
|
|
224
|
+
status: ContentfulStatusCode = 200,
|
|
225
|
+
cacheable = false,
|
|
226
|
+
): Response {
|
|
227
|
+
if (cacheable) {
|
|
228
|
+
const etag = `"${createHash("sha256").update(JSON.stringify(payload)).digest("base64url")}"`;
|
|
229
|
+
context.header("ETag", etag);
|
|
230
|
+
context.header("Cache-Control", "private, no-cache");
|
|
231
|
+
if (context.req.header("if-none-match") === etag) return context.body(null, 304);
|
|
232
|
+
}
|
|
233
|
+
if (context.req.method === "HEAD") return context.body(null, status);
|
|
234
|
+
return context.json(payload, status);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Keep the entity/macro details on the snapshot filesystem, while making the
|
|
239
|
+
* first-fold counters and token/cost facts come from SQL.
|
|
240
|
+
* Duration intentionally retains the shared hybrid legacy policy in the
|
|
241
|
+
* Operations module; its underlying daily rows are still SQLite-backed.
|
|
242
|
+
*/
|
|
243
|
+
async function buildFirstFoldQuickProjection(
|
|
130
244
|
filesystem: WorkspaceFileSystem,
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
245
|
+
layout: WorkspaceLayout,
|
|
246
|
+
filter: { from: string | null; to: string | null },
|
|
247
|
+
options: {
|
|
248
|
+
includeBoardTasks?: boolean;
|
|
249
|
+
includeExtended?: boolean;
|
|
250
|
+
includePeriods?: boolean;
|
|
251
|
+
includeTopTasks?: boolean;
|
|
252
|
+
} = {},
|
|
253
|
+
): Promise<OperationsProjection> {
|
|
254
|
+
// The comparison runtime deliberately has no lifecycle daily aggregates in
|
|
255
|
+
// its snapshot. Its immutable filesystem contains only canonical records,
|
|
256
|
+
// while the SQL views supply the first-fold facts. Never enter the legacy
|
|
257
|
+
// quick projection here: it requires lifecycle daily read models and would
|
|
258
|
+
// make an otherwise valid SQLite dashboard fail on first paint.
|
|
259
|
+
const projection =
|
|
260
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
261
|
+
? buildSqliteOperationsProjection(filesystem, layout, filter, options)
|
|
262
|
+
: await buildQuickOperationsProjection(filesystem, layout, filter, options);
|
|
263
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem)) return projection;
|
|
264
|
+
const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
|
|
265
|
+
const aggregate =
|
|
266
|
+
filter.from === null && filter.to === null
|
|
267
|
+
? filesystem.firstFoldViewportAllTime().aggregate
|
|
268
|
+
: (cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter));
|
|
269
|
+
return {
|
|
270
|
+
...projection,
|
|
271
|
+
summary: {
|
|
272
|
+
...projection.summary,
|
|
273
|
+
run_count: aggregate.total_run_count,
|
|
274
|
+
overview_metrics: {
|
|
275
|
+
...projection.summary.overview_metrics,
|
|
276
|
+
duration_ms: aggregate.duration_ms,
|
|
277
|
+
tokens: aggregate.total_tokens,
|
|
278
|
+
cost_brl: aggregate.cost_brl,
|
|
279
|
+
execution_day_count: aggregate.execution_day_count,
|
|
280
|
+
data_source: "run_records",
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* SQLite's files snapshot intentionally excludes derived lifecycle manifests.
|
|
288
|
+
* Build the Operations first fold from its canonical SQL views instead of
|
|
289
|
+
* treating a missing manifest as a broken workspace.
|
|
290
|
+
*/
|
|
291
|
+
function buildSqliteOperationsProjection(
|
|
292
|
+
filesystem: SqliteReadModelFileSystem,
|
|
293
|
+
layout: WorkspaceLayout,
|
|
294
|
+
filter: { from: string | null; to: string | null },
|
|
295
|
+
options: {
|
|
296
|
+
includeBoardTasks?: boolean;
|
|
297
|
+
includeExtended?: boolean;
|
|
298
|
+
includePeriods?: boolean;
|
|
299
|
+
includeTopTasks?: boolean;
|
|
300
|
+
},
|
|
301
|
+
): OperationsProjection {
|
|
302
|
+
const cachedMonthlyQuick = filesystem.firstFoldMonthlyQuick(filter);
|
|
303
|
+
const fullAllTime =
|
|
304
|
+
filter.from === null && filter.to === null
|
|
305
|
+
? options.includeExtended === true
|
|
306
|
+
? filesystem.firstFoldAllTime()
|
|
307
|
+
: null
|
|
308
|
+
: null;
|
|
309
|
+
const allTime =
|
|
310
|
+
fullAllTime ??
|
|
311
|
+
(filter.from === null && filter.to === null ? filesystem.firstFoldViewportAllTime() : null);
|
|
312
|
+
const page = <T extends "task" | "spec" | "prd">(kind: T) =>
|
|
313
|
+
cachedMonthlyQuick?.pages[kind] ?? filesystem.summaryPage(kind, filter, 0, 10_000).rows;
|
|
314
|
+
const includeEntities = allTime === null || options.includeBoardTasks === true;
|
|
315
|
+
const tasks = includeEntities ? page("task") : [];
|
|
316
|
+
const specs = includeEntities ? page("spec") : [];
|
|
317
|
+
const prds = includeEntities ? page("prd") : [];
|
|
318
|
+
const aggregate =
|
|
319
|
+
allTime?.aggregate ?? cachedMonthlyQuick?.aggregate ?? filesystem.firstFoldAggregate(filter);
|
|
320
|
+
const taskCount = allTime?.aggregate.task_count ?? tasks.length;
|
|
321
|
+
const allTimeTopTasks =
|
|
322
|
+
filter.from === null &&
|
|
323
|
+
filter.to === null &&
|
|
324
|
+
(fullAllTime !== null || options.includeTopTasks === true)
|
|
325
|
+
? (fullAllTime?.top_tasks ?? filesystem.firstFoldTopTasksAllTime())
|
|
326
|
+
: null;
|
|
327
|
+
const topTasks =
|
|
328
|
+
allTimeTopTasks === null
|
|
329
|
+
? (cachedMonthlyQuick?.top_tasks ?? filesystem.topTasks(filter)).map((task) => ({
|
|
330
|
+
task_id: task.task_id,
|
|
331
|
+
duration_ms: task.duration_ms,
|
|
332
|
+
total_tokens: task.total_tokens,
|
|
333
|
+
cost_brl: task.cost_brl,
|
|
334
|
+
run_count: task.run_count,
|
|
335
|
+
}))
|
|
336
|
+
: projectAllTimeTopTasks(allTimeTopTasks);
|
|
337
|
+
const macroMetrics =
|
|
338
|
+
fullAllTime === null
|
|
339
|
+
? undefined
|
|
340
|
+
: (Object.fromEntries(
|
|
341
|
+
(["specs", "prds"] as const).map((kind) => {
|
|
342
|
+
const metric = fullAllTime.macro_metrics[kind];
|
|
343
|
+
return [
|
|
344
|
+
kind,
|
|
345
|
+
{
|
|
346
|
+
status_counts: metric.status_counts,
|
|
347
|
+
classification_counts: metric.classification_counts,
|
|
348
|
+
entity_count: metric.entity_count,
|
|
349
|
+
total_run_count: metric.total_run_count,
|
|
350
|
+
total_duration_ms: metric.total_duration_ms,
|
|
351
|
+
total_tokens: metric.total_tokens,
|
|
352
|
+
total_cost_usd: metric.total_cost_usd,
|
|
353
|
+
total_cost_brl: metric.total_cost_brl,
|
|
354
|
+
average_duration_ms: metric.average_duration_ms,
|
|
355
|
+
average_tokens: metric.average_tokens,
|
|
356
|
+
average_cost_usd: metric.average_cost_usd,
|
|
357
|
+
average_cost_brl: metric.average_cost_brl,
|
|
358
|
+
latest_created_at: metric.latest_created_at,
|
|
359
|
+
top_entities: metric.top_entities.map((entity) => ({
|
|
360
|
+
entity_id: entity.entity_id,
|
|
361
|
+
title: entity.title,
|
|
362
|
+
duration_ms: entity.total_duration_ms,
|
|
363
|
+
total_tokens: entity.total_tokens,
|
|
364
|
+
total_cost_usd: entity.total_cost_usd,
|
|
365
|
+
cost_brl: entity.total_cost_brl,
|
|
366
|
+
run_count: entity.total_run_count,
|
|
367
|
+
duration_share: entity.duration_share,
|
|
368
|
+
})),
|
|
369
|
+
},
|
|
370
|
+
];
|
|
371
|
+
}),
|
|
372
|
+
) as NonNullable<
|
|
373
|
+
NonNullable<OperationsProjection["summary"]["overview_metrics"]>["macro_metrics"]
|
|
374
|
+
>);
|
|
375
|
+
const statuses: Record<string, number> = allTime?.task_status_counts ?? {};
|
|
376
|
+
const risks: Record<string, number> = allTime?.task_risk_counts ?? {};
|
|
377
|
+
if (allTime === null) {
|
|
378
|
+
for (const task of tasks) {
|
|
379
|
+
const status = typeof task.status === "string" ? task.status : "draft";
|
|
380
|
+
const risk = typeof task.risk === "string" ? task.risk : "standard";
|
|
381
|
+
statuses[status] = (statuses[status] ?? 0) + 1;
|
|
382
|
+
risks[risk] = (risks[risk] ?? 0) + 1;
|
|
145
383
|
}
|
|
146
|
-
} catch {
|
|
147
|
-
// A configuração visual nunca deve impedir a inicialização do dashboard.
|
|
148
384
|
}
|
|
149
|
-
|
|
385
|
+
const latest =
|
|
386
|
+
allTime?.aggregate.latest_task_created_at ??
|
|
387
|
+
[...tasks, ...specs, ...prds]
|
|
388
|
+
.map((row) => (typeof row.occurred_at === "string" ? row.occurred_at : null))
|
|
389
|
+
.filter((value): value is string => value !== null)
|
|
390
|
+
.sort()
|
|
391
|
+
.at(-1) ??
|
|
392
|
+
null;
|
|
393
|
+
const unavailableUsage = {
|
|
394
|
+
events: 0,
|
|
395
|
+
tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
|
|
396
|
+
cost: { usd: null, brl: null },
|
|
397
|
+
coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
|
|
398
|
+
};
|
|
399
|
+
const totalUsage = {
|
|
400
|
+
...unavailableUsage,
|
|
401
|
+
tokens: { ...unavailableUsage.tokens, total: aggregate.total_tokens },
|
|
402
|
+
cost: { ...unavailableUsage.cost, brl: aggregate.cost_brl },
|
|
403
|
+
};
|
|
404
|
+
const usage = {
|
|
405
|
+
conversation: unavailableUsage,
|
|
406
|
+
planning: unavailableUsage,
|
|
407
|
+
execution: unavailableUsage,
|
|
408
|
+
total: totalUsage,
|
|
409
|
+
new_work: unavailableUsage,
|
|
410
|
+
rework: unavailableUsage,
|
|
411
|
+
};
|
|
412
|
+
const dailyPeriods = options.includePeriods === true ? filesystem.dailyPeriods(filter) : {};
|
|
413
|
+
const hasUnknownUsage = Object.values(dailyPeriods).some(
|
|
414
|
+
(period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
|
|
415
|
+
);
|
|
416
|
+
const taskProjection = (row: Record<string, unknown>) => ({
|
|
417
|
+
schema_version: 2 as const,
|
|
418
|
+
id: String(row.id),
|
|
419
|
+
revision: 1,
|
|
420
|
+
change_id: "",
|
|
421
|
+
title: String(row.title ?? row.id),
|
|
422
|
+
short_name: String(row.title ?? row.id),
|
|
423
|
+
objective: "",
|
|
424
|
+
status: normalizeTaskStatus(row.status),
|
|
425
|
+
attempt_count: null,
|
|
426
|
+
risk: normalizeRisk(row.risk),
|
|
427
|
+
priority: "medium" as const,
|
|
428
|
+
owner: null,
|
|
429
|
+
requirement_ids: [],
|
|
430
|
+
acceptance_ids: [],
|
|
431
|
+
depends_on: [],
|
|
432
|
+
write_scopes: [],
|
|
433
|
+
created_at: String(row.occurred_at),
|
|
434
|
+
updated_at: String(row.occurred_at),
|
|
435
|
+
started_at: null,
|
|
436
|
+
finished_at: null,
|
|
437
|
+
result: null,
|
|
438
|
+
artifact_path: ".agents/records",
|
|
439
|
+
progress: 0,
|
|
440
|
+
spec_id: null,
|
|
441
|
+
execution_started: false,
|
|
442
|
+
telemetry: {
|
|
443
|
+
duration_ms: nullableMetric(row.total_duration),
|
|
444
|
+
tokens: nullableMetric(row.total_tokens),
|
|
445
|
+
cost_brl: nullableMetric(row.total_cost_brl),
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
const macroProjection = (row: Record<string, unknown>, kind: "prd" | "spec") => {
|
|
449
|
+
const entityUsage = {
|
|
450
|
+
...unavailableUsage,
|
|
451
|
+
tokens: { ...unavailableUsage.tokens, total: nullableMetric(row.total_tokens) },
|
|
452
|
+
cost: { ...unavailableUsage.cost, brl: nullableMetric(row.total_cost_brl) },
|
|
453
|
+
};
|
|
150
454
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
455
|
+
id: String(row.id),
|
|
456
|
+
title: String(row.title ?? row.id),
|
|
457
|
+
created_at: String(row.occurred_at),
|
|
458
|
+
updated_at: String(row.occurred_at),
|
|
459
|
+
kind,
|
|
460
|
+
parent_id: null,
|
|
461
|
+
artifact_path: ".agents/records",
|
|
462
|
+
lifecycle_status: String(row.status ?? "draft"),
|
|
463
|
+
work_status: normalizeMacroStatus(row.status),
|
|
464
|
+
execution_ids: [],
|
|
465
|
+
task_ids: [],
|
|
466
|
+
planned_tasks: [],
|
|
467
|
+
canonical_run_ids: [],
|
|
468
|
+
run_ids: [],
|
|
469
|
+
root_execution_run_ids: [],
|
|
470
|
+
execution_count: 0,
|
|
471
|
+
task_count: 0,
|
|
472
|
+
run_count: nullableMetric(row.invocation_count) ?? 0,
|
|
473
|
+
duration_ms: nullableMetric(row.total_duration),
|
|
474
|
+
decisions: { human: 0, agent: 0 },
|
|
475
|
+
rework_run_count: 0,
|
|
476
|
+
quality: {
|
|
477
|
+
completed_tasks: 0,
|
|
478
|
+
task_completion_rate: 0,
|
|
479
|
+
terminal_runs: 0,
|
|
480
|
+
successful_runs: 0,
|
|
481
|
+
terminal_success_rate: 0,
|
|
482
|
+
rework_rate: 0,
|
|
483
|
+
},
|
|
484
|
+
usage: {
|
|
485
|
+
conversation: unavailableUsage,
|
|
486
|
+
planning: unavailableUsage,
|
|
487
|
+
execution: unavailableUsage,
|
|
488
|
+
total: entityUsage,
|
|
489
|
+
new_work: unavailableUsage,
|
|
490
|
+
rework: unavailableUsage,
|
|
491
|
+
},
|
|
153
492
|
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
493
|
+
};
|
|
494
|
+
return {
|
|
495
|
+
schema_version: 2,
|
|
496
|
+
layout_version: layout.version as 2 | 3,
|
|
497
|
+
readonly: true,
|
|
498
|
+
source: ".agents/records",
|
|
499
|
+
filters: filter,
|
|
500
|
+
summary: {
|
|
501
|
+
prd_count: allTime?.aggregate.prd_count ?? prds.length,
|
|
502
|
+
spec_count: allTime?.aggregate.spec_count ?? specs.length,
|
|
503
|
+
execution_count: 0,
|
|
504
|
+
task_count: taskCount,
|
|
505
|
+
run_count: aggregate.total_run_count,
|
|
506
|
+
overview_metrics: {
|
|
507
|
+
duration_ms: aggregate.duration_ms,
|
|
508
|
+
execution_day_count: aggregate.execution_day_count,
|
|
509
|
+
tokens: aggregate.total_tokens,
|
|
510
|
+
cost_brl: aggregate.cost_brl,
|
|
511
|
+
prd_count: allTime?.aggregate.prd_count ?? prds.length,
|
|
512
|
+
spec_count: allTime?.aggregate.spec_count ?? specs.length,
|
|
513
|
+
selected_counts: {
|
|
514
|
+
prds: allTime?.aggregate.prd_count ?? prds.length,
|
|
515
|
+
specs: allTime?.aggregate.spec_count ?? specs.length,
|
|
516
|
+
},
|
|
517
|
+
task_count: taskCount,
|
|
518
|
+
completed_prd_count:
|
|
519
|
+
allTime?.aggregate.completed_prd_count ??
|
|
520
|
+
prds.filter((row) => row.status === "completed").length,
|
|
521
|
+
completed_spec_count:
|
|
522
|
+
allTime?.aggregate.completed_spec_count ??
|
|
523
|
+
specs.filter((row) => row.status === "completed").length,
|
|
524
|
+
completed_task_count: allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0,
|
|
525
|
+
task_status_counts: statuses,
|
|
526
|
+
task_risk_counts: risks,
|
|
527
|
+
top_tasks: topTasks,
|
|
528
|
+
...(macroMetrics === undefined ? {} : { macro_metrics: macroMetrics }),
|
|
529
|
+
latest_task_created_at: latest,
|
|
530
|
+
coverage: { duration: 1, tokens: hasUnknownUsage ? 0 : 1, cost: hasUnknownUsage ? 0 : 1 },
|
|
531
|
+
data_source: "run_records",
|
|
532
|
+
partial: true,
|
|
533
|
+
},
|
|
534
|
+
decisions: { human: 0, agent: 0 },
|
|
535
|
+
source_latest_at: latest,
|
|
536
|
+
quality: {
|
|
537
|
+
completed_tasks: statuses.completed ?? 0,
|
|
538
|
+
task_completion_rate:
|
|
539
|
+
taskCount === 0
|
|
540
|
+
? 0
|
|
541
|
+
: (allTime?.aggregate.completed_task_count ?? statuses.completed ?? 0) / taskCount,
|
|
542
|
+
terminal_runs: 0,
|
|
543
|
+
successful_runs: 0,
|
|
544
|
+
terminal_success_rate: 0,
|
|
545
|
+
rework_rate: 0,
|
|
546
|
+
},
|
|
547
|
+
reconciliation: {
|
|
548
|
+
phase_tokens_match_total: true,
|
|
549
|
+
phase_usd_match_total: true,
|
|
550
|
+
phase_brl_match_total: true,
|
|
551
|
+
origin_tokens_match_total: true,
|
|
552
|
+
},
|
|
553
|
+
usage,
|
|
554
|
+
// The period tabs are part of the first-fold contract too. Supplying an
|
|
555
|
+
// empty object for the all-time fast path made Daily, Monthly and Annual
|
|
556
|
+
// look empty even though the SQLite daily aggregate already existed.
|
|
557
|
+
periods: { daily: options.includePeriods === true ? dailyPeriods : {} },
|
|
558
|
+
},
|
|
559
|
+
origins: { conversation: usage, task: usage, spec: usage, prd: usage },
|
|
560
|
+
prds: prds.map((row) => macroProjection(row, "prd")),
|
|
561
|
+
specs: specs.map((row) => macroProjection(row, "spec")),
|
|
562
|
+
tasks: options.includeBoardTasks ? tasks.map(taskProjection) : [],
|
|
563
|
+
changes: [],
|
|
564
|
+
executions: [],
|
|
565
|
+
runs: [],
|
|
566
|
+
decisions: [],
|
|
567
|
+
evidences: [],
|
|
568
|
+
validations: [],
|
|
569
|
+
};
|
|
166
570
|
}
|
|
167
571
|
|
|
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;
|
|
572
|
+
function nullableMetric(value: unknown): number | null {
|
|
573
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
174
574
|
}
|
|
175
575
|
|
|
176
|
-
|
|
177
|
-
|
|
576
|
+
function normalizeTaskStatus(
|
|
577
|
+
value: unknown,
|
|
578
|
+
):
|
|
579
|
+
| "draft"
|
|
580
|
+
| "planned"
|
|
581
|
+
| "executing"
|
|
582
|
+
| "verifying"
|
|
583
|
+
| "completed"
|
|
584
|
+
| "failed"
|
|
585
|
+
| "blocked"
|
|
586
|
+
| "cancelled" {
|
|
587
|
+
return [
|
|
588
|
+
"draft",
|
|
589
|
+
"planned",
|
|
590
|
+
"executing",
|
|
591
|
+
"verifying",
|
|
592
|
+
"completed",
|
|
593
|
+
"failed",
|
|
594
|
+
"blocked",
|
|
595
|
+
"cancelled",
|
|
596
|
+
].includes(String(value))
|
|
597
|
+
? (String(value) as
|
|
598
|
+
| "draft"
|
|
599
|
+
| "planned"
|
|
600
|
+
| "executing"
|
|
601
|
+
| "verifying"
|
|
602
|
+
| "completed"
|
|
603
|
+
| "failed"
|
|
604
|
+
| "blocked"
|
|
605
|
+
| "cancelled")
|
|
606
|
+
: "draft";
|
|
178
607
|
}
|
|
179
608
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
609
|
+
function normalizeRisk(value: unknown): "lite" | "standard" | "critical" {
|
|
610
|
+
return ["lite", "standard", "critical"].includes(String(value))
|
|
611
|
+
? (String(value) as "lite" | "standard" | "critical")
|
|
612
|
+
: "standard";
|
|
183
613
|
}
|
|
184
614
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
615
|
+
function normalizeMacroStatus(
|
|
616
|
+
value: unknown,
|
|
617
|
+
): "planning" | "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled" {
|
|
618
|
+
const status = String(value);
|
|
619
|
+
if (["executing", "verifying", "completed", "blocked", "failed", "cancelled"].includes(status))
|
|
620
|
+
return status as "executing" | "verifying" | "completed" | "blocked" | "failed" | "cancelled";
|
|
621
|
+
return "planning";
|
|
189
622
|
}
|
|
190
623
|
|
|
191
|
-
async function
|
|
624
|
+
async function firstFoldCostSource(
|
|
192
625
|
filesystem: WorkspaceFileSystem,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
626
|
+
filters: Parameters<SqliteReadModelFileSystem["invocationCostRows"]>[0] = {
|
|
627
|
+
from: null,
|
|
628
|
+
to: null,
|
|
629
|
+
},
|
|
630
|
+
): Promise<CostSummarySource> {
|
|
631
|
+
if (filesystem instanceof SqliteReadModelFileSystem) {
|
|
632
|
+
return filesystem.costMonitorSummaryAggregate(filters) as CostSummarySource;
|
|
197
633
|
}
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
634
|
+
const daily = await new LifecycleManifestStore(filesystem).readDailyReadModels("overview");
|
|
635
|
+
if (daily === null) throw new Error("Lifecycle daily overview is not initialized");
|
|
636
|
+
return {
|
|
637
|
+
periods: {
|
|
638
|
+
daily: Object.fromEntries(
|
|
639
|
+
Object.entries(daily.days).map(([day, model]) => [day, model.overview]),
|
|
640
|
+
),
|
|
641
|
+
},
|
|
642
|
+
} satisfies CostSummarySource;
|
|
205
643
|
}
|
|
206
644
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
645
|
+
function buildSqliteTaskTrace(
|
|
646
|
+
filesystem: SqliteReadModelFileSystem,
|
|
647
|
+
layout: WorkspaceLayout,
|
|
648
|
+
taskId: string,
|
|
649
|
+
): TaskTrace {
|
|
650
|
+
const initial = filesystem.canonicalTraceFiles([taskId]);
|
|
651
|
+
const taskFile = initial.find(
|
|
652
|
+
(row) =>
|
|
653
|
+
row.path.includes("/.agents/records/tasks/") || row.path.includes(".agents/records/tasks/"),
|
|
654
|
+
);
|
|
655
|
+
if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
|
|
656
|
+
const scalar = (key: string): string | null => {
|
|
657
|
+
const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(taskFile.content)?.[1] ?? "";
|
|
658
|
+
const value = new RegExp(`^${key}:\\s*(.+?)\\s*$`, "m").exec(frontMatter)?.[1]?.trim();
|
|
659
|
+
if (value === undefined || value === "null") return null;
|
|
660
|
+
return value.replace(/^['"]|['"]$/g, "");
|
|
214
661
|
};
|
|
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
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
remember(blocked, id, "Execution orphaned after the reconciliation grace period.");
|
|
246
|
-
});
|
|
247
|
-
const errors = Array.isArray(records.reconciliation_errors) ? records.reconciliation_errors : [];
|
|
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));
|
|
662
|
+
const relatedId = (key: string, prefix: string): string | null =>
|
|
663
|
+
scalar(key) ?? new RegExp(`\\b${prefix}[A-Z0-9-]+`).exec(taskFile.content)?.[0] ?? null;
|
|
664
|
+
const changeId = relatedId("change_id", "CHG-") ?? taskId;
|
|
665
|
+
const specId = relatedId("spec_id", "SPEC-");
|
|
666
|
+
const prdId = relatedId("prd_id", "PRD-");
|
|
667
|
+
const markers = [taskId, changeId, specId, prdId].filter(
|
|
668
|
+
(value): value is string => value !== null,
|
|
669
|
+
);
|
|
670
|
+
const documents = filesystem
|
|
671
|
+
.canonicalTraceFiles(markers)
|
|
672
|
+
.filter(
|
|
673
|
+
(row) => /\.(?:md|json|jsonl|txt|log)$/i.test(row.path) && row.content.length <= 250_000,
|
|
674
|
+
)
|
|
675
|
+
.map((row) => {
|
|
676
|
+
const classification = classifyTraceDocument(row.path);
|
|
677
|
+
const timestamps = timestampsFromContent(row.content);
|
|
678
|
+
return {
|
|
679
|
+
id: createHash("sha256").update(row.path).digest("hex").slice(0, 16),
|
|
680
|
+
path: row.path,
|
|
681
|
+
title:
|
|
682
|
+
row.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? row.path.split("/").at(-1) ?? 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,111 @@ 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 =
|
|
746
|
+
options.refreshReadModel ??
|
|
747
|
+
(async () => {
|
|
748
|
+
if (!isSqliteSnapshot) return;
|
|
749
|
+
// Explicit user refreshes must replace the immutable generation with the
|
|
750
|
+
// current canonical records. Keep stale-seed fallback exclusively for
|
|
751
|
+
// startup warmth, never for this visible convergence request.
|
|
752
|
+
await filesystem.rebuildAndRefresh(() =>
|
|
753
|
+
rebuildSqliteReadModel(workspaceRoot, { forceCanonical: true }),
|
|
754
|
+
);
|
|
755
|
+
});
|
|
295
756
|
const workspaceKey = dashboardWorkspaceKey(workspaceRoot);
|
|
296
757
|
const syncAgentAdapters = options.syncAgentAdapters ?? (() => runAgentAdapterSync(workspaceRoot));
|
|
297
758
|
let catalogWriteTail = Promise.resolve();
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
759
|
+
type DashboardSnapshot = Awaited<ReturnType<typeof dashboardSnapshot>>;
|
|
760
|
+
const snapshotInFlight = new Map<string, Promise<DashboardSnapshot>>();
|
|
761
|
+
const cachedSnapshots = new Map<string, { snapshot: DashboardSnapshot; cachedAt: number }>();
|
|
762
|
+
const aggregatePayloads = new Map<string, { payload: unknown; cachedAt: number }>();
|
|
763
|
+
const aggregateInFlight = new Map<string, Promise<unknown>>();
|
|
764
|
+
const readAggregate = async <T>(
|
|
765
|
+
key: string,
|
|
766
|
+
build: () => Promise<T>,
|
|
767
|
+
fresh = false,
|
|
768
|
+
): Promise<T> => {
|
|
769
|
+
const cached = aggregatePayloads.get(key);
|
|
770
|
+
// Kanban cards are canonical projections. SQLite generations are immutable,
|
|
771
|
+
// so retain their expensive board projection until an explicit refresh
|
|
772
|
+
// clears this map rather than expiring it on the short snapshot TTL.
|
|
304
773
|
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
Date.now() -
|
|
774
|
+
!fresh &&
|
|
775
|
+
cached !== undefined &&
|
|
776
|
+
(key.startsWith("operations:board:") || Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS)
|
|
308
777
|
)
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
778
|
+
return cached.payload as T;
|
|
779
|
+
const pending = aggregateInFlight.get(key);
|
|
780
|
+
// A bootstrap fresh read must not observe a normal request that began
|
|
781
|
+
// before reload. Wait for it to settle, then rebuild and replace cache.
|
|
782
|
+
if (pending !== undefined) {
|
|
783
|
+
if (!fresh) return pending as Promise<T>;
|
|
784
|
+
await pending;
|
|
785
|
+
return readAggregate(key, build, true);
|
|
786
|
+
}
|
|
787
|
+
const pendingBuild = build()
|
|
788
|
+
.then((payload) => {
|
|
789
|
+
aggregatePayloads.set(key, { payload, cachedAt: Date.now() });
|
|
790
|
+
return payload;
|
|
791
|
+
})
|
|
792
|
+
.finally(() => aggregateInFlight.delete(key));
|
|
793
|
+
aggregateInFlight.set(key, pendingBuild);
|
|
794
|
+
return pendingBuild;
|
|
795
|
+
};
|
|
796
|
+
const snapshotKey = (context: Context) => {
|
|
797
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
798
|
+
return `${selectedCurrency(context)}:${from ?? ""}:${to ?? ""}`;
|
|
799
|
+
};
|
|
800
|
+
const clearSnapshotCache = () => {
|
|
801
|
+
cachedSnapshots.clear();
|
|
802
|
+
aggregatePayloads.clear();
|
|
803
|
+
aggregateInFlight.clear();
|
|
804
|
+
};
|
|
805
|
+
let projectionRefreshInFlight: Promise<void> | null = null;
|
|
806
|
+
const rebuildProjection = async () => {
|
|
807
|
+
if (projectionRefreshInFlight !== null) return projectionRefreshInFlight;
|
|
808
|
+
projectionRefreshInFlight = refreshReadModel()
|
|
809
|
+
.then(() => clearSnapshotCache())
|
|
810
|
+
.finally(() => {
|
|
811
|
+
projectionRefreshInFlight = null;
|
|
812
|
+
});
|
|
813
|
+
return projectionRefreshInFlight;
|
|
814
|
+
};
|
|
815
|
+
const readSnapshot = (context: Context) => {
|
|
816
|
+
const key = snapshotKey(context);
|
|
817
|
+
const cached = cachedSnapshots.get(key);
|
|
818
|
+
if (cached !== undefined && Date.now() - cached.cachedAt < SNAPSHOT_CACHE_TTL_MS) {
|
|
819
|
+
context.header("X-ZAIP-Snapshot-Cache", "hit");
|
|
820
|
+
return Promise.resolve(cached.snapshot);
|
|
821
|
+
}
|
|
822
|
+
const pending = snapshotInFlight.get(key);
|
|
823
|
+
if (pending !== undefined) {
|
|
824
|
+
context.header("X-ZAIP-Snapshot-Cache", "inflight");
|
|
825
|
+
return pending;
|
|
826
|
+
}
|
|
827
|
+
context.header("X-ZAIP-Snapshot-Cache", "miss");
|
|
828
|
+
const inFlight = readLayout()
|
|
829
|
+
.then((layout) => dashboardSnapshot(filesystem, workspaceRoot, context, layout))
|
|
312
830
|
.then((snapshot) => {
|
|
313
|
-
|
|
314
|
-
cachedSnapshotKey = key;
|
|
315
|
-
cachedSnapshotAt = Date.now();
|
|
831
|
+
cachedSnapshots.set(key, { snapshot, cachedAt: Date.now() });
|
|
316
832
|
return snapshot;
|
|
317
833
|
})
|
|
318
834
|
.finally(() => {
|
|
319
|
-
snapshotInFlight
|
|
835
|
+
snapshotInFlight.delete(key);
|
|
320
836
|
});
|
|
321
|
-
|
|
837
|
+
snapshotInFlight.set(key, inFlight);
|
|
838
|
+
return inFlight;
|
|
322
839
|
};
|
|
323
840
|
|
|
324
841
|
app.use("*", async (context, next) => {
|
|
@@ -342,32 +859,44 @@ export function createDashboardApp(
|
|
|
342
859
|
app.use(`${API_PREFIX}/*`, async (context, next) => {
|
|
343
860
|
const settingsWrite =
|
|
344
861
|
context.req.method === "PUT" &&
|
|
345
|
-
(context.req.path === `${API_PREFIX}/
|
|
346
|
-
context.req.path === `${API_PREFIX}/
|
|
347
|
-
|
|
348
|
-
|
|
862
|
+
(context.req.path === `${API_PREFIX}/work-priority` ||
|
|
863
|
+
context.req.path === `${API_PREFIX}/project-settings` ||
|
|
864
|
+
(!snapshotReadOnly &&
|
|
865
|
+
(context.req.path === `${API_PREFIX}/pricing` ||
|
|
866
|
+
context.req.path === `${API_PREFIX}/fx-rate` ||
|
|
867
|
+
context.req.path === `${API_PREFIX}/agent-catalog`)));
|
|
868
|
+
// Reconciliation updates canonical lifecycle records through the supported
|
|
869
|
+
// CLI. It does not mutate the disposable SQLite read model, so it remains
|
|
870
|
+
// available while the dashboard serves a read-only snapshot.
|
|
349
871
|
const reconcileWrite =
|
|
350
872
|
context.req.method === "POST" && context.req.path === `${API_PREFIX}/reconcile-open-work`;
|
|
873
|
+
const projectionRefreshWrite =
|
|
874
|
+
context.req.method === "POST" && context.req.path === `${API_PREFIX}/refresh-projection`;
|
|
351
875
|
if (
|
|
352
876
|
context.req.method !== "GET" &&
|
|
353
877
|
context.req.method !== "HEAD" &&
|
|
354
878
|
!settingsWrite &&
|
|
355
|
-
!reconcileWrite
|
|
879
|
+
!reconcileWrite &&
|
|
880
|
+
!projectionRefreshWrite
|
|
356
881
|
) {
|
|
357
882
|
context.header(
|
|
358
883
|
"Allow",
|
|
359
884
|
context.req.path === `${API_PREFIX}/pricing` ||
|
|
360
885
|
context.req.path === `${API_PREFIX}/fx-rate` ||
|
|
361
886
|
context.req.path === `${API_PREFIX}/agent-catalog` ||
|
|
362
|
-
context.req.path === `${API_PREFIX}/project-settings`
|
|
887
|
+
context.req.path === `${API_PREFIX}/project-settings` ||
|
|
888
|
+
context.req.path === `${API_PREFIX}/work-priority`
|
|
363
889
|
? "GET, HEAD, PUT"
|
|
364
890
|
: context.req.path === `${API_PREFIX}/reconcile-open-work`
|
|
365
891
|
? "POST"
|
|
366
|
-
:
|
|
892
|
+
: context.req.path === `${API_PREFIX}/refresh-projection`
|
|
893
|
+
? "POST"
|
|
894
|
+
: "GET, HEAD",
|
|
367
895
|
);
|
|
368
896
|
return context.json({ error: "Dashboard API is read-only" }, 405);
|
|
369
897
|
}
|
|
370
|
-
if (context.req.path === `${API_PREFIX}/health`
|
|
898
|
+
if (context.req.path === `${API_PREFIX}/health` || context.req.path === `${API_PREFIX}/ready`)
|
|
899
|
+
return next();
|
|
371
900
|
|
|
372
901
|
const origin = context.req.header("origin");
|
|
373
902
|
if (origin !== undefined && !isAllowedOrigin(origin)) {
|
|
@@ -392,7 +921,7 @@ export function createDashboardApp(
|
|
|
392
921
|
|
|
393
922
|
app.on(["GET", "HEAD"], `${API_PREFIX}/health`, async (context) => {
|
|
394
923
|
try {
|
|
395
|
-
const layout = await
|
|
924
|
+
const layout = await readLayout();
|
|
396
925
|
return headOrJson(context, {
|
|
397
926
|
kind: RUNTIME_KIND,
|
|
398
927
|
workspaceKey,
|
|
@@ -401,6 +930,8 @@ export function createDashboardApp(
|
|
|
401
930
|
api_version: 2,
|
|
402
931
|
workspace_layout: layout.version,
|
|
403
932
|
read_only: true,
|
|
933
|
+
storage: isSqliteSnapshot ? filesystem.metadata() : { storage_mode: "filesystem" },
|
|
934
|
+
snapshot_state: snapshotState,
|
|
404
935
|
});
|
|
405
936
|
} catch (error) {
|
|
406
937
|
return headOrJson(
|
|
@@ -419,17 +950,184 @@ export function createDashboardApp(
|
|
|
419
950
|
}
|
|
420
951
|
});
|
|
421
952
|
|
|
953
|
+
// Launcher readiness proves a canonical projection can be read; health alone
|
|
954
|
+
// intentionally remains a lightweight process/layout probe.
|
|
955
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/ready`, async (context) => {
|
|
956
|
+
try {
|
|
957
|
+
const layout = await readLayout();
|
|
958
|
+
if (isSqliteSnapshot) {
|
|
959
|
+
const metadata = filesystem.metadata();
|
|
960
|
+
return headOrJson(context, {
|
|
961
|
+
kind: RUNTIME_KIND,
|
|
962
|
+
workspaceKey,
|
|
963
|
+
status: "ready",
|
|
964
|
+
workspace_layout: layout.version,
|
|
965
|
+
storage_mode: metadata.storage_mode,
|
|
966
|
+
activity_count: metadata.activity_count,
|
|
967
|
+
// Readiness is only an immutable-generation probe. The default
|
|
968
|
+
// monthly Operations projection was materialized during rebuild;
|
|
969
|
+
// /ready must never start a competing projection in the background.
|
|
970
|
+
day_count: metadata.daily_aggregate_count,
|
|
971
|
+
snapshot_state: snapshotState,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
const projection = await buildFirstFoldQuickProjection(filesystem, layout, {
|
|
975
|
+
from: null,
|
|
976
|
+
to: null,
|
|
977
|
+
});
|
|
978
|
+
return headOrJson(context, {
|
|
979
|
+
kind: RUNTIME_KIND,
|
|
980
|
+
workspaceKey,
|
|
981
|
+
status: "ready",
|
|
982
|
+
source_latest_at: projection.summary.source_latest_at,
|
|
983
|
+
});
|
|
984
|
+
} catch (error) {
|
|
985
|
+
return headOrJson(
|
|
986
|
+
context,
|
|
987
|
+
{
|
|
988
|
+
kind: RUNTIME_KIND,
|
|
989
|
+
workspaceKey,
|
|
990
|
+
status: "unavailable",
|
|
991
|
+
error: error instanceof Error ? error.message : String(error),
|
|
992
|
+
},
|
|
993
|
+
503,
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
|
|
422
998
|
app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
|
|
423
999
|
try {
|
|
424
|
-
const
|
|
1000
|
+
const requestStartedAt = performance.now();
|
|
1001
|
+
const scope = context.req.query("scope");
|
|
1002
|
+
if (
|
|
1003
|
+
scope !== "overview" &&
|
|
1004
|
+
scope !== "quick" &&
|
|
1005
|
+
scope !== "detail" &&
|
|
1006
|
+
scope !== "board" &&
|
|
1007
|
+
scope !== "classification"
|
|
1008
|
+
)
|
|
1009
|
+
return headOrJson(context, (await readSnapshot(context)).operations);
|
|
1010
|
+
const layout = await readLayout();
|
|
1011
|
+
const layoutMs = performance.now() - requestStartedAt;
|
|
425
1012
|
const { from, to } = projectionPeriodFilter(context);
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1013
|
+
if (scope === "board") {
|
|
1014
|
+
const stage = boardStage(context.req.query("stage"));
|
|
1015
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1016
|
+
const limit = Math.min(
|
|
1017
|
+
50,
|
|
1018
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "10", 10) || 10),
|
|
1019
|
+
);
|
|
1020
|
+
const board = await readAggregate(
|
|
1021
|
+
`operations:board:${from ?? ""}:${to ?? ""}`,
|
|
1022
|
+
async () => {
|
|
1023
|
+
const projection = await buildFirstFoldQuickProjection(
|
|
1024
|
+
filesystem,
|
|
1025
|
+
layout,
|
|
1026
|
+
{ from, to },
|
|
1027
|
+
{ includeBoardTasks: true },
|
|
1028
|
+
);
|
|
1029
|
+
return {
|
|
1030
|
+
projection,
|
|
1031
|
+
priority: await readWorkPriority(canonicalFilesystem, knownWorkIds(projection)),
|
|
1032
|
+
};
|
|
1033
|
+
},
|
|
1034
|
+
);
|
|
1035
|
+
const filters = boardFilters(context);
|
|
1036
|
+
const pageKey = JSON.stringify({ from, to, stage, offset, limit, ...filters });
|
|
1037
|
+
const page = await readAggregate(`operations:board-page:${pageKey}`, async () =>
|
|
1038
|
+
boardPage(board.projection, stage, offset, limit, board.priority.item_ids, filters),
|
|
1039
|
+
);
|
|
1040
|
+
return headOrJson(context, page, 200, true);
|
|
1041
|
+
}
|
|
1042
|
+
if (scope === "classification") {
|
|
1043
|
+
const kind = context.req.query("kind");
|
|
1044
|
+
const classification = context.req.query("classification");
|
|
1045
|
+
if (kind !== "task" && kind !== "spec" && kind !== "prd")
|
|
1046
|
+
return headOrJson(context, { error: "A valid work kind is required" }, 400);
|
|
1047
|
+
if (
|
|
1048
|
+
!["lite", "standard", "critical", "empty", "structured", "rework"].includes(
|
|
1049
|
+
classification ?? "",
|
|
1050
|
+
)
|
|
1051
|
+
)
|
|
1052
|
+
return headOrJson(context, { error: "A valid classification is required" }, 400);
|
|
1053
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1054
|
+
const limit = Math.min(
|
|
1055
|
+
50,
|
|
1056
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "50", 10) || 50),
|
|
1057
|
+
);
|
|
1058
|
+
const projection = await readAggregate(
|
|
1059
|
+
`operations:classification:${from ?? ""}:${to ?? ""}`,
|
|
1060
|
+
() =>
|
|
1061
|
+
buildFirstFoldQuickProjection(
|
|
1062
|
+
filesystem,
|
|
1063
|
+
layout,
|
|
1064
|
+
{ from, to },
|
|
1065
|
+
{ includeBoardTasks: true },
|
|
1066
|
+
),
|
|
1067
|
+
);
|
|
1068
|
+
return headOrJson(
|
|
1069
|
+
context,
|
|
1070
|
+
classificationPage(
|
|
1071
|
+
projection,
|
|
1072
|
+
kind,
|
|
1073
|
+
classification as BoardClassification,
|
|
1074
|
+
offset,
|
|
1075
|
+
limit,
|
|
1076
|
+
),
|
|
1077
|
+
200,
|
|
1078
|
+
true,
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
if (scope === "quick") {
|
|
1082
|
+
const projectionStartedAt = performance.now();
|
|
1083
|
+
const quick = await readAggregate(
|
|
1084
|
+
`operations:quick:${from ?? ""}:${to ?? ""}`,
|
|
1085
|
+
() =>
|
|
1086
|
+
buildFirstFoldQuickProjection(
|
|
1087
|
+
filesystem,
|
|
1088
|
+
layout,
|
|
1089
|
+
{ from, to },
|
|
1090
|
+
{
|
|
1091
|
+
includeTopTasks: from === null && to === null,
|
|
1092
|
+
},
|
|
1093
|
+
),
|
|
1094
|
+
context.req.query("fresh") === "1",
|
|
1095
|
+
);
|
|
1096
|
+
if (
|
|
1097
|
+
process.env.ZAIP_DASHBOARD_PROFILE_FIRST_FOLD === "1" &&
|
|
1098
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1099
|
+
) {
|
|
1100
|
+
const timings = filesystem.firstFoldViewportProfile();
|
|
1101
|
+
context.header(
|
|
1102
|
+
"Server-Timing",
|
|
1103
|
+
[
|
|
1104
|
+
`layout;dur=${Math.round(layoutMs * 10) / 10}`,
|
|
1105
|
+
`projection;dur=${Math.round((performance.now() - projectionStartedAt) * 10) / 10}`,
|
|
1106
|
+
...Object.entries(timings).map(([name, duration]) => `${name};dur=${duration}`),
|
|
1107
|
+
].join(", "),
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
return headOrJson(context, quick, 200, true);
|
|
1111
|
+
}
|
|
1112
|
+
const projection =
|
|
1113
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1114
|
+
? await buildFirstFoldQuickProjection(
|
|
1115
|
+
filesystem,
|
|
1116
|
+
layout,
|
|
1117
|
+
{ from, to },
|
|
1118
|
+
{
|
|
1119
|
+
includeBoardTasks: scope === "detail",
|
|
1120
|
+
includeExtended: scope === "detail" || scope === "overview",
|
|
1121
|
+
includePeriods: scope === "detail" || scope === "overview",
|
|
1122
|
+
},
|
|
1123
|
+
)
|
|
1124
|
+
: await buildCanonicalOperationsProjection(
|
|
1125
|
+
filesystem,
|
|
1126
|
+
layout,
|
|
1127
|
+
{ from, to },
|
|
1128
|
+
scope === "overview",
|
|
1129
|
+
);
|
|
1130
|
+
return headOrJson(context, projection, 200, scope === "overview");
|
|
433
1131
|
} catch (error) {
|
|
434
1132
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
435
1133
|
return headOrJson(
|
|
@@ -440,6 +1138,146 @@ export function createDashboardApp(
|
|
|
440
1138
|
}
|
|
441
1139
|
});
|
|
442
1140
|
|
|
1141
|
+
registerOperationsRunsRoute(app, filesystem, readAggregate, headOrJson);
|
|
1142
|
+
|
|
1143
|
+
// Explicit SQL read-model API for the comparison dashboard. It never falls
|
|
1144
|
+
// back to filesystem pagination, making first-fold/entity query plans
|
|
1145
|
+
// inspectable while preserving the established Operations contracts.
|
|
1146
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-read-models`, async (context) => {
|
|
1147
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem))
|
|
1148
|
+
return headOrJson(context, { error: "SQLite read model is not active" }, 409);
|
|
1149
|
+
const kind = context.req.query("kind");
|
|
1150
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1151
|
+
const limit = Math.min(
|
|
1152
|
+
200,
|
|
1153
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "20", 10) || 20),
|
|
1154
|
+
);
|
|
1155
|
+
if (kind === "overview") {
|
|
1156
|
+
const period = context.req.query("period");
|
|
1157
|
+
if (period !== "day" && period !== "month" && period !== "year")
|
|
1158
|
+
return headOrJson(context, { error: "Unsupported SQLite overview period" }, 400);
|
|
1159
|
+
return headOrJson(context, filesystem.overviewPage(period, offset, limit), 200, true);
|
|
1160
|
+
}
|
|
1161
|
+
if (kind !== "task" && kind !== "spec" && kind !== "prd")
|
|
1162
|
+
return headOrJson(context, { error: "Unsupported SQLite read model" }, 400);
|
|
1163
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1164
|
+
const filters = operationsPageFilters(context);
|
|
1165
|
+
return headOrJson(
|
|
1166
|
+
context,
|
|
1167
|
+
filesystem.summaryPage(
|
|
1168
|
+
kind,
|
|
1169
|
+
{ from, to, ...filters },
|
|
1170
|
+
offset,
|
|
1171
|
+
limit,
|
|
1172
|
+
context.req.query("cursor") ?? null,
|
|
1173
|
+
),
|
|
1174
|
+
200,
|
|
1175
|
+
true,
|
|
1176
|
+
);
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/sqlite-views`, async (context) => {
|
|
1180
|
+
if (!(filesystem instanceof SqliteReadModelFileSystem))
|
|
1181
|
+
return headOrJson(context, { error: "SQLite read model is not active" }, 409);
|
|
1182
|
+
const view = context.req.query("view");
|
|
1183
|
+
if (view === undefined || !(view in sqliteViewMetadata))
|
|
1184
|
+
return headOrJson(context, { error: "Unsupported SQLite view" }, 400);
|
|
1185
|
+
const parsed = Number.parseInt(context.req.query("limit") ?? "", 10);
|
|
1186
|
+
const defaultLimit = view.includes("agent_invocations")
|
|
1187
|
+
? 100
|
|
1188
|
+
: view === "first_fold_top_tasks"
|
|
1189
|
+
? 5
|
|
1190
|
+
: 50;
|
|
1191
|
+
const limit = Math.min(200, Math.max(1, Number.isFinite(parsed) ? parsed : defaultLimit));
|
|
1192
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1193
|
+
const optional = (name: string) => {
|
|
1194
|
+
const value = context.req.query(name);
|
|
1195
|
+
return value === undefined ? {} : { [name]: value };
|
|
1196
|
+
};
|
|
1197
|
+
return headOrJson(
|
|
1198
|
+
context,
|
|
1199
|
+
filesystem.viewPage(view as keyof typeof sqliteViewMetadata, {
|
|
1200
|
+
from,
|
|
1201
|
+
to,
|
|
1202
|
+
...optional("query"),
|
|
1203
|
+
...optional("status"),
|
|
1204
|
+
...optional("risk"),
|
|
1205
|
+
...optional("provider"),
|
|
1206
|
+
...optional("model"),
|
|
1207
|
+
...optional("agent"),
|
|
1208
|
+
...optional("phase"),
|
|
1209
|
+
...optional("origin"),
|
|
1210
|
+
...optional("workType"),
|
|
1211
|
+
...optional("priority"),
|
|
1212
|
+
offset: Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0),
|
|
1213
|
+
limit,
|
|
1214
|
+
}),
|
|
1215
|
+
200,
|
|
1216
|
+
true,
|
|
1217
|
+
);
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/work-priority`, async (context) => {
|
|
1221
|
+
const layout = await requireV2Layout(canonicalFilesystem);
|
|
1222
|
+
const operations = await buildOperationsProjection(canonicalFilesystem, layout, {
|
|
1223
|
+
from: null,
|
|
1224
|
+
to: null,
|
|
1225
|
+
});
|
|
1226
|
+
return headOrJson(
|
|
1227
|
+
context,
|
|
1228
|
+
await readWorkPriority(canonicalFilesystem, knownWorkIds(operations)),
|
|
1229
|
+
);
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/operations/entities`, async (context) => {
|
|
1233
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1234
|
+
const limit = Math.min(
|
|
1235
|
+
500,
|
|
1236
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
|
|
1237
|
+
);
|
|
1238
|
+
const kind = context.req.query("kind");
|
|
1239
|
+
if (kind !== "changes" && kind !== "decisions")
|
|
1240
|
+
return headOrJson(context, { error: "Unsupported Operations entity page" }, 400);
|
|
1241
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1242
|
+
const pageFilters = operationsPageFilters(context);
|
|
1243
|
+
const payload = await readAggregate(
|
|
1244
|
+
`operations:entities:${kind}:${from ?? ""}:${to ?? ""}:${offset}:${limit}:${pageFilters.query}:${pageFilters.status}:${pageFilters.risk}`,
|
|
1245
|
+
async () => {
|
|
1246
|
+
const layout = await readLayout();
|
|
1247
|
+
const entities = await new LifecycleManifestStore(filesystem).readEntities(
|
|
1248
|
+
kind === "changes" ? "change" : "decision",
|
|
1249
|
+
);
|
|
1250
|
+
const entityRows =
|
|
1251
|
+
kind === "changes"
|
|
1252
|
+
? changeEntityPageRows(entities, layout, from, to)
|
|
1253
|
+
: decisionEntityPageRows(entities, layout, from, to);
|
|
1254
|
+
const rows = filterLifecyclePageRows(entityRows, pageFilters);
|
|
1255
|
+
return {
|
|
1256
|
+
data_source: "lifecycle_manifest_shards",
|
|
1257
|
+
kind,
|
|
1258
|
+
offset,
|
|
1259
|
+
limit,
|
|
1260
|
+
total: rows.length,
|
|
1261
|
+
rows: rows.slice(offset, offset + limit),
|
|
1262
|
+
};
|
|
1263
|
+
},
|
|
1264
|
+
);
|
|
1265
|
+
return headOrJson(context, payload, 200, true);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
registerDailyReadModelRoute(app, filesystem, readAggregate, headOrJson);
|
|
1269
|
+
app.put(`${API_PREFIX}/work-priority`, async (context) => {
|
|
1270
|
+
const candidate = normalizeWorkPriority(await context.req.json());
|
|
1271
|
+
// Priority is canonical queue configuration, never snapshot content.
|
|
1272
|
+
// Keep every other settings mutation blocked in snapshot-serving mode.
|
|
1273
|
+
await canonicalFilesystem.writeTextAtomic(
|
|
1274
|
+
WORK_PRIORITY_PATH,
|
|
1275
|
+
`${JSON.stringify(candidate, null, 2)}\n`,
|
|
1276
|
+
);
|
|
1277
|
+
clearSnapshotCache();
|
|
1278
|
+
return context.json(candidate);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
443
1281
|
app.on(["GET", "HEAD"], `${API_PREFIX}/snapshot`, async (context) => {
|
|
444
1282
|
try {
|
|
445
1283
|
return headOrJson(context, await readSnapshot(context));
|
|
@@ -455,10 +1293,14 @@ export function createDashboardApp(
|
|
|
455
1293
|
|
|
456
1294
|
app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
|
|
457
1295
|
try {
|
|
458
|
-
await
|
|
459
|
-
|
|
1296
|
+
await readLayout();
|
|
1297
|
+
// The served snapshot is immutable by design. Reconciliation must use
|
|
1298
|
+
// canonical records, then atomically rebuild and reopen the snapshot
|
|
1299
|
+
// before the response exposes fresh projections.
|
|
1300
|
+
const reconciledIds = await reconcileOpen(canonicalFilesystem, workspaceRoot);
|
|
1301
|
+
await rebuildProjection();
|
|
460
1302
|
return context.json({
|
|
461
|
-
...(await
|
|
1303
|
+
...(await readSnapshot(context)),
|
|
462
1304
|
reconciliation: reconciledIds,
|
|
463
1305
|
});
|
|
464
1306
|
} catch (error) {
|
|
@@ -467,9 +1309,87 @@ export function createDashboardApp(
|
|
|
467
1309
|
}
|
|
468
1310
|
});
|
|
469
1311
|
|
|
1312
|
+
app.post(`${API_PREFIX}/refresh-projection`, async (context) => {
|
|
1313
|
+
try {
|
|
1314
|
+
await readLayout();
|
|
1315
|
+
await rebuildProjection();
|
|
1316
|
+
return context.json(await readSnapshot(context));
|
|
1317
|
+
} catch (error) {
|
|
1318
|
+
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
1319
|
+
return context.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
470
1323
|
app.on(["GET", "HEAD"], `${API_PREFIX}/cost-monitor`, async (context) => {
|
|
471
1324
|
try {
|
|
472
|
-
const
|
|
1325
|
+
const scope = context.req.query("scope") ?? "summary";
|
|
1326
|
+
if (scope === "summary") {
|
|
1327
|
+
const layout = await readLayout();
|
|
1328
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1329
|
+
const currency = selectedCurrency(context);
|
|
1330
|
+
const aggregateStartedAt = performance.now();
|
|
1331
|
+
const [source, rate] = await Promise.all([
|
|
1332
|
+
firstFoldCostSource(filesystem, {
|
|
1333
|
+
from,
|
|
1334
|
+
to,
|
|
1335
|
+
providers: queryValues(context, "provider"),
|
|
1336
|
+
models: queryValues(context, "model"),
|
|
1337
|
+
agents: queryValues(context, "agent"),
|
|
1338
|
+
phases: queryValues(context, "phase"),
|
|
1339
|
+
statuses: queryValues(context, "status"),
|
|
1340
|
+
tasks: queryValues(context, "task"),
|
|
1341
|
+
specs: queryValues(context, "spec"),
|
|
1342
|
+
prds: queryValues(context, "prd"),
|
|
1343
|
+
executions: queryValues(context, "execution"),
|
|
1344
|
+
changes: queryValues(context, "change"),
|
|
1345
|
+
}),
|
|
1346
|
+
currency === "BRL" ? readCurrentFxRate(filesystem) : Promise.resolve(null),
|
|
1347
|
+
]);
|
|
1348
|
+
// First fold receives only aggregate facts. Detailed Runs and entity
|
|
1349
|
+
// arrays remain on their explicit paginated endpoints.
|
|
1350
|
+
const aggregateMs = performance.now() - aggregateStartedAt;
|
|
1351
|
+
const costOperations = {
|
|
1352
|
+
schema_version: 1,
|
|
1353
|
+
layout_version: layout.version,
|
|
1354
|
+
readonly: true,
|
|
1355
|
+
source: ".agents/records",
|
|
1356
|
+
filters: { from, to },
|
|
1357
|
+
summary: { task_count: source.task_count ?? 0 },
|
|
1358
|
+
prds: [],
|
|
1359
|
+
specs: [],
|
|
1360
|
+
runs: [],
|
|
1361
|
+
} as unknown as OperationsProjection;
|
|
1362
|
+
const projectionStartedAt = performance.now();
|
|
1363
|
+
const payload = buildCostSummaryProjection(
|
|
1364
|
+
source,
|
|
1365
|
+
costOperations,
|
|
1366
|
+
currency,
|
|
1367
|
+
rate?.rate ?? null,
|
|
1368
|
+
);
|
|
1369
|
+
context.header(
|
|
1370
|
+
"Server-Timing",
|
|
1371
|
+
`cost_sql;dur=${aggregateMs.toFixed(1)},cost_projection;dur=${(performance.now() - projectionStartedAt).toFixed(1)}`,
|
|
1372
|
+
);
|
|
1373
|
+
return headOrJson(context, payload, 200, true);
|
|
1374
|
+
}
|
|
1375
|
+
if (scope === "runs") {
|
|
1376
|
+
const layout = await readLayout();
|
|
1377
|
+
const { from, to } = projectionPeriodFilter(context);
|
|
1378
|
+
const offset = Math.max(0, Number.parseInt(context.req.query("offset") ?? "0", 10) || 0);
|
|
1379
|
+
const limit = Math.min(
|
|
1380
|
+
500,
|
|
1381
|
+
Math.max(1, Number.parseInt(context.req.query("limit") ?? "100", 10) || 100),
|
|
1382
|
+
);
|
|
1383
|
+
return headOrJson(
|
|
1384
|
+
context,
|
|
1385
|
+
await buildManifestCostRunPage(filesystem, layout, context, { from, to }, offset, limit),
|
|
1386
|
+
200,
|
|
1387
|
+
true,
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
if (isUnfilteredCostRequest(context))
|
|
1391
|
+
return headOrJson(context, (await readSnapshot(context)).costs);
|
|
1392
|
+
const layout = await readLayout();
|
|
473
1393
|
const { from, to } = projectionPeriodFilter(context);
|
|
474
1394
|
// Cost and Operations must start from the same manifest-corrected Run
|
|
475
1395
|
// population; financial availability is represented on each row, never
|
|
@@ -507,6 +1427,7 @@ export function createDashboardApp(
|
|
|
507
1427
|
".agents/config/pricing/models.json",
|
|
508
1428
|
`${JSON.stringify(catalog, null, 2)}\n`,
|
|
509
1429
|
);
|
|
1430
|
+
clearSnapshotCache();
|
|
510
1431
|
return context.json(catalog);
|
|
511
1432
|
});
|
|
512
1433
|
|
|
@@ -518,6 +1439,7 @@ export function createDashboardApp(
|
|
|
518
1439
|
app.put(`${API_PREFIX}/fx-rate`, async (context) => {
|
|
519
1440
|
const input = fxRateSchema.pick({ rate: true }).parse(await context.req.json());
|
|
520
1441
|
const rate = await new PricingRepository(filesystem).writeCurrentFxRate(input.rate);
|
|
1442
|
+
clearSnapshotCache();
|
|
521
1443
|
return context.json(rate);
|
|
522
1444
|
});
|
|
523
1445
|
|
|
@@ -553,9 +1475,13 @@ export function createDashboardApp(
|
|
|
553
1475
|
app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
|
|
554
1476
|
const update = async () => {
|
|
555
1477
|
try {
|
|
556
|
-
|
|
557
|
-
|
|
1478
|
+
const persisted = await persistAgentCatalogUpdate(
|
|
1479
|
+
filesystem,
|
|
1480
|
+
await context.req.json(),
|
|
1481
|
+
syncAgentAdapters,
|
|
558
1482
|
);
|
|
1483
|
+
clearSnapshotCache();
|
|
1484
|
+
return context.json(persisted);
|
|
559
1485
|
} catch (error) {
|
|
560
1486
|
if (error instanceof AgentCatalogUpdateError) {
|
|
561
1487
|
return context.json({ error: error.message }, error.status);
|
|
@@ -573,7 +1499,7 @@ export function createDashboardApp(
|
|
|
573
1499
|
|
|
574
1500
|
app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings`, async (context) => {
|
|
575
1501
|
try {
|
|
576
|
-
return headOrJson(context, await readProjectSettings(
|
|
1502
|
+
return headOrJson(context, await readProjectSettings(canonicalFilesystem));
|
|
577
1503
|
} catch (error) {
|
|
578
1504
|
return headOrJson(
|
|
579
1505
|
context,
|
|
@@ -583,9 +1509,15 @@ export function createDashboardApp(
|
|
|
583
1509
|
}
|
|
584
1510
|
});
|
|
585
1511
|
|
|
1512
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/project-settings/overview`, async (context) => {
|
|
1513
|
+
return headOrJson(context, await readProjectOverview(canonicalFilesystem));
|
|
1514
|
+
});
|
|
1515
|
+
|
|
586
1516
|
app.put(`${API_PREFIX}/project-settings`, async (context) => {
|
|
587
1517
|
try {
|
|
588
|
-
|
|
1518
|
+
const persisted = await persistProjectSettings(canonicalFilesystem, await context.req.json());
|
|
1519
|
+
clearSnapshotCache();
|
|
1520
|
+
return context.json(persisted);
|
|
589
1521
|
} catch (error) {
|
|
590
1522
|
return context.json(
|
|
591
1523
|
{ error: error instanceof Error ? error.message : "Project settings update failed" },
|
|
@@ -596,7 +1528,7 @@ export function createDashboardApp(
|
|
|
596
1528
|
|
|
597
1529
|
app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id`, async (context) => {
|
|
598
1530
|
try {
|
|
599
|
-
const layout = await
|
|
1531
|
+
const layout = await readLayout();
|
|
600
1532
|
return headOrJson(context, await readTaskDetail(filesystem, layout, context.req.param("id")));
|
|
601
1533
|
} catch (error) {
|
|
602
1534
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -606,7 +1538,7 @@ export function createDashboardApp(
|
|
|
606
1538
|
|
|
607
1539
|
app.on(["GET", "HEAD"], `${API_PREFIX}/prds/:id`, async (context) => {
|
|
608
1540
|
try {
|
|
609
|
-
const layout = await
|
|
1541
|
+
const layout = await readLayout();
|
|
610
1542
|
return headOrJson(context, await readPrdDetail(filesystem, layout, context.req.param("id")));
|
|
611
1543
|
} catch (error) {
|
|
612
1544
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -616,7 +1548,7 @@ export function createDashboardApp(
|
|
|
616
1548
|
|
|
617
1549
|
app.on(["GET", "HEAD"], `${API_PREFIX}/specs/:id`, async (context) => {
|
|
618
1550
|
try {
|
|
619
|
-
const layout = await
|
|
1551
|
+
const layout = await readLayout();
|
|
620
1552
|
return headOrJson(context, await readSpecDetail(filesystem, layout, context.req.param("id")));
|
|
621
1553
|
} catch (error) {
|
|
622
1554
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
@@ -626,8 +1558,14 @@ export function createDashboardApp(
|
|
|
626
1558
|
|
|
627
1559
|
app.on(["GET", "HEAD"], `${API_PREFIX}/tasks/:id/trace`, async (context) => {
|
|
628
1560
|
try {
|
|
629
|
-
const layout = await
|
|
630
|
-
|
|
1561
|
+
const layout = await readLayout();
|
|
1562
|
+
const taskId = context.req.param("id");
|
|
1563
|
+
return headOrJson(
|
|
1564
|
+
context,
|
|
1565
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1566
|
+
? buildSqliteTaskTrace(filesystem, layout, taskId)
|
|
1567
|
+
: await buildTaskTrace(filesystem, layout, taskId),
|
|
1568
|
+
);
|
|
631
1569
|
} catch (error) {
|
|
632
1570
|
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
633
1571
|
return headOrJson(context, { error: "Task trace not found" }, 404);
|
|
@@ -657,57 +1595,28 @@ export function createDashboardApp(
|
|
|
657
1595
|
return app;
|
|
658
1596
|
}
|
|
659
1597
|
|
|
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
1598
|
async function dashboardSnapshot(
|
|
703
1599
|
filesystem: WorkspaceFileSystem,
|
|
704
1600
|
workspaceRoot: string,
|
|
705
1601
|
context: Context,
|
|
1602
|
+
cachedLayout?: WorkspaceLayout,
|
|
706
1603
|
) {
|
|
707
|
-
const layout = await requireV2Layout(filesystem);
|
|
1604
|
+
const layout = cachedLayout ?? (await requireV2Layout(filesystem));
|
|
708
1605
|
const { from, to } = projectionPeriodFilter(context);
|
|
709
|
-
const
|
|
710
|
-
const
|
|
1606
|
+
const currency = selectedCurrency(context);
|
|
1607
|
+
const operations =
|
|
1608
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1609
|
+
? await buildFirstFoldQuickProjection(filesystem, layout, { from, to })
|
|
1610
|
+
: await buildCanonicalOperationsProjection(filesystem, layout, { from, to });
|
|
1611
|
+
const costs =
|
|
1612
|
+
filesystem instanceof SqliteReadModelFileSystem
|
|
1613
|
+
? buildCostSummaryProjection(
|
|
1614
|
+
await firstFoldCostSource(filesystem, { from, to }),
|
|
1615
|
+
operations,
|
|
1616
|
+
currency,
|
|
1617
|
+
currency === "BRL" ? ((await readCurrentFxRate(filesystem))?.rate ?? null) : null,
|
|
1618
|
+
)
|
|
1619
|
+
: await buildCurrentCostProjection(filesystem, operations, currency);
|
|
711
1620
|
return {
|
|
712
1621
|
operations,
|
|
713
1622
|
costs,
|
|
@@ -719,81 +1628,75 @@ async function dashboardSnapshot(
|
|
|
719
1628
|
};
|
|
720
1629
|
}
|
|
721
1630
|
|
|
1631
|
+
const moduleStartedAt = performance.now();
|
|
1632
|
+
const sessionToken = defaultSessionToken();
|
|
1633
|
+
const runtimeWorkspaceRoot = process.env.ZAIP_WORKSPACE ?? process.cwd();
|
|
1634
|
+
|
|
1635
|
+
let defaultApp: Hono | undefined;
|
|
1636
|
+
|
|
722
1637
|
/**
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
1638
|
+
* Compatibility export for direct module consumers. The executable must not
|
|
1639
|
+
* construct this filesystem-backed app: `main` creates the SQLite app after
|
|
1640
|
+
* its private read model is ready. Construct on first consumer access instead.
|
|
726
1641
|
*/
|
|
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
|
-
});
|
|
1642
|
+
export function defaultDashboardApp(): Hono {
|
|
1643
|
+
defaultApp ??= createDashboardApp(runtimeWorkspaceRoot, sessionToken);
|
|
1644
|
+
return defaultApp;
|
|
745
1645
|
}
|
|
746
1646
|
|
|
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
|
-
}
|
|
1647
|
+
export const app = new Proxy({} as Hono, {
|
|
1648
|
+
get(_target, property) {
|
|
1649
|
+
const value = Reflect.get(defaultDashboardApp(), property);
|
|
1650
|
+
return typeof value === "function" ? value.bind(defaultDashboardApp()) : value;
|
|
1651
|
+
},
|
|
1652
|
+
});
|
|
760
1653
|
|
|
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));
|
|
1654
|
+
function startupPhase(phase: string): void {
|
|
1655
|
+
process.stderr.write(
|
|
1656
|
+
`${JSON.stringify({
|
|
1657
|
+
event: "dashboard_sqlite_startup_phase",
|
|
1658
|
+
phase,
|
|
1659
|
+
elapsed_ms: Math.round(performance.now() - moduleStartedAt),
|
|
1660
|
+
})}\n`,
|
|
1661
|
+
);
|
|
783
1662
|
}
|
|
784
1663
|
|
|
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) {
|
|
1664
|
+
async function main(): Promise<void> {
|
|
793
1665
|
const port = Number.parseInt(process.env.ZAIP_SERVER_PORT ?? "4317", 10);
|
|
794
|
-
|
|
1666
|
+
startupPhase("main_entered");
|
|
1667
|
+
const startedAt = performance.now();
|
|
1668
|
+
const readModel = await rebuildSqliteReadModel(runtimeWorkspaceRoot);
|
|
1669
|
+
// The parent launcher captures stderr even when stdout is intentionally
|
|
1670
|
+
// hidden. This makes seed hit/miss and private-copy cost observable without
|
|
1671
|
+
// exposing a shared handle or workspace payload.
|
|
1672
|
+
process.stderr.write(
|
|
1673
|
+
`${JSON.stringify({
|
|
1674
|
+
event: "dashboard_sqlite_read_model",
|
|
1675
|
+
elapsed_ms: Math.round(performance.now() - startedAt),
|
|
1676
|
+
cache: readModel.cache,
|
|
1677
|
+
fingerprint_ms: readModel.fingerprint_ms,
|
|
1678
|
+
materialize_ms: readModel.materialize_ms,
|
|
1679
|
+
source_files: readModel.files,
|
|
1680
|
+
})}\n`,
|
|
1681
|
+
);
|
|
1682
|
+
startupPhase("read_model_ready");
|
|
1683
|
+
const runtimeApp = createDashboardApp(runtimeWorkspaceRoot, sessionToken, undefined, {
|
|
1684
|
+
filesystem: new SqliteReadModelFileSystem(runtimeWorkspaceRoot),
|
|
1685
|
+
snapshotState: readModel.cache === "stale_seed" ? "updating" : "current",
|
|
1686
|
+
});
|
|
1687
|
+
startupPhase("runtime_app_ready");
|
|
1688
|
+
serve({ fetch: runtimeApp.fetch, hostname: HOST, port }, ({ port: boundPort }) => {
|
|
1689
|
+
startupPhase("listening");
|
|
795
1690
|
process.stdout.write(
|
|
796
1691
|
`ZAIP-AIOS dashboard API: http://${HOST}:${boundPort}${API_PREFIX}/health\n`,
|
|
797
1692
|
);
|
|
798
1693
|
});
|
|
799
1694
|
}
|
|
1695
|
+
|
|
1696
|
+
if (
|
|
1697
|
+
process.argv[1]?.endsWith("server.cjs") ||
|
|
1698
|
+
process.argv[1]?.endsWith("server.js") ||
|
|
1699
|
+
process.argv[1]?.endsWith("server.mjs")
|
|
1700
|
+
) {
|
|
1701
|
+
void main();
|
|
1702
|
+
}
|