@zaip-ai/cli 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/README.md +6 -4
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.d.ts.map +1 -1
  4. package/dist/services/adapters.js +15 -5
  5. package/dist/services/adapters.js.map +1 -1
  6. package/dist/services/adapters.test.js +1 -1
  7. package/dist/services/agent-adapters.d.ts +9 -0
  8. package/dist/services/agent-adapters.d.ts.map +1 -1
  9. package/dist/services/agent-adapters.js +38 -3
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +8 -1
  12. package/dist/services/agent-adapters.test.js.map +1 -1
  13. package/dist/services/codex-config-template.d.ts +2 -1
  14. package/dist/services/codex-config-template.d.ts.map +1 -1
  15. package/dist/services/codex-config-template.js +13 -2
  16. package/dist/services/codex-config-template.js.map +1 -1
  17. package/dist/services/codex-rules.d.ts +1 -1
  18. package/dist/services/codex-rules.d.ts.map +1 -1
  19. package/dist/services/codex-rules.js +31 -7
  20. package/dist/services/codex-rules.js.map +1 -1
  21. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  22. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  23. package/dist/services/codex-subagent-hook-template.js +4 -1
  24. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  25. package/dist/services/doctor.d.ts.map +1 -1
  26. package/dist/services/doctor.js +3 -1
  27. package/dist/services/doctor.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts +6 -3
  29. package/dist/services/global-codex-config.d.ts.map +1 -1
  30. package/dist/services/global-codex-config.js +82 -13
  31. package/dist/services/global-codex-config.js.map +1 -1
  32. package/dist/services/global-codex-config.test.js +57 -10
  33. package/dist/services/global-codex-config.test.js.map +1 -1
  34. package/dist/services/install-agents-template.d.ts.map +1 -1
  35. package/dist/services/install-agents-template.js +15 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/logger.js +1 -1
  38. package/dist/services/logger.js.map +1 -1
  39. package/dist/services/logger.test.js +3 -3
  40. package/dist/services/logger.test.js.map +1 -1
  41. package/dist/services/runtime-template.test.js +21 -5
  42. package/dist/services/runtime-template.test.js.map +1 -1
  43. package/dist/services/template-manifest.d.ts.map +1 -1
  44. package/dist/services/template-manifest.js +2 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/config/agents/catalog.yaml +310 -10
  48. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  49. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  50. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  57. package/templates/.agents/config/project.yaml +1 -1
  58. package/templates/.agents/dashboard/.zaip-managed.json +659 -163
  59. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  60. package/templates/.agents/dashboard/README.md +48 -97
  61. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-Z60df2r-.js +1 -0
  62. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-Cv-JcoxP.js → DashboardHelpDialog-Cs6dzDmW.js} +294 -109
  63. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-Coc6pA-1.js +1 -0
  64. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +1 -0
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +3 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +1 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +68 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-DFBuc_qp.js +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +2 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-CCQOHdVI.js +9 -0
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-pC2_0J8x.js +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  79. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  80. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +952 -595
  81. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  82. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  83. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  84. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  85. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  89. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +257 -313
  91. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +149 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +35 -0
  93. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +111 -12
  94. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  95. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  98. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +70 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +111 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +432 -13
  101. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  102. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  111. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +24 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  114. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +471 -2
  115. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  116. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +196 -39
  118. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  119. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  120. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  121. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  122. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +187 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +190 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +121 -0
  125. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +398 -0
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +37 -0
  130. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +55 -5
  131. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  132. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  133. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  134. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +64 -0
  135. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +275 -0
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +100 -15
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +131 -40
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +303 -0
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +226 -6
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +479 -402
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +312 -72
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +373 -321
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +52 -14
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +32 -5
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +108 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +60 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +106 -0
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -137
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +956 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +775 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +95 -0
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  177. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  178. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +182 -0
  179. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  180. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  181. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  182. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  183. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +325 -18
  184. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  185. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  186. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  187. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  188. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  189. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +365 -14
  190. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +532 -131
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +314 -358
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +68 -0
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +51 -8
  195. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  196. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  197. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  198. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  199. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +99 -42
  200. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  201. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +255 -34
  202. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +163 -8
  203. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +101 -68
  204. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  205. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -16
  206. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  207. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +50 -1
  208. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +143 -9
  209. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +29 -4
  210. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  211. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  212. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  213. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  214. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  215. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +65 -5
  217. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +19 -0
  219. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  220. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  221. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +136 -0
  222. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  223. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +24 -7
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +165 -6
  232. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  233. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  234. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  235. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  238. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +5 -3
  239. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +4 -83
  240. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  241. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  242. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  243. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  244. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  245. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  246. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  247. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +27 -2
  248. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +22 -13
  249. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  250. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  251. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  252. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  253. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  255. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +324 -0
  256. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  257. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  258. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  259. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  260. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  261. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  262. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  263. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  264. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  265. package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
  266. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  267. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1756 -0
  268. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1608 -0
  269. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  270. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +12 -0
  271. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +83 -0
  272. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +28 -0
  273. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +262 -0
  274. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +41 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +105 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1643 -107
  277. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  278. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  279. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  281. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +335 -4
  282. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  283. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  284. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +71 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +179 -0
  286. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  288. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  290. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  291. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  292. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +163 -84
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +145 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +551 -266
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1145 -308
  302. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +708 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +2232 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +6151 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  307. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  308. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +437 -190
  310. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +299 -24
  311. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  312. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  313. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  314. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  315. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  316. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  317. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  318. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  319. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +214 -41
  320. package/templates/.agents/dashboard/work-priority.json +34 -0
  321. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  322. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  323. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  324. package/templates/.agents/design/.zaip-managed.json +3 -3
  325. package/templates/.agents/design/apps/catalog/package.json +1 -1
  326. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  327. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  328. package/templates/.agents/requirements/comparative-audit.md +1 -1
  329. package/templates/.agents/requirements/context-engine.md +1 -1
  330. package/templates/.agents/requirements/cost-observability.md +1 -1
  331. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  332. package/templates/.agents/requirements/data-export.md +1 -1
  333. package/templates/.agents/requirements/design-system.md +1 -1
  334. package/templates/.agents/requirements/execution-engine.md +1 -1
  335. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  336. package/templates/.agents/requirements/model-orchestration.md +1 -1
  337. package/templates/.agents/requirements/public-distribution.md +9 -6
  338. package/templates/.agents/requirements/software-architecture.md +1 -1
  339. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  340. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  341. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  342. package/templates/.agents/rules/autonomy-decision.md +15 -0
  343. package/templates/.agents/rules/core.md +1 -1
  344. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  345. package/templates/.agents/rules/specialist-routing.md +19 -0
  346. package/templates/.agents/rules/workflow.md +1 -1
  347. package/templates/.agents/runtime/VERSION +1 -1
  348. package/templates/.agents/runtime/bin/zaip.mjs +114 -64
  349. package/templates/.agents/runtime/checksums.sha256 +6 -4
  350. package/templates/.agents/runtime/manifest.json +3 -1
  351. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  352. package/templates/.agents/runtime/provenance.json +11 -0
  353. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  354. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  355. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +15 -0
  356. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  357. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  358. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  359. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  360. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  361. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  362. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  363. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  364. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  365. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  366. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  367. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  368. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  369. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  370. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  371. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  372. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  373. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  374. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +15 -0
  375. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  377. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  378. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +3 -3
  380. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +76 -27
  381. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +128 -24
  382. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +122 -4
  383. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +66 -19
  384. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +193 -7
  385. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +14 -0
  386. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  387. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  388. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  389. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  390. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +18 -0
  392. package/templates/.agents/zaip-aios/INDEX.md +1 -0
  393. package/templates/.agents/zaip-aios/README.md +37 -8
  394. package/templates/.agents/zaip-aios/VERSION +1 -1
  395. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  396. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  397. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  398. package/templates/template-manifest.json +945 -214
  399. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  400. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  401. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  402. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  403. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  404. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  405. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  406. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  407. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  408. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  409. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  410. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
- import { appendFileSync, mkdirSync } from "node:fs";
3
+ import { appendFileSync, closeSync, mkdirSync, openSync } from "node:fs";
4
4
  import {
5
5
  access,
6
6
  mkdir,
@@ -15,35 +15,57 @@ import {
15
15
  import { createServer as createHttpServer } from "node:http";
16
16
  import { createServer } from "node:net";
17
17
  import { tmpdir } from "node:os";
18
- import { dirname, join, resolve, win32 } from "node:path";
18
+ import { dirname, join, relative, resolve, win32 } from "node:path";
19
19
  import process from "node:process";
20
20
  import { fileURLToPath } from "node:url";
21
+ import { collectPreviousDashboardSessions } from "./start-dashboard-history.mjs";
22
+ import {
23
+ stopOwnedDashboardProcess,
24
+ stopProcessTree,
25
+ teardownWarning,
26
+ } from "./start-dashboard-processes.mjs";
21
27
 
28
+ export {
29
+ isAncestorProcess,
30
+ isProcessAlive,
31
+ registeredListenerMatches,
32
+ stopOwnedDashboardProcess,
33
+ teardownWarning,
34
+ } from "./start-dashboard-processes.mjs";
22
35
  export const host = "127.0.0.1";
23
- export const defaultPort = 4317;
36
+ export const defaultPort = 4417;
24
37
  export const maxPortAttempts = 25;
25
- export const runtimeKind = "zaip-dashboard-local-runtime";
26
-
38
+ export const runtimeKind = "zaip-dashboard-sqlite-runtime";
27
39
  const packageRoot = dirname(fileURLToPath(import.meta.url));
28
40
  export const workspaceRoot = resolve(packageRoot, "../../../..");
29
- export const workspaceKey = createHash("sha256").update(workspaceRoot).digest("hex").slice(0, 16);
30
- export const sessionDirectory = join(tmpdir(), "zaip-aios", workspaceKey);
31
- export const sessionPath = join(sessionDirectory, "dashboard-session.json");
32
- export const portRegistryPath = join(sessionDirectory, "dashboard-ports.json");
33
- export const lockPath = join(sessionDirectory, "dashboard-launcher.lock");
34
- export const launcherLogPath = join(sessionDirectory, "dashboard-launcher.log");
35
-
41
+ export const workspaceKey = createHash("sha256").update(`${workspaceRoot}:dashboard-sqlite`).digest("hex").slice(0, 16);
42
+ export const sessionDirectory = join(tmpdir(), "zaip-aios", `dashboard-sqlite-${workspaceKey}`);
43
+ export const sessionPath = join(sessionDirectory, "dashboard-sqlite-session.json");
44
+ export const portRegistryPath = join(sessionDirectory, "dashboard-sqlite-ports.json");
45
+ export const lockPath = join(sessionDirectory, "dashboard-sqlite-launcher.lock");
46
+ export const launcherLogPath = join(sessionDirectory, "dashboard-sqlite-launcher.log");
36
47
  const frontendRoot = resolve(workspaceRoot, ".agents/dashboard/apps/frontend/dist");
48
+ const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "dashboard-sqlite");
49
+ // Keep the immutable seed cache in one durable workspace location. Runtime
50
+ // generations and the isolated background refresher must never derive a
51
+ // different cache directory from their temporary SQLite path.
52
+ const sqliteSnapshotCacheDirectory = join(sqliteReadModelDirectory, "cache");
53
+ // Node's bytecode cache is disposable runtime acceleration, never dashboard
54
+ // data. Keep it beside the private SQLite generations so fresh-port semantics
55
+ // and canonical sources remain unchanged.
56
+ const nodeCompileCacheDirectory = join(sqliteReadModelDirectory, "node-compile-cache");
37
57
  const installedServer = resolve(packageRoot, "server.mjs");
38
- const sourceServer = resolve(packageRoot, "dist/server.js");
58
+ const sourceServer = resolve(packageRoot, "dist/server.cjs");
39
59
  const healthPath = "/api/v2/health";
40
- // A fresh dashboard build can legitimately take longer than the old ten-second
41
- // contention window. A second executable click waits for the first handoff
42
- // and then coalesces with it instead of surfacing a spurious lock failure.
60
+ // Fresh-generation probe marker: changes the immutable source fingerprint only.
61
+ // Keep the preparation lock through a full build so concurrent clicks coalesce safely.
43
62
  const lockTimeoutMs = 120_000;
44
63
  const staleLockMs = 30_000;
45
64
  const lockHeartbeatFloorMs = 10;
46
- const startupTimeoutMs = 12_000;
65
+ // A cold immutable seed can legitimately take ~176 s on a busy Windows
66
+ // volume. Keep the immediate preparation page and fresh-port handoff, but do
67
+ // not kill that one-time canonical build before it can populate the cache.
68
+ const startupTimeoutMs = 180_000;
47
69
  const maxServerStartAttempts = 4;
48
70
  const maxPortHistory = 50;
49
71
  const dashboardSourceRoots = [
@@ -52,20 +74,16 @@ const dashboardSourceRoots = [
52
74
  resolve(workspaceRoot, ".agents/dashboard/packages/ui/src"),
53
75
  resolve(packageRoot, "src"),
54
76
  ];
55
-
56
77
  export function preferredPort(value = process.env.ZAIP_SERVER_PORT) {
57
78
  const parsed = Number.parseInt(value ?? "", 10);
58
79
  return Number.isInteger(parsed) && parsed > 0 && parsed <= 65_535 ? parsed : defaultPort;
59
80
  }
60
-
61
81
  export function dashboardHealthUrl(port) {
62
82
  return `http://${host}:${port}${healthPath}`;
63
83
  }
64
-
65
- export function dashboardUrl(port, token) {
66
- return `http://${host}:${port}/?token=${encodeURIComponent(token)}`;
84
+ export function dashboardUrl(port, token, snapshotUpdating = false) {
85
+ return `http://${host}:${port}/?token=${encodeURIComponent(token)}${snapshotUpdating ? "&snapshot=updating" : ""}`;
67
86
  }
68
-
69
87
  export function appendLauncherLog(message) {
70
88
  try {
71
89
  mkdirSync(sessionDirectory, { recursive: true });
@@ -78,7 +96,6 @@ export function appendLauncherLog(message) {
78
96
  // Best-effort diagnostics must not hide the launcher failure.
79
97
  }
80
98
  }
81
-
82
99
  export function stripTerminalControlCodes(value) {
83
100
  const escapeCode = String.fromCharCode(27);
84
101
  const bell = String.fromCharCode(7);
@@ -93,16 +110,37 @@ export function stripTerminalControlCodes(value) {
93
110
  .replace(/\r/g, "")
94
111
  .trim();
95
112
  }
96
-
97
113
  export function formatLauncherError(value, maximumLength = 3_600) {
98
114
  const message = stripTerminalControlCodes(value);
99
115
  if (message.length <= maximumLength) return message;
100
-
101
116
  const firstHalf = Math.floor(maximumLength / 2);
102
117
  const lastHalf = maximumLength - firstHalf;
103
118
  return `${message.slice(0, firstHalf)}\n\n… detalhes reduzidos; consulte o log temporário …\n\n${message.slice(-lastHalf)}`;
104
119
  }
105
-
120
+ export async function assertDashboardPrerequisites() {
121
+ const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
122
+ if (!Number.isInteger(major) || major < 22)
123
+ throw new Error(
124
+ `O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
125
+ );
126
+ try {
127
+ await import("node:sqlite");
128
+ } catch {
129
+ throw new Error(
130
+ "O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
131
+ );
132
+ }
133
+ for (const required of [
134
+ resolve(workspaceRoot, ".agents/records"),
135
+ resolve(workspaceRoot, ".agents/config"),
136
+ resolve(workspaceRoot, "node_modules/esbuild"),
137
+ ]) {
138
+ if (!(await exists(required)))
139
+ throw new Error(
140
+ `Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
141
+ );
142
+ }
143
+ }
106
144
  export async function readDashboardSession(path = sessionPath) {
107
145
  try {
108
146
  return JSON.parse(await readFile(path, "utf8"));
@@ -110,11 +148,9 @@ export async function readDashboardSession(path = sessionPath) {
110
148
  return null;
111
149
  }
112
150
  }
113
-
114
151
  export async function writeDashboardSession(session, path = sessionPath) {
115
152
  await writeJsonAtomic(path, session);
116
153
  }
117
-
118
154
  async function writeJsonAtomic(path, value) {
119
155
  const directory = dirname(path);
120
156
  await mkdir(directory, { recursive: true });
@@ -125,7 +161,6 @@ async function writeJsonAtomic(path, value) {
125
161
  });
126
162
  await rename(temporaryPath, path);
127
163
  }
128
-
129
164
  export async function readPortRegistry(path = portRegistryPath) {
130
165
  try {
131
166
  const registry = JSON.parse(await readFile(path, "utf8"));
@@ -141,7 +176,6 @@ export async function readPortRegistry(path = portRegistryPath) {
141
176
  return [];
142
177
  }
143
178
  }
144
-
145
179
  export async function recordPortStatus(session, status, path = portRegistryPath) {
146
180
  const entries = await readPortRegistry(path);
147
181
  const now = new Date().toISOString();
@@ -152,6 +186,7 @@ export async function recordPortStatus(session, status, path = portRegistryPath)
152
186
  port: session.port,
153
187
  rootPid: session.rootPid,
154
188
  serverPid: session.serverPid ?? null,
189
+ dbPath: typeof session.dbPath === "string" ? session.dbPath : null,
155
190
  openedAt: session.createdAt ?? session.openedAt ?? now,
156
191
  status,
157
192
  closedAt: status === "active" ? null : now,
@@ -166,12 +201,10 @@ export async function recordPortStatus(session, status, path = portRegistryPath)
166
201
  ports: entries.slice(-maxPortHistory),
167
202
  });
168
203
  }
169
-
170
204
  export async function clearOwnedDashboardSession(rootPid = process.pid, path = sessionPath) {
171
205
  const session = await readDashboardSession(path);
172
206
  if (session?.rootPid === rootPid) await rm(path, { force: true });
173
207
  }
174
-
175
208
  export async function probeDashboardPort(
176
209
  port,
177
210
  fetchImpl = fetch,
@@ -189,13 +222,13 @@ export async function probeDashboardPort(
189
222
  return false;
190
223
  }
191
224
  }
192
-
193
225
  export async function findReusableDashboardSession({
194
226
  path = sessionPath,
195
227
  startPort = preferredPort(),
196
228
  fetchImpl = fetch,
197
229
  expectedWorkspaceKey = workspaceKey,
198
230
  expectedBuildSignature = null,
231
+ expectedSnapshotState = "current",
199
232
  } = {}) {
200
233
  const session = await readDashboardSession(path);
201
234
  if (
@@ -204,7 +237,8 @@ export async function findReusableDashboardSession({
204
237
  session?.workspaceKey !== expectedWorkspaceKey ||
205
238
  typeof session?.token !== "string" ||
206
239
  session.token.length === 0 ||
207
- (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature)
240
+ (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature) ||
241
+ (expectedSnapshotState !== null && session?.snapshotState !== expectedSnapshotState)
208
242
  ) {
209
243
  return null;
210
244
  }
@@ -214,7 +248,6 @@ export async function findReusableDashboardSession({
214
248
  ) {
215
249
  return session;
216
250
  }
217
-
218
251
  const ports = Array.from({ length: maxPortAttempts }, (_, index) => startPort + index).filter(
219
252
  (port) => port <= 65_535 && port !== session.port,
220
253
  );
@@ -226,7 +259,6 @@ export async function findReusableDashboardSession({
226
259
  const port = matches.find((candidate) => candidate !== null);
227
260
  return port === undefined ? null : { ...session, port };
228
261
  }
229
-
230
262
  async function latestModifiedAt(paths) {
231
263
  let latest = 0;
232
264
  for (const path of paths) {
@@ -246,15 +278,14 @@ async function latestModifiedAt(paths) {
246
278
  }
247
279
  return latest;
248
280
  }
249
-
250
- async function runWorkspaceBuild(filter) {
251
- const command = process.platform === "win32" ? (process.env.ComSpec ?? "cmd.exe") : "corepack";
252
- const args =
253
- process.platform === "win32"
254
- ? ["/d", "/s", "/c", `corepack pnpm --filter ${filter} build`]
255
- : ["pnpm", "--filter", filter, "build"];
281
+ async function runSqliteBuild(target) {
282
+ const frontend = target === "frontend";
283
+ const command = process.execPath;
284
+ const args = frontend
285
+ ? [resolve(workspaceRoot, "node_modules/vite/bin/vite.js"), "build"]
286
+ : [resolve(workspaceRoot, ".agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs")];
256
287
  const output = await new Promise((resolveBuild, rejectBuild) => {
257
- const child = spawn(command, args, { cwd: workspaceRoot, windowsHide: true, stdio: "pipe" });
288
+ const child = spawn(command, args, { cwd: frontend ? resolve(workspaceRoot, ".agents/dashboard/apps/frontend") : packageRoot, windowsHide: true, stdio: "pipe" });
258
289
  let outputText = "";
259
290
  const appendOutput = (chunk) => {
260
291
  outputText = `${outputText}${chunk}`.slice(-8_000);
@@ -268,13 +299,12 @@ async function runWorkspaceBuild(filter) {
268
299
  if (code === 0) resolveBuild();
269
300
  else
270
301
  rejectBuild(
271
- new Error(`Build ${filter} failed (${code}): ${formatLauncherError(outputText)}`),
302
+ new Error(`SQLite dashboard ${target} build failed (${code}): ${formatLauncherError(outputText)}`),
272
303
  );
273
304
  });
274
305
  });
275
306
  return output;
276
307
  }
277
-
278
308
  async function ensureFreshDashboardBuild() {
279
309
  const artifacts = [resolve(frontendRoot, "index.html"), sourceServer];
280
310
  const [sourcesModifiedAt, artifactsModifiedAt] = await Promise.all([
@@ -283,8 +313,8 @@ async function ensureFreshDashboardBuild() {
283
313
  ]);
284
314
  const forceRebuild = process.env.ZAIP_DASHBOARD_FORCE_REBUILD === "1";
285
315
  if (forceRebuild || artifactsModifiedAt < sourcesModifiedAt) {
286
- await runWorkspaceBuild("@zaip-ai/dashboard-frontend");
287
- await runWorkspaceBuild("@zaip-ai/dashboard-local-runtime");
316
+ await runSqliteBuild("frontend");
317
+ await runSqliteBuild("runtime");
288
318
  }
289
319
  const artifactFingerprint = await Promise.all(
290
320
  [...artifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
@@ -294,7 +324,6 @@ async function ensureFreshDashboardBuild() {
294
324
  );
295
325
  return createHash("sha256").update(artifactFingerprint.join("|")).digest("hex");
296
326
  }
297
-
298
327
  export async function withLauncherLock(
299
328
  action,
300
329
  {
@@ -333,7 +362,6 @@ export async function withLauncherLock(
333
362
  }
334
363
  throw new Error("Timed out waiting for the ZAIP dashboard launcher lock");
335
364
  }
336
-
337
365
  export async function findAvailablePort(startPort = preferredPort()) {
338
366
  for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
339
367
  if (await isPortFree(port)) return port;
@@ -342,7 +370,6 @@ export async function findAvailablePort(startPort = preferredPort()) {
342
370
  `No free dashboard port found between ${startPort} and ${Math.min(startPort + maxPortAttempts - 1, 65_535)}`,
343
371
  );
344
372
  }
345
-
346
373
  export async function findAvailablePortWithFallback({
347
374
  preferred = preferredPort(),
348
375
  excludedPorts = [],
@@ -364,7 +391,6 @@ export async function findAvailablePortWithFallback({
364
391
  "No free dashboard port was found in the preferred, fallback, or ephemeral ranges",
365
392
  );
366
393
  }
367
-
368
394
  async function findEphemeralPort() {
369
395
  return new Promise((resolvePort, rejectPort) => {
370
396
  const server = createServer();
@@ -380,7 +406,6 @@ async function findEphemeralPort() {
380
406
  });
381
407
  });
382
408
  }
383
-
384
409
  export function browserLaunchCommands(
385
410
  target,
386
411
  platform = process.platform,
@@ -401,7 +426,6 @@ export function browserLaunchCommands(
401
426
  }
402
427
  return [[platform === "darwin" ? "open" : "xdg-open", [target]]];
403
428
  }
404
-
405
429
  export async function openBrowser(target, spawnImpl = spawn) {
406
430
  if (process.env.ZAIP_UI_NO_BROWSER === "1") return true;
407
431
  const commands = browserLaunchCommands(target);
@@ -431,9 +455,14 @@ export async function openBrowser(target, spawnImpl = spawn) {
431
455
  appendLauncherLog(`Could not dispatch the dashboard URL to the default browser: ${target}`);
432
456
  return false;
433
457
  }
434
-
435
458
  export async function startDashboardLaunchPage(port) {
436
459
  let redirectUrl = null;
460
+ let progress = {
461
+ percent: 4,
462
+ phase: "Iniciando o launcher local",
463
+ detail: "Abrindo a página de preparação.",
464
+ };
465
+ const connections = new Set();
437
466
  let resolveRedirectObserved;
438
467
  const redirectObserved = new Promise((resolveObserved) => {
439
468
  resolveRedirectObserved = resolveObserved;
@@ -446,6 +475,11 @@ export async function startDashboardLaunchPage(port) {
446
475
  response.end(JSON.stringify({ target: redirectUrl }));
447
476
  return;
448
477
  }
478
+ if (request.url?.startsWith("/api/v2/progress")) {
479
+ response.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
480
+ response.end(JSON.stringify(progress));
481
+ return;
482
+ }
449
483
  if (request.url?.startsWith(healthPath)) {
450
484
  response.writeHead(503, { "Content-Type": "application/json; charset=utf-8" });
451
485
  response.end(JSON.stringify({ kind: "zaip-dashboard-launcher", status: "starting" }));
@@ -460,40 +494,43 @@ export async function startDashboardLaunchPage(port) {
460
494
  <title>Dashboard Zaip AIOS</title>
461
495
  <style>
462
496
  :root{color-scheme:dark;font-family:Inter,system-ui,sans-serif;background:#010610;color:#f1f5f9}
463
- *{box-sizing:border-box}body{margin:0;min-height:100vh;background:radial-gradient(circle at 50% 0,rgba(0,176,240,.07),transparent 42%),#010610}
464
- main{padding:clamp(18px,2.4vw,38px)}header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}h1{font-size:clamp(1.45rem,2.4vw,2.25rem);margin:8px 0 0}
465
- .eyebrow,.line,.value,.action,.chart,.pill{display:block;position:relative;overflow:hidden;border-radius:999px;background:#172033}.eyebrow{width:110px;height:10px}.actions{display:flex;gap:10px}.action{width:48px;height:42px;border-radius:12px}
466
- .kpis,.panels,.board{display:grid;gap:14px}.kpis{grid-template-columns:repeat(4,minmax(0,1fr))}.kpis article,.panels article,.board article{border:1px solid rgba(255,255,255,.08);border-radius:16px;background:linear-gradient(145deg,rgba(17,24,39,.86),rgba(10,14,23,.92))}
467
- .kpis article{min-height:138px;padding:18px}.line{width:86%;height:10px;margin-top:12px}.value{width:42%;height:30px;margin-top:18px}.panels{grid-template-columns:1.7fr 1fr;margin-top:14px}.panels article{padding:18px}.chart{height:156px;margin-top:22px;border-radius:12px;background:repeating-linear-gradient(to bottom,rgba(51,65,85,.3) 0,rgba(51,65,85,.3) 1px,transparent 1px,transparent 38px)}
468
- .board{grid-template-columns:repeat(5,minmax(210px,1fr));margin-top:18px;overflow:hidden}.board article{min-height:220px;padding:14px}.board h2{min-height:34px;margin:0 0 12px;color:#cbd5e1;font-size:.82rem}.card{min-height:92px;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:10px;background:rgba(2,6,23,.56)}.pill{width:52px;height:15px;margin-top:14px}
469
- .eyebrow:after,.line:after,.value:after,.action:after,.chart:after,.pill:after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(125,211,252,.12),transparent);animation:shine 1.35s ease-in-out infinite;transform:translateX(-100%)}
470
- @keyframes shine{to{transform:translateX(100%)}}@media(max-width:860px){.kpis{grid-template-columns:repeat(2,1fr)}.panels{grid-template-columns:1fr}.board{overflow-x:auto}}
497
+ *{box-sizing:border-box}body{margin:0;background:#010610}.launch-overlay{position:fixed;inset:0;width:100vw;height:100vh;display:grid;place-items:center;padding:clamp(18px,5vw,72px);background:radial-gradient(circle at 50% 35%,rgba(0,176,240,.11),transparent 42%),#010610}
498
+ .progress{width:min(100%,760px);padding:clamp(24px,4vw,42px);border:1px solid rgba(125,211,252,.2);border-radius:20px;background:rgba(3,9,22,.94);box-shadow:0 24px 80px rgba(0,0,0,.45);backdrop-filter:blur(10px)}.progress strong,.progress span{display:block}.progress strong{font-size:clamp(1.05rem,2vw,1.35rem)}.progress span{color:#b6c6db;font-size:clamp(.88rem,1.5vw,1rem);margin-top:7px}.track{height:9px;margin-top:20px;border-radius:999px;background:#172033;overflow:hidden}.bar{height:100%;width:4%;border-radius:inherit;background:linear-gradient(90deg,#38bdf8,#818cf8);transition:width .35s ease}.handoff{display:none;margin-top:16px;color:#dbeafe}.handoff a{color:#7dd3fc;font-weight:700}
471
499
  </style>
472
500
  </head>
473
501
  <body>
474
- <main aria-label="Preparando dashboard">
475
- <header><div><span class="eyebrow"></span><h1>Dashboard Zaip AIOS</h1></div><div class="actions"><span class="action"></span><span class="action"></span><span class="action"></span></div></header>
476
- <section class="kpis">${Array.from({ length: 4 }, () => '<article><span class="line"></span><span class="value"></span><span class="line"></span></article>').join("")}</section>
477
- <section class="panels"><article><span class="line"></span><div class="chart"></div></article><article><span class="line"></span><div class="chart"></div></article></section>
478
- <section class="board">${["Definição", "Planejamento", "Pronto para Executar", "Execução", "Verificação"].map((label) => `<article><h2>${label}</h2><div class="card"><span class="line"></span><span class="line"></span><span class="pill"></span></div></article>`).join("")}</section>
502
+ <main class="launch-overlay" aria-label="Preparando dashboard">
503
+ <section class="progress" aria-live="polite" role="status"><strong id="phase">Iniciando o launcher local</strong><span id="detail">Abrindo a página de preparação.</span><div class="track"><div class="bar" id="bar"></div></div><span class="handoff" id="handoff">O dashboard está pronto. Se ele não abrir automaticamente, <a id="handoff-link" href="#">abra o dashboard agora</a>.</span></section>
479
504
  </main>
480
505
  <script>
506
+ let handoffTarget=null;
507
+ function showHandoff(target){handoffTarget=target;const box=document.getElementById("handoff");const link=document.getElementById("handoff-link");link.href=target;box.style.display="block";document.getElementById("phase").textContent="Dashboard pronto";document.getElementById("detail").textContent="Concluindo a abertura da tela principal."}
508
+ async function updateProgress(){try{const r=await fetch("/api/v2/progress",{cache:"no-store"});const p=await r.json();if(!handoffTarget){document.getElementById("phase").textContent=p.phase;document.getElementById("detail").textContent=p.detail};document.getElementById("bar").style.width=Math.max(4,Math.min(100,Number(p.percent)||4))+"%"}catch{}}
481
509
  async function waitForDashboard(){
482
510
  try{
511
+ await updateProgress();
483
512
  const ready=await fetch("/api/v2/ready",{cache:"no-store"});
484
513
  const next=await ready.json();
485
- if(next.target){location.replace(next.target);return}
514
+ if(next.target){showHandoff(next.target);setTimeout(()=>location.replace(next.target),0);return}
486
515
  const response=await fetch("${healthPath}",{cache:"no-store"});
487
516
  const payload=await response.json();
488
517
  if(response.ok&&payload.kind==="${runtimeKind}"){location.reload();return}
489
518
  }catch{}
490
519
  setTimeout(waitForDashboard,250)
491
520
  }
492
- setTimeout(waitForDashboard,100)
521
+ updateProgress();setTimeout(waitForDashboard,100)
522
+ // A browser extension or a failed navigation must never leave a silent
523
+ // skeleton. The visible link remains actionable while the temporary
524
+ // launcher server keeps serving this page through the handoff grace.
525
+ setTimeout(()=>{if(handoffTarget)showHandoff(handoffTarget)},4000)
493
526
  </script>
494
527
  </body>
495
528
  </html>`);
496
529
  });
530
+ server.on("connection", (connection) => {
531
+ connections.add(connection);
532
+ connection.once("close", () => connections.delete(connection));
533
+ });
497
534
  await new Promise((resolveListen, rejectListen) => {
498
535
  server.once("error", rejectListen);
499
536
  server.listen({ host, port }, resolveListen);
@@ -506,25 +543,52 @@ export async function startDashboardLaunchPage(port) {
506
543
  redirect: (target) => {
507
544
  redirectUrl = target;
508
545
  },
546
+ setProgress: (next) => {
547
+ progress = { ...progress, ...next, percent: Math.max(progress.percent, next.percent ?? progress.percent) };
548
+ },
509
549
  waitForRedirect: async (timeoutMs = 1_500) =>
510
550
  Promise.race([redirectObserved, delay(timeoutMs).then(() => false)]),
511
- close: () =>
551
+ closeAfterHandoffGrace: (graceMs = 12_000) => {
552
+ const timer = setTimeout(() => {
553
+ void server.close();
554
+ server.closeAllConnections?.();
555
+ for (const connection of connections) connection.destroy();
556
+ }, graceMs);
557
+ timer.unref?.();
558
+ },
559
+ close: ({ forceAfterMs = 750 } = {}) =>
512
560
  new Promise((resolveClose, rejectClose) => {
513
- server.close((error) => (error ? rejectClose(error) : resolveClose()));
561
+ let forceTimer = null;
562
+ const finish = (error) => {
563
+ if (forceTimer !== null) clearTimeout(forceTimer);
564
+ if (error) rejectClose(error);
565
+ else resolveClose();
566
+ };
567
+ server.close(finish);
568
+ // The preparation page polls while redirecting. A browser keep-alive
569
+ // or an interrupted polling request must not keep this transient
570
+ // server alive indefinitely after its handoff was issued.
571
+ forceTimer = setTimeout(() => {
572
+ server.closeAllConnections?.();
573
+ for (const connection of connections) connection.destroy();
574
+ }, forceAfterMs);
575
+ forceTimer.unref?.();
514
576
  }),
515
577
  };
516
578
  }
517
-
518
579
  async function redirectLaunchPage(launchPage, target) {
519
580
  launchPage.redirect(target);
520
581
  const redirected =
521
582
  process.env.ZAIP_UI_NO_BROWSER === "1" ? true : await launchPage.waitForRedirect();
522
- await launchPage.close();
523
583
  if (!redirected && !(await openBrowser(target))) {
524
584
  throw new Error("Could not open the dashboard after the preparation page did not redirect.");
525
585
  }
586
+ // Do not tear down the preparation page merely because its ready poll was
587
+ // observed: that happens before location.replace and used to strand users
588
+ // on the skeleton when navigation was blocked. Its watchdog/link remains
589
+ // available for a bounded grace period.
590
+ launchPage.closeAfterHandoffGrace();
526
591
  }
527
-
528
592
  async function assertRuntimeReady() {
529
593
  const serverPath = (await exists(sourceServer)) ? sourceServer : installedServer;
530
594
  if (!(await exists(serverPath))) {
@@ -539,53 +603,32 @@ async function assertRuntimeReady() {
539
603
  }
540
604
  return serverPath;
541
605
  }
542
-
543
606
  async function stopOwnedDashboard(session) {
544
- if (!(await probeDashboardPort(session?.port, fetch, workspaceKey, false))) return true;
545
- if (!Number.isInteger(session?.rootPid) || session.rootPid <= 0) return false;
546
- await stopProcessTree(session.rootPid);
547
- const deadline = Date.now() + 5_000;
548
- while (Date.now() < deadline) {
549
- if (!(await probeDashboardPort(session.port))) return true;
550
- await delay(100);
551
- }
552
- return false;
553
- }
554
-
555
- async function stopProcessTree(pid) {
556
- if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return;
557
- if (process.platform === "win32") {
558
- await new Promise((resolveStop) => {
559
- const killer = spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
560
- stdio: "ignore",
561
- windowsHide: true,
562
- });
563
- killer.once("error", () => resolveStop());
564
- killer.once("exit", () => resolveStop());
565
- });
566
- return;
567
- }
568
- try {
569
- process.kill(pid, "SIGTERM");
570
- } catch {
571
- // A stale recorded PID is already stopped.
572
- }
607
+ return stopOwnedDashboardProcess(session, {
608
+ probeDashboardPort,
609
+ workspaceKey,
610
+ fetchImpl: fetch,
611
+ host,
612
+ packageRoot,
613
+ delay,
614
+ });
573
615
  }
574
-
575
- async function previousDashboardSessions() {
576
- const session = await readDashboardSession();
577
- const registryEntries = (await readPortRegistry()).filter((entry) => entry.status === "active");
578
- const candidates = [session, ...registryEntries].filter(Boolean);
579
- return candidates.filter(
580
- (candidate, index) =>
581
- Number.isInteger(candidate.port) &&
582
- candidates.findIndex(
583
- (entry) => entry.port === candidate.port && entry.rootPid === candidate.rootPid,
584
- ) === index,
585
- );
616
+ export async function previousDashboardSessions({
617
+ sessionPath: candidateSessionPath = sessionPath,
618
+ registryPath: candidateRegistryPath = portRegistryPath,
619
+ probe = (port) => probeDashboardPort(port, fetch, workspaceKey, false),
620
+ } = {}) {
621
+ return collectPreviousDashboardSessions({
622
+ candidateSessionPath,
623
+ candidateRegistryPath,
624
+ probe,
625
+ readDashboardSession,
626
+ readPortRegistry,
627
+ workspaceRoot,
628
+ workspaceKey,
629
+ });
586
630
  }
587
-
588
- async function startDashboardServer(serverPath, token, excludedPorts, initialPort = null) {
631
+ async function startDashboardServer(serverPath, token, excludedPorts, initialPort = null, onProgress = () => {}) {
589
632
  const failures = [];
590
633
  for (let attempt = 1; attempt <= maxServerStartAttempts; attempt += 1) {
591
634
  const port =
@@ -593,7 +636,13 @@ async function startDashboardServer(serverPath, token, excludedPorts, initialPor
593
636
  ? initialPort
594
637
  : await findAvailablePortWithFallback({ excludedPorts: [...excludedPorts] });
595
638
  excludedPorts.add(port);
639
+ const dbPath = resolve(
640
+ sqliteReadModelDirectory,
641
+ `read-model-${port}-${process.pid}-${attempt}.sqlite`,
642
+ );
596
643
  let stderr = "";
644
+ let startupTelemetryBuffer = "";
645
+ let snapshotCache = null;
597
646
  const child = spawn(process.execPath, [serverPath], {
598
647
  cwd: packageRoot,
599
648
  env: {
@@ -602,6 +651,15 @@ async function startDashboardServer(serverPath, token, excludedPorts, initialPor
602
651
  ZAIP_UI_ROOT: frontendRoot,
603
652
  ZAIP_UI_TOKEN: token,
604
653
  ZAIP_SERVER_PORT: String(port),
654
+ NODE_COMPILE_CACHE: nodeCompileCacheDirectory,
655
+ NODE_NO_WARNINGS: "1",
656
+ ZAIP_DASHBOARD_LAUNCH_PROGRESS: "1",
657
+ ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "1",
658
+ ZAIP_SQLITE_CACHE_DIR: sqliteSnapshotCacheDirectory,
659
+ // A retry or a fresh-port handoff never reuses a live SQLite file.
660
+ // Windows keeps an open read-only handle locked, so each child owns a
661
+ // generation identified by launcher PID, attempt and selected port.
662
+ ZAIP_SQLITE_DB_PATH: dbPath,
605
663
  },
606
664
  stdio: ["ignore", "ignore", "pipe"],
607
665
  windowsHide: true,
@@ -609,14 +667,50 @@ async function startDashboardServer(serverPath, token, excludedPorts, initialPor
609
667
  child.stderr?.setEncoding("utf8");
610
668
  child.stderr?.on("data", (chunk) => {
611
669
  stderr = `${stderr}${chunk}`.slice(-4_000);
670
+ startupTelemetryBuffer = `${startupTelemetryBuffer}${String(chunk)}`;
671
+ const lines = startupTelemetryBuffer.split("\n");
672
+ startupTelemetryBuffer = lines.pop() ?? "";
673
+ for (const line of lines) {
674
+ try {
675
+ const event = JSON.parse(line);
676
+ if (event?.event === "dashboard_sqlite_rebuild_progress")
677
+ onProgress({
678
+ percent: event.percent,
679
+ phase: "Configurando o banco SQLite local",
680
+ detail: "Esta é a instalação/atualização da cópia local descartável; nenhum servidor externo é instalado.",
681
+ });
682
+ if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "main_entered")
683
+ onProgress({ percent: 50, phase: "Lendo os registros do projeto", detail: "Preparando o read model SQLite local." });
684
+ if (event?.event === "dashboard_sqlite_read_model") snapshotCache = event.cache;
685
+ if (event?.event === "dashboard_sqlite_read_model")
686
+ onProgress({ percent: 82, phase: "Banco local pronto", detail: event.cache === "rebuilt" ? "O read model SQLite foi reconstruído para os dados atuais." : "Uma cópia local do read model foi preparada." });
687
+ if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "listening")
688
+ onProgress({ percent: 88, phase: "Iniciando a interface", detail: "Verificando saúde e disponibilidade dos dados." });
689
+ } catch {
690
+ // Only structured startup telemetry affects the user-facing progress.
691
+ }
692
+ }
612
693
  });
613
694
  try {
614
695
  await waitForDashboard(port, child);
615
- return { child, port };
696
+ const timings = stripTerminalControlCodes(stderr)
697
+ .split("\n")
698
+ .filter(
699
+ (line) =>
700
+ line.includes('"event":"dashboard_sqlite_read_model"') ||
701
+ line.includes('"event":"dashboard_sqlite_startup_phase"'),
702
+ );
703
+ for (const timing of timings) appendLauncherLog(timing);
704
+ return { child, port, dbPath, snapshotCache };
616
705
  } catch (error) {
617
706
  await stopProcessTree(child.pid);
618
- const detail = stderr.trim();
619
- failures.push(`porta ${port}: ${error.message}${detail ? ` — ${detail}` : ""}`);
707
+ await removeGeneratedReadModel(dbPath);
708
+ const detail = stripTerminalControlCodes(stderr)
709
+ .split("\n")
710
+ .filter((line) => !line.includes("ExperimentalWarning") && !line.includes("node --trace-warnings"))
711
+ .join("\n")
712
+ .trim();
713
+ failures.push(`porta ${port}: ${error.message}${detail ? ` — ${formatLauncherError(detail, 600)}` : ""}`);
620
714
  appendLauncherLog(
621
715
  `Dashboard start attempt ${attempt} failed on port ${port}: ${error.message}`,
622
716
  );
@@ -628,6 +722,82 @@ async function startDashboardServer(serverPath, token, excludedPorts, initialPor
628
722
  );
629
723
  }
630
724
 
725
+ async function removeGeneratedReadModel(candidate) {
726
+ if (typeof candidate !== "string") return false;
727
+ const resolvedCandidate = resolve(candidate);
728
+ const fromReadModelDirectory = relative(sqliteReadModelDirectory, resolvedCandidate);
729
+ const generatedName = /^read-model-\d+-\d+-\d+\.sqlite$/;
730
+ if (
731
+ fromReadModelDirectory === "" ||
732
+ fromReadModelDirectory.startsWith("..") ||
733
+ fromReadModelDirectory.includes("/") ||
734
+ fromReadModelDirectory.includes("\\") ||
735
+ !generatedName.test(fromReadModelDirectory)
736
+ )
737
+ return false;
738
+ for (let attempt = 1; attempt <= 20; attempt += 1) {
739
+ try {
740
+ await rm(resolvedCandidate, { force: true });
741
+ return true;
742
+ } catch (error) {
743
+ if (error?.code !== "EBUSY" && error?.code !== "EPERM") throw error;
744
+ if (attempt < 20) await delay(250);
745
+ }
746
+ }
747
+ appendLauncherLog(`Could not remove closed SQLite read model after retries: ${resolvedCandidate}`);
748
+ return false;
749
+ }
750
+ function refreshSnapshotSeedInBackground() {
751
+ const refreshPath = resolve(sqliteReadModelDirectory, `refresh-${process.pid}-${randomBytes(4).toString("hex")}.sqlite`);
752
+ const refreshStatusPath = join(sessionDirectory, "dashboard-sqlite-refresh.json");
753
+ const refreshLogPath = join(sessionDirectory, "dashboard-sqlite-refresh.log");
754
+ mkdirSync(sessionDirectory, { recursive: true });
755
+ const refreshLogDescriptor = openSync(refreshLogPath, "a");
756
+ let worker;
757
+ try {
758
+ worker = spawn(process.execPath, [resolve(packageRoot, "scripts/rebuild-sqlite-read-model.mjs")], {
759
+ cwd: workspaceRoot,
760
+ env: {
761
+ ...process.env,
762
+ ZAIP_SQLITE_DB_PATH: refreshPath,
763
+ ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "0",
764
+ ZAIP_DASHBOARD_LAUNCH_PROGRESS: "0",
765
+ ZAIP_SQLITE_REFRESH_CLEANUP: "1",
766
+ ZAIP_SQLITE_CACHE_DIR: sqliteSnapshotCacheDirectory,
767
+ },
768
+ // A detached worker keeps its own append-only evidence file. Pipes can
769
+ // disappear with a fast launcher exit and silently abort the refresh.
770
+ stdio: ["ignore", refreshLogDescriptor, refreshLogDescriptor],
771
+ detached: true,
772
+ windowsHide: true,
773
+ });
774
+ } finally {
775
+ closeSync(refreshLogDescriptor);
776
+ }
777
+ void writeJsonAtomic(refreshStatusPath, {
778
+ status: "running",
779
+ pid: worker.pid ?? null,
780
+ path: refreshPath,
781
+ cacheDirectory: sqliteSnapshotCacheDirectory,
782
+ startedAt: new Date().toISOString(),
783
+ }).catch(() => {});
784
+ worker.once("error", (error) => {
785
+ appendLauncherLog(`Background SQLite seed refresh failed to start: ${error.message}`);
786
+ void writeJsonAtomic(refreshStatusPath, { status: "failed", error: error.message, finishedAt: new Date().toISOString() }).catch(() => {});
787
+ });
788
+ worker.once("exit", (code) => {
789
+ const status = code === 0 ? "completed" : "failed";
790
+ appendLauncherLog(`Background SQLite seed refresh ${status} (${code ?? "unknown"}); inspect ${refreshLogPath}.`);
791
+ void writeJsonAtomic(refreshStatusPath, {
792
+ status,
793
+ exitCode: code ?? null,
794
+ logPath: refreshLogPath,
795
+ finishedAt: new Date().toISOString(),
796
+ }).catch(() => {});
797
+ });
798
+ worker.unref();
799
+ appendLauncherLog(`Started background SQLite seed refresh at ${refreshPath}; cache ${sqliteSnapshotCacheDirectory}; worker log ${refreshLogPath}.`);
800
+ }
631
801
  async function writeLauncherStatus(status, message = "") {
632
802
  const path = process.env.ZAIP_LAUNCH_STATUS;
633
803
  if (!path) return;
@@ -635,74 +805,142 @@ async function writeLauncherStatus(status, message = "") {
635
805
  await writeFile(temporaryPath, `${status}\n${message}`.trimEnd(), "utf8");
636
806
  await rename(temporaryPath, path);
637
807
  }
638
-
808
+ function phaseTiming(phase, startedAt, details = {}) {
809
+ appendLauncherLog(JSON.stringify({ event: "dashboard_sqlite_phase", phase, elapsed_ms: Date.now() - startedAt, launcher_pid: process.pid, ...details }));
810
+ }
639
811
  async function main() {
640
812
  let ownedChild = null;
641
813
  let currentSession = null;
642
814
  let launchPage = null;
643
815
  const token = process.env.ZAIP_UI_TOKEN ?? randomBytes(24).toString("base64url");
644
- // Keep the session observed before waiting. If a different healthy session
645
- // exists once this process owns the lock, it was produced by a concurrent
646
- // launch and is already handed off to the browser. Do not open a second
647
- // preparation page or replace that healthy launch.
648
- const observedSession = await readDashboardSession();
649
- const observedRootPid = observedSession?.rootPid ?? null;
650
-
651
- await withLauncherLock(async () => {
652
- const concurrentSession = await findReusableDashboardSession();
653
- if (concurrentSession !== null && concurrentSession.rootPid !== observedRootPid) {
654
- appendLauncherLog(
655
- `Coalesced dashboard launcher request with healthy concurrent session on port ${concurrentSession.port}.`,
656
- );
657
- await writeLauncherStatus("ok");
658
- return;
659
- }
660
-
661
- const launchPort = await findAvailablePortWithFallback();
662
- launchPage = await startDashboardLaunchPage(launchPort);
816
+ const launchStartedAt = Date.now();
817
+ // Bind the preparation page atomically on an ephemeral port before waiting
818
+ // for the launch lock. Looking up a free port and binding it later lets a
819
+ // concurrent preparation page claim that port, which makes the runtime
820
+ // probe mistake its intentional 503 for a server failure.
821
+ launchPage = await startDashboardLaunchPage(0);
822
+ const launchPort = launchPage.port;
823
+ try {
663
824
  if (!(await openBrowser(dashboardUrl(launchPort, token)))) {
664
- await launchPage.close();
665
- launchPage = null;
666
825
  throw new Error("Could not open the dashboard preparation page in the default browser.");
667
826
  }
668
-
669
- const buildSignature = await ensureFreshDashboardBuild();
670
- const previousSessions = await previousDashboardSessions();
671
- const excludedPorts = new Set([...previousSessions.map((session) => session.port), launchPort]);
672
- const serverPath = await assertRuntimeReady();
673
- const { child, port } = await startDashboardServer(serverPath, token, excludedPorts);
674
- ownedChild = child;
675
- try {
676
- await redirectLaunchPage(launchPage, dashboardUrl(port, token));
677
- launchPage = null;
678
- currentSession = {
679
- kind: runtimeKind,
680
- workspace: workspaceRoot,
681
- workspaceKey,
682
- rootPid: process.pid,
683
- serverPid: child.pid,
684
- port,
827
+ launchPage.setProgress({
828
+ percent: 10,
829
+ phase: "Validando pré-requisitos locais",
830
+ detail: "Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
831
+ });
832
+ await assertDashboardPrerequisites();
833
+ launchPage.setProgress({
834
+ percent: 18,
835
+ phase: "Preparando a sessão do dashboard",
836
+ detail: "Coordenando esta abertura local com segurança.",
837
+ });
838
+ await withLauncherLock(async () => {
839
+ phaseTiming("lock_acquired", launchStartedAt);
840
+ const buildStartedAt = Date.now();
841
+ launchPage.setProgress({
842
+ percent: 28,
843
+ phase: "Verificando os artefatos do dashboard",
844
+ detail: "Usando o build existente quando ele já está atualizado.",
845
+ });
846
+ const buildSignature = await ensureFreshDashboardBuild();
847
+ phaseTiming("build_complete", buildStartedAt, { build_signature: buildSignature });
848
+ // A second deliberate click must use the already healthy compatible
849
+ // runtime. Build signature makes this safe across dashboard updates;
850
+ // an incompatible session continues through a replacement handoff.
851
+ const reusable = await findReusableDashboardSession({
852
+ expectedBuildSignature: buildSignature,
853
+ expectedSnapshotState: "current",
854
+ });
855
+ if (reusable !== null) {
856
+ appendLauncherLog(`Reused healthy compatible dashboard session on port ${reusable.port}, PID ${reusable.serverPid ?? "unknown"}.`);
857
+ await redirectLaunchPage(launchPage, dashboardUrl(reusable.port, reusable.token));
858
+ launchPage = null;
859
+ await writeLauncherStatus("ok");
860
+ return;
861
+ }
862
+ const previousSessions = await previousDashboardSessions();
863
+ const excludedPorts = new Set([
864
+ ...previousSessions.map((session) => session.port),
865
+ launchPort,
866
+ ]);
867
+ const serverPath = await assertRuntimeReady();
868
+ const readyStartedAt = Date.now();
869
+ launchPage.setProgress({
870
+ percent: 42,
871
+ phase: "Abrindo o banco local descartável",
872
+ detail: "Criando uma cópia privada do read model SQLite para esta sessão.",
873
+ });
874
+ const { child, port, dbPath, snapshotCache } = await startDashboardServer(
875
+ serverPath,
685
876
  token,
686
- buildSignature,
687
- createdAt: new Date().toISOString(),
688
- };
689
- await writeDashboardSession(currentSession);
690
- await recordPortStatus(currentSession, "active");
691
- const warnings = [];
692
- for (const previous of previousSessions) {
693
- const stopped = await stopOwnedDashboard(previous);
694
- await recordPortStatus(previous, stopped ? "closed" : "close_failed");
695
- if (!stopped) warnings.push(`não foi possível fechar a porta anterior ${previous.port}`);
877
+ excludedPorts,
878
+ null,
879
+ (progress) => launchPage?.setProgress(progress),
880
+ );
881
+ phaseTiming("server_ready", readyStartedAt, { server_pid: child.pid, port, db_path: dbPath });
882
+ ownedChild = child;
883
+ try {
884
+ launchPage.setProgress({
885
+ percent: 92,
886
+ phase: snapshotCache === "stale_seed" ? "Abrindo snapshot local validado" : "Dashboard pronto",
887
+ detail: snapshotCache === "stale_seed"
888
+ ? "Os dados canônicos estão sendo atualizados em segundo plano para a próxima abertura."
889
+ : "Validando saúde e dados iniciais antes de abrir a tela principal.",
890
+ });
891
+ if (snapshotCache === "stale_seed") await delay(350);
892
+ await redirectLaunchPage(launchPage, dashboardUrl(port, token, snapshotCache === "stale_seed"));
893
+ phaseTiming("handoff_complete", launchStartedAt, { server_pid: child.pid, port });
894
+ launchPage = null;
895
+ if (snapshotCache === "stale_seed")
896
+ refreshSnapshotSeedInBackground();
897
+ currentSession = {
898
+ kind: runtimeKind,
899
+ workspace: workspaceRoot,
900
+ workspaceKey,
901
+ rootPid: process.pid,
902
+ serverPid: child.pid,
903
+ port,
904
+ token,
905
+ dbPath,
906
+ buildSignature,
907
+ generationId: `${process.pid}-${child.pid}-${port}-${randomBytes(6).toString("hex")}`,
908
+ // Never pin subsequent clicks to a stale seed. A completed
909
+ // background refresh is consumed by a fresh handoff on the next
910
+ // click, then the old updating generation is retired below.
911
+ snapshotState: snapshotCache === "stale_seed" ? "updating" : "current",
912
+ createdAt: new Date().toISOString(),
913
+ };
914
+ await writeDashboardSession(currentSession);
915
+ await recordPortStatus(currentSession, "active");
916
+ const warnings = [];
917
+ for (const previous of previousSessions) {
918
+ const status = await stopOwnedDashboard(previous);
919
+ await recordPortStatus(previous, status);
920
+ if (status === "closed") await removeGeneratedReadModel(previous.dbPath);
921
+ if (teardownWarning(status))
922
+ warnings.push(`não foi possível fechar a porta anterior ${previous.port}`);
923
+ else if (status === "skipped_unowned")
924
+ appendLauncherLog(
925
+ `Skipped teardown of port ${previous.port}: registered server PID does not own its live listener.`,
926
+ );
927
+ }
928
+ if (warnings.length > 0)
929
+ appendLauncherLog(
930
+ `Dashboard launched on port ${port}; prior-port cleanup: ${warnings.join("; ")}`,
931
+ );
932
+ await writeLauncherStatus("ok");
933
+ } catch (error) {
934
+ await stopProcessTree(child.pid);
935
+ if (currentSession !== null) await recordPortStatus(currentSession, "failed");
936
+ await clearOwnedDashboardSession();
937
+ throw error;
696
938
  }
697
- await writeLauncherStatus(warnings.length > 0 ? "warning" : "ok", warnings.join("; "));
698
- } catch (error) {
699
- await stopProcessTree(child.pid);
700
- if (currentSession !== null) await recordPortStatus(currentSession, "failed");
701
- await clearOwnedDashboardSession();
702
- throw error;
703
- }
704
- });
705
-
939
+ });
940
+ } catch (error) {
941
+ if (launchPage !== null) await launchPage.close().catch(() => {});
942
+ throw error;
943
+ }
706
944
  if (ownedChild === null) return;
707
945
  for (const signal of ["SIGINT", "SIGTERM"]) {
708
946
  process.once(signal, () => ownedChild?.kill(signal));
@@ -712,10 +950,12 @@ async function main() {
712
950
  ownedChild.once("exit", (code) => resolveExit(code ?? 0));
713
951
  });
714
952
  await clearOwnedDashboardSession();
715
- if (currentSession !== null) await recordPortStatus(currentSession, "closed");
953
+ if (currentSession !== null) {
954
+ await recordPortStatus(currentSession, "closed");
955
+ await removeGeneratedReadModel(currentSession.dbPath);
956
+ }
716
957
  process.exitCode = exitCode;
717
958
  }
718
-
719
959
  function isPortFree(port) {
720
960
  return new Promise((resolvePort) => {
721
961
  const server = createServer();
@@ -724,7 +964,6 @@ function isPortFree(port) {
724
964
  server.listen({ host, port }, () => server.close(() => resolvePort(true)));
725
965
  });
726
966
  }
727
-
728
967
  async function waitForDashboard(port, child) {
729
968
  const deadline = Date.now() + startupTimeoutMs;
730
969
  while (Date.now() < deadline) {
@@ -735,7 +974,19 @@ async function waitForDashboard(port, child) {
735
974
  const response = await fetch(dashboardHealthUrl(port), { signal: AbortSignal.timeout(750) });
736
975
  const payload = await response.json().catch(() => null);
737
976
  if (response.ok && payload?.kind === runtimeKind && payload?.workspaceKey === workspaceKey) {
738
- return;
977
+ const readiness = await fetch(`http://${host}:${port}/api/v2/ready`, {
978
+ signal: AbortSignal.timeout(750),
979
+ cache: "no-store",
980
+ });
981
+ if (readiness.ok) return;
982
+ if (readiness.status >= 500) {
983
+ const detail = await readiness.json().catch(() => null);
984
+ const unavailable = new Error(
985
+ `Dashboard data readiness returned ${readiness.status}: ${detail?.error ?? "projection unavailable"}`,
986
+ );
987
+ unavailable.retryable = false;
988
+ throw unavailable;
989
+ }
739
990
  }
740
991
  if (response.status >= 500) {
741
992
  const unavailable = new Error(
@@ -747,26 +998,22 @@ async function waitForDashboard(port, child) {
747
998
  } catch (error) {
748
999
  if (error?.retryable === false) throw error;
749
1000
  }
750
- await delay(250);
1001
+ await delay(100);
751
1002
  }
752
1003
  throw new Error(`Timed out waiting for ${dashboardHealthUrl(port)}`);
753
1004
  }
754
-
755
1005
  function exists(path) {
756
1006
  return access(path).then(
757
1007
  () => true,
758
1008
  () => false,
759
1009
  );
760
1010
  }
761
-
762
1011
  function delay(milliseconds) {
763
1012
  return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
764
1013
  }
765
-
766
1014
  const isMain = process.argv[1]
767
1015
  ? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
768
1016
  : false;
769
-
770
1017
  if (isMain) {
771
1018
  main().catch(async (error) => {
772
1019
  const logMessage = error instanceof Error ? (error.stack ?? error.message) : String(error);