@zaip-ai/cli 1.3.8 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/README.md +58 -3
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.d.ts.map +1 -1
  4. package/dist/commands/init.js +17 -6
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/services/adapters.js +4 -4
  11. package/dist/services/adapters.js.map +1 -1
  12. package/dist/services/codex-config-template.d.ts +2 -2
  13. package/dist/services/codex-config-template.d.ts.map +1 -1
  14. package/dist/services/codex-config-template.js +3 -3
  15. package/dist/services/codex-config-template.js.map +1 -1
  16. package/dist/services/codex-rules.d.ts +1 -1
  17. package/dist/services/codex-rules.d.ts.map +1 -1
  18. package/dist/services/codex-rules.js +9 -24
  19. package/dist/services/codex-rules.js.map +1 -1
  20. package/dist/services/delivery-git-preflight.d.ts +23 -0
  21. package/dist/services/delivery-git-preflight.d.ts.map +1 -0
  22. package/dist/services/delivery-git-preflight.js +69 -0
  23. package/dist/services/delivery-git-preflight.js.map +1 -0
  24. package/dist/services/doctor.d.ts.map +1 -1
  25. package/dist/services/doctor.js +13 -1
  26. package/dist/services/doctor.js.map +1 -1
  27. package/dist/services/install-agents-template.d.ts.map +1 -1
  28. package/dist/services/install-agents-template.js +22 -28
  29. package/dist/services/install-agents-template.js.map +1 -1
  30. package/dist/services/install-profile.d.ts +8 -0
  31. package/dist/services/install-profile.d.ts.map +1 -0
  32. package/dist/services/install-profile.js +56 -0
  33. package/dist/services/install-profile.js.map +1 -0
  34. package/dist/services/logger.d.ts.map +1 -1
  35. package/dist/services/logger.js +6 -5
  36. package/dist/services/logger.js.map +1 -1
  37. package/dist/services/stack-git.d.ts +9 -0
  38. package/dist/services/stack-git.d.ts.map +1 -0
  39. package/dist/services/stack-git.js +188 -0
  40. package/dist/services/stack-git.js.map +1 -0
  41. package/dist/services/stack-plan.d.ts +3 -1
  42. package/dist/services/stack-plan.d.ts.map +1 -1
  43. package/dist/services/stack-plan.js +15 -1
  44. package/dist/services/stack-plan.js.map +1 -1
  45. package/dist/services/stack-terraform.d.ts +13 -0
  46. package/dist/services/stack-terraform.d.ts.map +1 -0
  47. package/dist/services/stack-terraform.js +274 -0
  48. package/dist/services/stack-terraform.js.map +1 -0
  49. package/dist/services/template-manifest.d.ts.map +1 -1
  50. package/dist/services/template-manifest.js +14 -1
  51. package/dist/services/template-manifest.js.map +1 -1
  52. package/dist/types.d.ts +1 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/templates/.agents/.env.example +34 -0
  56. package/templates/{README.md → .agents/README.md} +124 -60
  57. package/templates/.agents/config/agents/catalog.yaml +23 -23
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
  60. package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
  61. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
  62. package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
  63. package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
  64. package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
  65. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
  66. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
  67. package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
  68. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
  69. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
  70. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
  71. package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
  72. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
  73. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
  74. package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
  75. package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
  76. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
  77. package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
  78. package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
  79. package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
  80. package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
  81. package/templates/.agents/config/pricing/models.json +25 -1
  82. package/templates/.agents/config/project.yaml +2 -0
  83. package/templates/.agents/dashboard/.zaip-managed.json +200 -88
  84. package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
  85. package/templates/.agents/dashboard/README.md +58 -19
  86. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
  87. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-VcLx9iOO.js → DashboardHelpDialog-oZVlf-l5.js} +72 -8
  88. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
  89. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
  90. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-DFZwIXSC.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
  91. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
  92. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
  93. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
  94. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
  95. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
  96. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
  97. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  98. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
  99. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
  100. package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +11 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
  106. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +7 -8
  107. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +14 -2
  108. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +35 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
  110. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
  112. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
  113. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
  114. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
  115. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
  116. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
  117. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
  118. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
  119. package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
  120. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
  121. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
  122. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
  123. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
  124. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
  125. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
  126. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
  127. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +482 -332
  128. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
  129. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
  130. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
  131. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
  132. package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
  133. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
  134. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +90 -1
  135. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +112 -22
  136. package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
  137. package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
  138. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
  139. package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
  140. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
  141. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
  142. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
  143. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
  144. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
  145. package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
  146. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
  147. package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
  148. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
  149. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
  150. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
  151. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
  152. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
  153. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
  154. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
  155. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
  156. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
  157. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
  158. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
  159. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
  160. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
  161. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
  162. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
  163. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
  164. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
  165. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
  166. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
  167. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
  168. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
  169. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
  170. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
  171. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
  172. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
  173. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
  174. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
  175. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
  176. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
  177. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
  178. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
  179. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
  180. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
  181. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +231 -239
  182. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
  183. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
  184. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  185. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  186. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
  187. package/templates/.agents/requirements/comparative-audit.md +1 -1
  188. package/templates/.agents/requirements/context-engine.md +1 -1
  189. package/templates/.agents/requirements/cost-observability.md +1 -1
  190. package/templates/.agents/requirements/dashboard-launch.md +2 -2
  191. package/templates/.agents/requirements/data-export.md +1 -1
  192. package/templates/.agents/requirements/design-system.md +1 -1
  193. package/templates/.agents/requirements/execution-engine.md +1 -1
  194. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  195. package/templates/.agents/requirements/model-orchestration.md +1 -1
  196. package/templates/.agents/requirements/public-distribution.md +1 -1
  197. package/templates/.agents/requirements/software-architecture.md +1 -1
  198. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  199. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  200. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  201. package/templates/.agents/rules/autonomy-decision.md +1 -1
  202. package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
  203. package/templates/.agents/rules/core.md +5 -4
  204. package/templates/.agents/rules/local-sqlite-storage.md +18 -0
  205. package/templates/.agents/rules/meta-mcp.md +21 -0
  206. package/templates/.agents/rules/workflow.md +1 -0
  207. package/templates/.agents/runtime/bin/zaip.mjs +305 -137
  208. package/templates/.agents/runtime/checksums.sha256 +2 -2
  209. package/templates/.agents/runtime/provenance.json +2 -2
  210. package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
  211. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
  212. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
  213. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
  214. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
  215. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
  216. package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
  217. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
  218. package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
  219. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
  220. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
  221. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
  222. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
  223. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
  224. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
  225. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
  226. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
  227. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
  228. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
  229. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
  230. package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
  231. package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
  232. package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
  233. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
  234. package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
  235. package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
  236. package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
  237. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
  238. package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
  239. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
  240. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
  241. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
  242. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
  243. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
  244. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
  245. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
  246. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
  247. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
  248. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
  249. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
  250. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
  251. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
  252. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
  253. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
  254. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
  255. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
  256. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
  257. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
  258. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
  259. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
  260. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
  261. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
  262. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
  263. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
  264. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
  265. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
  266. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
  267. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
  268. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
  269. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
  270. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
  271. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
  272. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
  273. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
  274. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
  275. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
  276. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
  277. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
  278. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
  279. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
  280. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
  281. package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
  282. package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
  283. package/templates/.agents/tools/catalog.yaml +6 -0
  284. package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
  285. package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
  286. package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
  287. package/templates/.agents/tools/cloud-runner.mjs +786 -0
  288. package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
  289. package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
  290. package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
  291. package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
  292. package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
  293. package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
  294. package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
  295. package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
  296. package/templates/.agents/tools/profiles/development.yaml +3 -0
  297. package/templates/infra/terraform/.terraform.lock.hcl +42 -0
  298. package/templates/infra/terraform/README.md +31 -0
  299. package/templates/infra/terraform/backend.tf.json +13 -0
  300. package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
  301. package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
  302. package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
  303. package/templates/infra/terraform/main.tf +77 -0
  304. package/templates/infra/terraform/outputs.tf +20 -0
  305. package/templates/infra/terraform/terraform.tfvars.example +26 -0
  306. package/templates/infra/terraform/variables.tf +91 -0
  307. package/templates/infra/terraform/versions.tf +18 -0
  308. package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
  309. package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
  310. package/templates/template-manifest.json +599 -167
  311. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.js +0 -3
  312. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
  313. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Dyo_wpL4.js +0 -2
  314. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
  315. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
@@ -1,20 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createHash, randomBytes } from "node:crypto";
3
3
  import { appendFileSync, mkdirSync } from "node:fs";
4
- import {
5
- access,
6
- mkdir,
7
- readdir,
8
- readFile,
9
- rename,
10
- rm,
11
- stat,
12
- utimes,
13
- writeFile,
14
- } from "node:fs/promises";
15
- import { createServer } from "node:net";
4
+ import { mkdir, readdir, readFile, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
16
5
  import { tmpdir } from "node:os";
17
- import { dirname, join, relative, resolve, win32 } from "node:path";
6
+ import { dirname, join, resolve, win32 } from "node:path";
18
7
  import process from "node:process";
19
8
  import { fileURLToPath } from "node:url";
20
9
  import { collectPreviousDashboardSessions } from "./start-dashboard-history.mjs";
@@ -25,14 +14,18 @@ import {
25
14
  presentLaunchFailure,
26
15
  writeLauncherStatus,
27
16
  } from "./start-dashboard-lifecycle.mjs";
28
- import { assertDashboardPrerequisites } from "./start-dashboard-prerequisites.mjs";
17
+ import { assertDashboardPrerequisites, exists } from "./start-dashboard-prerequisites.mjs";
29
18
  import {
19
+ isKnownDashboardLauncher,
30
20
  isProcessAlive,
31
21
  stopOwnedDashboardProcess,
32
22
  stopProcessTree,
33
23
  teardownWarning,
34
24
  } from "./start-dashboard-processes.mjs";
35
- import { refreshIsRunning, startSeedRefresh } from "./start-dashboard-refresh.mjs";
25
+ import {
26
+ isPortFree,
27
+ startDashboardServer as launchDashboardServer,
28
+ } from "./start-dashboard-server.mjs";
36
29
 
37
30
  export {
38
31
  isAncestorProcess,
@@ -53,15 +46,22 @@ export const workspaceKey = createHash("sha256")
53
46
  .slice(0, 16);
54
47
  export const sessionDirectory = join(tmpdir(), "zaip-aios", `dashboard-sqlite-${workspaceKey}`);
55
48
  export const sessionPath = join(sessionDirectory, "dashboard-sqlite-session.json");
49
+ // This small, token-free record is the observable ownership contract while a
50
+ // dashboard is still preparing. It deliberately does not replace the active
51
+ // runtime session, so a newer click can take priority without losing the PID
52
+ // needed to stop the older runtime.
53
+ export const launchPath = join(sessionDirectory, "dashboard-sqlite-launch.json");
54
+ const claimLockPath = join(sessionDirectory, "dashboard-sqlite-claim.lock");
55
+ const claimLockTimeoutMs = 5_000;
56
+ const claimLockStaleMs = 30_000;
56
57
  export const portRegistryPath = join(sessionDirectory, "dashboard-sqlite-ports.json");
57
58
  export const lockPath = join(sessionDirectory, "dashboard-sqlite-launcher.lock");
58
59
  export const launcherLogPath = join(sessionDirectory, "dashboard-sqlite-launcher.log");
59
60
  const frontendRoot = resolve(workspaceRoot, ".agents/dashboard/apps/frontend/dist");
60
- const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "dashboard-sqlite");
61
- // Keep the immutable seed cache durable and independent of temporary SQLite generations.
62
- const sqliteSnapshotCacheDirectory = join(sqliteReadModelDirectory, "cache");
63
- // Node's bytecode cache is disposable acceleration beside private SQLite generations.
64
- const nodeCompileCacheDirectory = join(sqliteReadModelDirectory, "node-compile-cache");
61
+ const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "sqlite");
62
+ const sqliteReadModelPath = join(sqliteReadModelDirectory, "zaip-aios-dashboard.sqlite");
63
+ // Bytecode is disposable session data, separate from the persistent database.
64
+ const nodeCompileCacheDirectory = join(sessionDirectory, "node-compile-cache");
65
65
  const installedServer = resolve(packageRoot, "server.mjs");
66
66
  const sourceServer = resolve(packageRoot, "dist/server.cjs");
67
67
  const healthPath = "/api/v2/health";
@@ -183,6 +183,103 @@ export async function readDashboardSession(path = sessionPath) {
183
183
  export async function writeDashboardSession(session, path = sessionPath) {
184
184
  await writeJsonAtomic(path, session);
185
185
  }
186
+ export async function claimDashboardLaunch(
187
+ { launchPort, token },
188
+ {
189
+ path = launchPath,
190
+ lockPath = claimLockPath,
191
+ stopProcessTreeImpl = stopProcessTree,
192
+ isProcessAliveImpl = isProcessAlive,
193
+ isKnownDashboardLauncherImpl = isKnownDashboardLauncher,
194
+ delayImpl = delay,
195
+ } = {},
196
+ ) {
197
+ await mkdir(sessionDirectory, { recursive: true });
198
+ // This lock is deliberately short: it serializes only ownership transfer,
199
+ // never the SQLite build. A later click therefore observes and cancels the
200
+ // immediately preceding claim rather than a stale common predecessor.
201
+ const claimDeadline = Date.now() + claimLockTimeoutMs;
202
+ while (true) {
203
+ try {
204
+ await mkdir(lockPath);
205
+ await writeFile(join(lockPath, "owner"), String(process.pid), "utf8");
206
+ break;
207
+ } catch (error) {
208
+ if (error?.code !== "EEXIST") throw error;
209
+ const details = await stat(lockPath).catch(() => null);
210
+ const ownerPid = Number.parseInt(
211
+ await readFile(join(lockPath, "owner"), "utf8").catch(() => ""),
212
+ 10,
213
+ );
214
+ if (Number.isInteger(ownerPid) && ownerPid > 0 && !(await isProcessAliveImpl(ownerPid))) {
215
+ await rm(lockPath, { recursive: true, force: true });
216
+ continue;
217
+ }
218
+ if (details !== null && Date.now() - details.mtimeMs > claimLockStaleMs) {
219
+ await rm(lockPath, { recursive: true, force: true });
220
+ continue;
221
+ }
222
+ if (Date.now() >= claimDeadline)
223
+ throw new Error("Timed out waiting for the dashboard ownership claim lock.");
224
+ await delayImpl(25);
225
+ }
226
+ }
227
+ try {
228
+ const previous = await readDashboardSession(path);
229
+ const claim = {
230
+ kind: "zaip-dashboard-sqlite-launch",
231
+ workspace: workspaceRoot,
232
+ workspaceKey,
233
+ launcherPid: process.pid,
234
+ launchPort,
235
+ // Only a non-secret correlation id is persisted for diagnostics.
236
+ claimId: createHash("sha256").update(`${token}:${process.pid}`).digest("hex").slice(0, 16),
237
+ state: "preparing",
238
+ claimedAt: new Date().toISOString(),
239
+ statusPath: process.env.ZAIP_LAUNCH_STATUS ?? null,
240
+ };
241
+ await writeJsonAtomic(path, claim);
242
+ if (
243
+ previous?.kind === claim.kind &&
244
+ previous?.workspaceKey === workspaceKey &&
245
+ Number.isInteger(previous?.launcherPid) &&
246
+ previous.launcherPid > 0 &&
247
+ previous.launcherPid !== process.pid
248
+ ) {
249
+ // A claim replacement is also an active cancellation boundary. Do not
250
+ // merely fence stale cleanup while its expensive SQLite build continues.
251
+ if (!(await isKnownDashboardLauncherImpl(previous.launcherPid, packageRoot))) {
252
+ // The PID may have exited or been reused by an unrelated process. Its
253
+ // record is stale; never kill it and let this claim continue.
254
+ return claim;
255
+ }
256
+ if (typeof previous.statusPath === "string" && previous.statusPath.startsWith(tmpdir()))
257
+ await writeFile(
258
+ previous.statusPath,
259
+ "cancelled\nSuperseded by a newer dashboard click.",
260
+ "utf8",
261
+ ).catch(() => {});
262
+ await stopProcessTreeImpl(previous.launcherPid);
263
+ const deadline = Date.now() + 5_000;
264
+ while ((await isProcessAliveImpl(previous.launcherPid)) && Date.now() < deadline)
265
+ await delayImpl(50);
266
+ if (await isProcessAliveImpl(previous.launcherPid))
267
+ throw new Error(
268
+ "Could not stop the superseded dashboard launcher before starting a new session.",
269
+ );
270
+ }
271
+ return claim;
272
+ } finally {
273
+ await rm(lockPath, { recursive: true, force: true });
274
+ }
275
+ }
276
+ export async function isCurrentDashboardLaunch(claim) {
277
+ const current = await readDashboardSession(launchPath);
278
+ return current?.kind === claim.kind && current?.claimId === claim.claimId;
279
+ }
280
+ export async function clearDashboardLaunch(claim) {
281
+ if (await isCurrentDashboardLaunch(claim)) await rm(launchPath, { force: true });
282
+ }
186
283
  async function writeJsonAtomic(path, value) {
187
284
  const directory = dirname(path);
188
285
  await mkdir(directory, { recursive: true });
@@ -269,11 +366,31 @@ export async function findReusableDashboardSession({
269
366
  session?.workspaceKey !== expectedWorkspaceKey ||
270
367
  typeof session?.token !== "string" ||
271
368
  session.token.length === 0 ||
272
- (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature) ||
273
- (expectedSnapshotState !== null && session?.snapshotState !== expectedSnapshotState)
369
+ (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature)
274
370
  ) {
275
371
  return null;
276
372
  }
373
+ // Session metadata is written before the serving process finishes promoting
374
+ // a stale seed. Trust a state transition only after the owned server itself
375
+ // reports it; never reuse an unknown or still-updating generation as current.
376
+ if (expectedSnapshotState !== null && session.snapshotState !== expectedSnapshotState) {
377
+ if (!Number.isInteger(session.port)) return null;
378
+ try {
379
+ const response = await fetchImpl(dashboardHealthUrl(session.port), {
380
+ signal: AbortSignal.timeout(500),
381
+ });
382
+ const payload = await response.json();
383
+ if (
384
+ !response.ok ||
385
+ payload?.kind !== runtimeKind ||
386
+ payload?.workspaceKey !== expectedWorkspaceKey ||
387
+ payload?.snapshot_state !== expectedSnapshotState
388
+ )
389
+ return null;
390
+ } catch {
391
+ return null;
392
+ }
393
+ }
277
394
  if (
278
395
  Number.isInteger(session.port) &&
279
396
  (await probeDashboardPort(session.port, fetchImpl, expectedWorkspaceKey))
@@ -292,9 +409,14 @@ export async function findReusableDashboardSession({
292
409
  return port === undefined ? null : { ...session, port };
293
410
  }
294
411
 
295
- async function backgroundRefreshIsRunning() {
296
- return refreshIsRunning(join(sessionDirectory, "dashboard-sqlite-refresh.json"), isProcessAlive);
412
+ export function requestBackgroundProjectionRefresh(session, fetchImpl = fetch) {
413
+ return fetchImpl(`http://${host}:${session.port}/api/v2/refresh-projection`, {
414
+ method: "POST",
415
+ headers: { "x-zaip-session": session.token },
416
+ signal: AbortSignal.timeout(750),
417
+ }).catch(() => undefined);
297
418
  }
419
+
298
420
  export async function latestModifiedAt(paths, excludedSourcePaths = []) {
299
421
  let latest = 0;
300
422
  for (const path of paths) {
@@ -478,6 +600,7 @@ export async function withLauncherLock(
478
600
  timeoutMs = lockTimeoutMs,
479
601
  staleAfterMs = staleLockMs,
480
602
  onWait = () => {},
603
+ newestClickWins = false,
481
604
  } = {},
482
605
  ) {
483
606
  await mkdir(directory, { recursive: true });
@@ -485,6 +608,9 @@ export async function withLauncherLock(
485
608
  while (Date.now() < deadline) {
486
609
  try {
487
610
  await mkdir(path);
611
+ const lockOwner = `${process.pid}-${randomBytes(8).toString("hex")}`;
612
+ const lockOwnerPath = join(path, "owner");
613
+ await writeFile(lockOwnerPath, lockOwner, { encoding: "utf8", mode: 0o600 });
488
614
  const heartbeatIntervalMs = Math.max(lockHeartbeatFloorMs, Math.floor(staleAfterMs / 3));
489
615
  const heartbeat = setInterval(() => {
490
616
  const now = new Date();
@@ -495,10 +621,20 @@ export async function withLauncherLock(
495
621
  return await action();
496
622
  } finally {
497
623
  clearInterval(heartbeat);
498
- await rm(path, { recursive: true, force: true });
624
+ // Fencing prevents a superseded owner from deleting the newer click's
625
+ // lock directory in its finally block.
626
+ if ((await readFile(lockOwnerPath, "utf8").catch(() => "")) === lockOwner)
627
+ await rm(path, { recursive: true, force: true });
499
628
  }
500
629
  } catch (error) {
501
630
  if (error?.code !== "EEXIST") throw error;
631
+ if (newestClickWins) {
632
+ // The previous holder checks its launch claim at every expensive
633
+ // boundary. Replacing the directory lets the latest executable click
634
+ // proceed immediately instead of presenting a waiting screen.
635
+ await rm(path, { recursive: true, force: true });
636
+ continue;
637
+ }
502
638
  const lockStat = await stat(path).catch(() => null);
503
639
  if (lockStat !== null && Date.now() - lockStat.mtimeMs > staleAfterMs) {
504
640
  await rm(path, { recursive: true, force: true });
@@ -512,7 +648,7 @@ export async function withLauncherLock(
512
648
  }
513
649
  export async function findAvailablePort(startPort = preferredPort()) {
514
650
  for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
515
- if (await isPortFree(port)) return port;
651
+ if (await isPortFree(host, port)) return port;
516
652
  }
517
653
  throw new Error(
518
654
  `No free dashboard port found between ${startPort} and ${Math.min(startPort + maxPortAttempts - 1, 65_535)}`,
@@ -528,7 +664,7 @@ export async function findAvailablePortWithFallback({
528
664
  );
529
665
  for (const startPort of fallbackStarts) {
530
666
  for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
531
- if (!excluded.has(port) && (await isPortFree(port))) return port;
667
+ if (!excluded.has(port) && (await isPortFree(host, port))) return port;
532
668
  }
533
669
  }
534
670
  for (let attempt = 0; attempt < 5; attempt += 1) {
@@ -677,167 +813,30 @@ async function startDashboardServer(
677
813
  excludedPorts,
678
814
  initialPort = null,
679
815
  onProgress = () => {},
816
+ onChildStarted = () => {},
680
817
  ) {
681
- const failures = [];
682
- for (let attempt = 1; attempt <= maxServerStartAttempts; attempt += 1) {
683
- const port =
684
- attempt === 1 && Number.isInteger(initialPort)
685
- ? initialPort
686
- : await findAvailablePortWithFallback({ excludedPorts: [...excludedPorts] });
687
- excludedPorts.add(port);
688
- const dbPath = resolve(
689
- sqliteReadModelDirectory,
690
- `read-model-${port}-${process.pid}-${attempt}.sqlite`,
691
- );
692
- let stderr = "";
693
- let startupTelemetryBuffer = "";
694
- let snapshotCache = null;
695
- const child = spawn(process.execPath, [serverPath], {
696
- cwd: packageRoot,
697
- env: {
698
- ...process.env,
699
- ZAIP_WORKSPACE: workspaceRoot,
700
- ZAIP_UI_ROOT: frontendRoot,
701
- ZAIP_UI_TOKEN: token,
702
- ZAIP_SERVER_PORT: String(port),
703
- NODE_COMPILE_CACHE: nodeCompileCacheDirectory,
704
- NODE_NO_WARNINGS: "1",
705
- ZAIP_DASHBOARD_LAUNCH_PROGRESS: "1",
706
- ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "1",
707
- ZAIP_SQLITE_CACHE_DIR: sqliteSnapshotCacheDirectory,
708
- // A retry or a fresh-port handoff never reuses a live SQLite file.
709
- // Windows keeps an open read-only handle locked, so each child owns a
710
- // generation identified by launcher PID, attempt and selected port.
711
- ZAIP_SQLITE_DB_PATH: dbPath,
712
- },
713
- stdio: ["ignore", "ignore", "pipe"],
714
- windowsHide: true,
715
- });
716
- child.stderr?.setEncoding("utf8");
717
- child.stderr?.on("data", (chunk) => {
718
- stderr = `${stderr}${chunk}`.slice(-4_000);
719
- startupTelemetryBuffer = `${startupTelemetryBuffer}${String(chunk)}`;
720
- const lines = startupTelemetryBuffer.split("\n");
721
- startupTelemetryBuffer = lines.pop() ?? "";
722
- for (const line of lines) {
723
- try {
724
- const event = JSON.parse(line);
725
- if (event?.event === "dashboard_sqlite_rebuild_progress")
726
- onProgress({
727
- percent: event.percent,
728
- phase: "Configurando o banco SQLite local",
729
- detail:
730
- "Esta é a instalação/atualização da cópia local descartável; nenhum servidor externo é instalado.",
731
- });
732
- if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "main_entered")
733
- onProgress({
734
- percent: 50,
735
- phase: "Lendo os registros do projeto",
736
- detail: "Preparando o read model SQLite local.",
737
- });
738
- if (event?.event === "dashboard_sqlite_read_model") snapshotCache = event.cache;
739
- if (event?.event === "dashboard_sqlite_read_model")
740
- onProgress({
741
- percent: 82,
742
- phase: "Banco local pronto",
743
- detail:
744
- event.cache === "rebuilt"
745
- ? "O read model SQLite foi reconstruído para os dados atuais."
746
- : "Uma cópia local do read model foi preparada.",
747
- });
748
- if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "listening")
749
- onProgress({
750
- percent: 88,
751
- phase: "Iniciando a interface",
752
- detail: "Verificando saúde e disponibilidade dos dados.",
753
- });
754
- } catch {
755
- // Only structured startup telemetry affects the user-facing progress.
756
- }
757
- }
758
- });
759
- try {
760
- await waitForDashboard(port, child);
761
- const timings = stripTerminalControlCodes(stderr)
762
- .split("\n")
763
- .filter(
764
- (line) =>
765
- line.includes('"event":"dashboard_sqlite_read_model"') ||
766
- line.includes('"event":"dashboard_sqlite_startup_phase"'),
767
- );
768
- for (const timing of timings) appendLauncherLog(timing);
769
- return { child, port, dbPath, snapshotCache };
770
- } catch (error) {
771
- await stopProcessTree(child.pid);
772
- await removeGeneratedReadModel(dbPath);
773
- const detail = stripTerminalControlCodes(stderr)
774
- .split("\n")
775
- .filter(
776
- (line) =>
777
- !line.includes("ExperimentalWarning") && !line.includes("node --trace-warnings"),
778
- )
779
- .join("\n")
780
- .trim();
781
- failures.push(
782
- `porta ${port}: ${error.message}${detail ? ` — ${formatLauncherError(detail, 600)}` : ""}`,
783
- );
784
- appendLauncherLog(
785
- `Dashboard start attempt ${attempt} failed on port ${port}: ${error.message}`,
786
- );
787
- if (error.retryable === false) break;
788
- }
789
- }
790
- throw new Error(
791
- `Não foi possível iniciar o dashboard após os fallbacks. ${failures.join(" | ")}`,
792
- );
793
- }
794
-
795
- async function removeGeneratedReadModel(candidate) {
796
- if (typeof candidate !== "string") return false;
797
- const resolvedCandidate = resolve(candidate);
798
- const fromReadModelDirectory = relative(sqliteReadModelDirectory, resolvedCandidate);
799
- const generatedName = /^read-model-\d+-\d+-\d+\.sqlite$/;
800
- if (
801
- fromReadModelDirectory === "" ||
802
- fromReadModelDirectory.startsWith("..") ||
803
- fromReadModelDirectory.includes("/") ||
804
- fromReadModelDirectory.includes("\\") ||
805
- !generatedName.test(fromReadModelDirectory)
806
- )
807
- return false;
808
- for (let attempt = 1; attempt <= 20; attempt += 1) {
809
- try {
810
- await rm(resolvedCandidate, { force: true });
811
- return true;
812
- } catch (error) {
813
- if (error?.code !== "EBUSY" && error?.code !== "EPERM") throw error;
814
- if (attempt < 20) await delay(250);
815
- }
816
- }
817
- appendLauncherLog(
818
- `Could not remove closed SQLite read model after retries: ${resolvedCandidate}`,
819
- );
820
- return false;
821
- }
822
- async function refreshSnapshotSeedInBackground() {
823
- const refreshStatusPath = join(sessionDirectory, "dashboard-sqlite-refresh.json");
824
- const refreshLogPath = join(sessionDirectory, "dashboard-sqlite-refresh.log");
825
- return startSeedRefresh({
826
- appendLog: appendLauncherLog,
827
- cacheDirectory: sqliteSnapshotCacheDirectory,
828
- isRunning: backgroundRefreshIsRunning,
829
- isProcessAlive,
830
- lockPath: join(sessionDirectory, "dashboard-sqlite-refresh.lock"),
818
+ return launchDashboardServer({
819
+ serverPath,
820
+ token,
821
+ excludedPorts,
822
+ initialPort,
823
+ onProgress,
824
+ onChildStarted,
825
+ maxAttempts: maxServerStartAttempts,
826
+ dbPath: sqliteReadModelPath,
831
827
  packageRoot,
832
- randomBytes,
833
- refreshLogPath,
834
- refreshStatusPath,
835
- sqliteDirectory: sqliteReadModelDirectory,
836
- stopProcessTree,
837
828
  workspaceRoot,
838
- writeJsonAtomic,
829
+ frontendRoot,
830
+ compileCacheDirectory: nodeCompileCacheDirectory,
831
+ findPort: findAvailablePortWithFallback,
832
+ waitForDashboard,
833
+ stopProcessTree,
834
+ sanitize: stripTerminalControlCodes,
835
+ formatError: formatLauncherError,
836
+ appendLog: appendLauncherLog,
839
837
  });
840
838
  }
839
+
841
840
  async function main() {
842
841
  let ownedChild = null;
843
842
  let currentSession = null;
@@ -851,6 +850,25 @@ async function main() {
851
850
  // probe mistake its intentional 503 for a server failure.
852
851
  launchPage = await startDashboardLaunchPage(0);
853
852
  const launchPort = launchPage.port;
853
+ const launchClaim = await claimDashboardLaunch({ launchPort, token });
854
+ let browserClosed = false;
855
+ void launchPage.waitForBrowserDeparture().then(() => {
856
+ if (launchPage === null) return;
857
+ browserClosed = true;
858
+ if (ownedChild !== null) void stopProcessTree(ownedChild.pid);
859
+ });
860
+ const assertCurrentLaunch = async () => {
861
+ if (browserClosed) {
862
+ const closed = new Error("The dashboard browser was closed during preparation.");
863
+ closed.code = "ZAIP_DASHBOARD_BROWSER_CLOSED";
864
+ throw closed;
865
+ }
866
+ if (!(await isCurrentDashboardLaunch(launchClaim))) {
867
+ const replacement = new Error("Dashboard launch was superseded by a newer executable click.");
868
+ replacement.code = "ZAIP_DASHBOARD_SUPERSEDED";
869
+ throw replacement;
870
+ }
871
+ };
854
872
  try {
855
873
  if (!(await openBrowser(dashboardUrl(launchPort, token)))) {
856
874
  throw new Error("Could not open the dashboard preparation page in the default browser.");
@@ -862,6 +880,7 @@ async function main() {
862
880
  "Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
863
881
  });
864
882
  await assertDashboardPrerequisites(workspaceRoot);
883
+ await assertCurrentLaunch();
865
884
  launchPage.setProgress({
866
885
  percent: 18,
867
886
  phase: "Preparando a sessão do dashboard",
@@ -870,6 +889,7 @@ async function main() {
870
889
  let reportedLockWait = false;
871
890
  await withLauncherLock(
872
891
  async () => {
892
+ await assertCurrentLaunch();
873
893
  phaseTiming("lock_acquired", launchStartedAt);
874
894
  const buildStartedAt = Date.now();
875
895
  launchPage.setProgress({
@@ -880,42 +900,16 @@ async function main() {
880
900
  const buildSignature = await ensureFreshDashboardBuild((progress) =>
881
901
  launchPage?.setProgress(progress),
882
902
  );
903
+ await assertCurrentLaunch();
883
904
  phaseTiming("build_complete", buildStartedAt, { build_signature: buildSignature });
884
- // A second deliberate click must use the already healthy compatible
885
- // runtime. Build signature makes this safe across dashboard updates;
886
- // an incompatible session continues through a replacement handoff.
887
- const reusable = await findReusableDashboardSession({
888
- expectedBuildSignature: buildSignature,
889
- expectedSnapshotState: "current",
890
- });
891
- if (reusable !== null) {
892
- appendLauncherLog(
893
- `Reused healthy compatible dashboard session on port ${reusable.port}, PID ${reusable.serverPid ?? "unknown"}.`,
894
- );
895
- await redirectLaunchPage(launchPage, dashboardUrl(reusable.port, reusable.token));
896
- launchPage = null;
897
- await writeLauncherStatus("ok");
898
- return;
899
- }
900
- // A stale seed is still a valid read-only dashboard while its one
901
- // canonical refresh runs. Reuse it instead of turning repeated clicks
902
- // into competing full SQLite rebuilds and private server generations.
903
- if (await backgroundRefreshIsRunning()) {
904
- const updating = await findReusableDashboardSession({
905
- expectedBuildSignature: buildSignature,
906
- expectedSnapshotState: "updating",
907
- });
908
- if (updating !== null) {
909
- appendLauncherLog(
910
- `Reused updating dashboard session on port ${updating.port}; canonical seed refresh is already running.`,
911
- );
912
- await redirectLaunchPage(launchPage, dashboardUrl(updating.port, updating.token, true));
913
- launchPage = null;
914
- await writeLauncherStatus("ok");
915
- return;
916
- }
917
- }
918
905
  const previousSessions = await previousDashboardSessions();
906
+ // The current executable is authoritative. Retire every verified
907
+ // previous owner before allocating the new generation, never reuse it.
908
+ for (const previous of previousSessions) {
909
+ const status = await stopOwnedDashboard(previous);
910
+ await recordPortStatus(previous, status);
911
+ }
912
+ await assertCurrentLaunch();
919
913
  const excludedPorts = new Set([
920
914
  ...previousSessions.map((session) => session.port),
921
915
  launchPort,
@@ -925,7 +919,7 @@ async function main() {
925
919
  launchPage.setProgress({
926
920
  percent: 42,
927
921
  phase: "Abrindo o banco local descartável",
928
- detail: "Criando uma cópia privada do read model SQLite para esta sessão.",
922
+ detail: "Abrindo o banco SQLite persistente deste workspace.",
929
923
  });
930
924
  const { child, port, dbPath, snapshotCache } = await startDashboardServer(
931
925
  serverPath,
@@ -933,6 +927,9 @@ async function main() {
933
927
  excludedPorts,
934
928
  null,
935
929
  (progress) => launchPage?.setProgress(progress),
930
+ (child) => {
931
+ ownedChild = child;
932
+ },
936
933
  );
937
934
  phaseTiming("server_ready", readyStartedAt, {
938
935
  server_pid: child.pid,
@@ -949,7 +946,7 @@ async function main() {
949
946
  : "Dashboard pronto",
950
947
  detail:
951
948
  snapshotCache === "stale_seed"
952
- ? "Os dados canônicos estão sendo atualizados em segundo plano para a próxima abertura."
949
+ ? "Os dados canônicos estão sendo atualizados em segundo plano nesta sessão."
953
950
  : "Validando saúde e dados iniciais antes de abrir a tela principal.",
954
951
  });
955
952
  if (snapshotCache === "stale_seed") await delay(350);
@@ -957,9 +954,9 @@ async function main() {
957
954
  launchPage,
958
955
  dashboardUrl(port, token, snapshotCache === "stale_seed"),
959
956
  );
957
+ await assertCurrentLaunch();
960
958
  phaseTiming("handoff_complete", launchStartedAt, { server_pid: child.pid, port });
961
959
  launchPage = null;
962
- if (snapshotCache === "stale_seed") await refreshSnapshotSeedInBackground();
963
960
  currentSession = {
964
961
  kind: runtimeKind,
965
962
  workspace: workspaceRoot,
@@ -971,19 +968,20 @@ async function main() {
971
968
  dbPath,
972
969
  buildSignature,
973
970
  generationId: `${process.pid}-${child.pid}-${port}-${randomBytes(6).toString("hex")}`,
974
- // Never pin subsequent clicks to a stale seed. A completed
975
- // background refresh is consumed by a fresh handoff on the next
976
- // click, then the old updating generation is retired below.
971
+ // The serving process owns the stale-seed refresh and promotes it
972
+ // into this session, so a later click can reuse the current
973
+ // generation after the health state flips.
977
974
  snapshotState: snapshotCache === "stale_seed" ? "updating" : "current",
978
975
  createdAt: new Date().toISOString(),
979
976
  };
977
+ await assertCurrentLaunch();
980
978
  await writeDashboardSession(currentSession);
979
+ await writeJsonAtomic(launchPath, { ...launchClaim, state: "ready", port });
981
980
  await recordPortStatus(currentSession, "active");
982
981
  const warnings = [];
983
982
  for (const previous of previousSessions) {
984
983
  const status = await stopOwnedDashboard(previous);
985
984
  await recordPortStatus(previous, status);
986
- if (status === "closed") await removeGeneratedReadModel(previous.dbPath);
987
985
  if (teardownWarning(status))
988
986
  warnings.push(`não foi possível fechar a porta anterior ${previous.port}`);
989
987
  else if (status === "skipped_unowned")
@@ -1004,6 +1002,7 @@ async function main() {
1004
1002
  }
1005
1003
  },
1006
1004
  {
1005
+ newestClickWins: true,
1007
1006
  onWait: () => {
1008
1007
  if (reportedLockWait) return;
1009
1008
  reportedLockWait = true;
@@ -1016,6 +1015,13 @@ async function main() {
1016
1015
  },
1017
1016
  );
1018
1017
  } catch (error) {
1018
+ if (
1019
+ error?.code === "ZAIP_DASHBOARD_SUPERSEDED" ||
1020
+ error?.code === "ZAIP_DASHBOARD_BROWSER_CLOSED"
1021
+ ) {
1022
+ await writeLauncherStatus("cancelled").catch(() => {});
1023
+ return;
1024
+ }
1019
1025
  launchPage = presentLaunchFailure(launchPage, error, formatLauncherError);
1020
1026
  throw error;
1021
1027
  }
@@ -1025,16 +1031,8 @@ async function main() {
1025
1031
  currentSession,
1026
1032
  clearOwnedDashboardSession,
1027
1033
  recordPortStatus,
1028
- removeGeneratedReadModel,
1029
- });
1030
- }
1031
- function isPortFree(port) {
1032
- return new Promise((resolvePort) => {
1033
- const server = createServer();
1034
- server.unref();
1035
- server.once("error", () => resolvePort(false));
1036
- server.listen({ host, port }, () => server.close(() => resolvePort(true)));
1037
1034
  });
1035
+ await clearDashboardLaunch(launchClaim);
1038
1036
  }
1039
1037
  async function waitForDashboard(port, child) {
1040
1038
  // A cold SQLite read model has no meaningful fixed duration: canonical
@@ -1075,12 +1073,6 @@ async function waitForDashboard(port, child) {
1075
1073
  await delay(100);
1076
1074
  }
1077
1075
  }
1078
- function exists(path) {
1079
- return access(path).then(
1080
- () => true,
1081
- () => false,
1082
- );
1083
- }
1084
1076
  function delay(milliseconds) {
1085
1077
  return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
1086
1078
  }