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