@zaip-ai/cli 1.3.8 → 1.4.2

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 (317) 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 +208 -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-runtime-selection.mjs +36 -0
  181. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-runtime-selection.test.mjs +63 -0
  182. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
  183. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +262 -267
  184. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
  185. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
  186. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  187. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  188. package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
  189. package/templates/.agents/requirements/comparative-audit.md +1 -1
  190. package/templates/.agents/requirements/context-engine.md +1 -1
  191. package/templates/.agents/requirements/cost-observability.md +1 -1
  192. package/templates/.agents/requirements/dashboard-launch.md +2 -2
  193. package/templates/.agents/requirements/data-export.md +1 -1
  194. package/templates/.agents/requirements/design-system.md +1 -1
  195. package/templates/.agents/requirements/execution-engine.md +1 -1
  196. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  197. package/templates/.agents/requirements/model-orchestration.md +1 -1
  198. package/templates/.agents/requirements/public-distribution.md +1 -1
  199. package/templates/.agents/requirements/software-architecture.md +1 -1
  200. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  201. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  202. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  203. package/templates/.agents/rules/autonomy-decision.md +1 -1
  204. package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
  205. package/templates/.agents/rules/core.md +5 -4
  206. package/templates/.agents/rules/local-sqlite-storage.md +18 -0
  207. package/templates/.agents/rules/meta-mcp.md +21 -0
  208. package/templates/.agents/rules/workflow.md +1 -0
  209. package/templates/.agents/runtime/bin/zaip.mjs +305 -137
  210. package/templates/.agents/runtime/checksums.sha256 +2 -2
  211. package/templates/.agents/runtime/provenance.json +2 -2
  212. package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
  213. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
  214. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
  215. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
  216. package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
  217. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
  218. package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
  219. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
  220. package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
  221. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
  222. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
  223. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
  224. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
  225. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
  226. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
  227. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
  228. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
  229. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
  230. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
  231. package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
  232. package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
  233. package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
  234. package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
  235. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
  236. package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
  237. package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
  238. package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
  239. package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
  240. package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
  241. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
  242. package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
  243. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
  244. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
  245. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
  246. package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
  247. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
  248. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
  249. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
  250. package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
  251. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
  252. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
  253. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
  254. package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
  255. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
  256. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
  257. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
  258. package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
  259. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
  260. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
  261. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
  262. package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
  263. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
  264. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
  265. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
  266. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
  267. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
  268. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
  269. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
  270. package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
  271. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
  272. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
  273. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
  274. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
  275. package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
  276. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
  277. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
  278. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
  279. package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
  280. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
  281. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
  282. package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
  283. package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
  284. package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
  285. package/templates/.agents/tools/catalog.yaml +6 -0
  286. package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
  287. package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
  288. package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
  289. package/templates/.agents/tools/cloud-runner.mjs +786 -0
  290. package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
  291. package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
  292. package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
  293. package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
  294. package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
  295. package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
  296. package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
  297. package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
  298. package/templates/.agents/tools/profiles/development.yaml +3 -0
  299. package/templates/infra/terraform/.terraform.lock.hcl +42 -0
  300. package/templates/infra/terraform/README.md +31 -0
  301. package/templates/infra/terraform/backend.tf.json +13 -0
  302. package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
  303. package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
  304. package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
  305. package/templates/infra/terraform/main.tf +77 -0
  306. package/templates/infra/terraform/outputs.tf +20 -0
  307. package/templates/infra/terraform/terraform.tfvars.example +26 -0
  308. package/templates/infra/terraform/variables.tf +91 -0
  309. package/templates/infra/terraform/versions.tf +18 -0
  310. package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
  311. package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
  312. package/templates/template-manifest.json +607 -167
  313. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.js +0 -3
  314. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
  315. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Dyo_wpL4.js +0 -2
  316. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
  317. 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,23 @@ 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
+ assertInstalledDashboardArtifacts,
27
+ isDashboardSourceWorkspace as hasDashboardSourceWorkspace,
28
+ dashboardServerPathForWorkspace as selectDashboardServerPath,
29
+ } from "./start-dashboard-runtime-selection.mjs";
30
+ import {
31
+ isPortFree,
32
+ startDashboardServer as launchDashboardServer,
33
+ } from "./start-dashboard-server.mjs";
36
34
 
37
35
  export {
38
36
  isAncestorProcess,
@@ -53,15 +51,22 @@ export const workspaceKey = createHash("sha256")
53
51
  .slice(0, 16);
54
52
  export const sessionDirectory = join(tmpdir(), "zaip-aios", `dashboard-sqlite-${workspaceKey}`);
55
53
  export const sessionPath = join(sessionDirectory, "dashboard-sqlite-session.json");
54
+ // This small, token-free record is the observable ownership contract while a
55
+ // dashboard is still preparing. It deliberately does not replace the active
56
+ // runtime session, so a newer click can take priority without losing the PID
57
+ // needed to stop the older runtime.
58
+ export const launchPath = join(sessionDirectory, "dashboard-sqlite-launch.json");
59
+ const claimLockPath = join(sessionDirectory, "dashboard-sqlite-claim.lock");
60
+ const claimLockTimeoutMs = 5_000;
61
+ const claimLockStaleMs = 30_000;
56
62
  export const portRegistryPath = join(sessionDirectory, "dashboard-sqlite-ports.json");
57
63
  export const lockPath = join(sessionDirectory, "dashboard-sqlite-launcher.lock");
58
64
  export const launcherLogPath = join(sessionDirectory, "dashboard-sqlite-launcher.log");
59
65
  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");
66
+ const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "sqlite");
67
+ const sqliteReadModelPath = join(sqliteReadModelDirectory, "zaip-aios-dashboard.sqlite");
68
+ // Bytecode is disposable session data, separate from the persistent database.
69
+ const nodeCompileCacheDirectory = join(sessionDirectory, "node-compile-cache");
65
70
  const installedServer = resolve(packageRoot, "server.mjs");
66
71
  const sourceServer = resolve(packageRoot, "dist/server.cjs");
67
72
  const healthPath = "/api/v2/health";
@@ -183,6 +188,103 @@ export async function readDashboardSession(path = sessionPath) {
183
188
  export async function writeDashboardSession(session, path = sessionPath) {
184
189
  await writeJsonAtomic(path, session);
185
190
  }
191
+ export async function claimDashboardLaunch(
192
+ { launchPort, token },
193
+ {
194
+ path = launchPath,
195
+ lockPath = claimLockPath,
196
+ stopProcessTreeImpl = stopProcessTree,
197
+ isProcessAliveImpl = isProcessAlive,
198
+ isKnownDashboardLauncherImpl = isKnownDashboardLauncher,
199
+ delayImpl = delay,
200
+ } = {},
201
+ ) {
202
+ await mkdir(sessionDirectory, { recursive: true });
203
+ // This lock is deliberately short: it serializes only ownership transfer,
204
+ // never the SQLite build. A later click therefore observes and cancels the
205
+ // immediately preceding claim rather than a stale common predecessor.
206
+ const claimDeadline = Date.now() + claimLockTimeoutMs;
207
+ while (true) {
208
+ try {
209
+ await mkdir(lockPath);
210
+ await writeFile(join(lockPath, "owner"), String(process.pid), "utf8");
211
+ break;
212
+ } catch (error) {
213
+ if (error?.code !== "EEXIST") throw error;
214
+ const details = await stat(lockPath).catch(() => null);
215
+ const ownerPid = Number.parseInt(
216
+ await readFile(join(lockPath, "owner"), "utf8").catch(() => ""),
217
+ 10,
218
+ );
219
+ if (Number.isInteger(ownerPid) && ownerPid > 0 && !(await isProcessAliveImpl(ownerPid))) {
220
+ await rm(lockPath, { recursive: true, force: true });
221
+ continue;
222
+ }
223
+ if (details !== null && Date.now() - details.mtimeMs > claimLockStaleMs) {
224
+ await rm(lockPath, { recursive: true, force: true });
225
+ continue;
226
+ }
227
+ if (Date.now() >= claimDeadline)
228
+ throw new Error("Timed out waiting for the dashboard ownership claim lock.");
229
+ await delayImpl(25);
230
+ }
231
+ }
232
+ try {
233
+ const previous = await readDashboardSession(path);
234
+ const claim = {
235
+ kind: "zaip-dashboard-sqlite-launch",
236
+ workspace: workspaceRoot,
237
+ workspaceKey,
238
+ launcherPid: process.pid,
239
+ launchPort,
240
+ // Only a non-secret correlation id is persisted for diagnostics.
241
+ claimId: createHash("sha256").update(`${token}:${process.pid}`).digest("hex").slice(0, 16),
242
+ state: "preparing",
243
+ claimedAt: new Date().toISOString(),
244
+ statusPath: process.env.ZAIP_LAUNCH_STATUS ?? null,
245
+ };
246
+ await writeJsonAtomic(path, claim);
247
+ if (
248
+ previous?.kind === claim.kind &&
249
+ previous?.workspaceKey === workspaceKey &&
250
+ Number.isInteger(previous?.launcherPid) &&
251
+ previous.launcherPid > 0 &&
252
+ previous.launcherPid !== process.pid
253
+ ) {
254
+ // A claim replacement is also an active cancellation boundary. Do not
255
+ // merely fence stale cleanup while its expensive SQLite build continues.
256
+ if (!(await isKnownDashboardLauncherImpl(previous.launcherPid, packageRoot))) {
257
+ // The PID may have exited or been reused by an unrelated process. Its
258
+ // record is stale; never kill it and let this claim continue.
259
+ return claim;
260
+ }
261
+ if (typeof previous.statusPath === "string" && previous.statusPath.startsWith(tmpdir()))
262
+ await writeFile(
263
+ previous.statusPath,
264
+ "cancelled\nSuperseded by a newer dashboard click.",
265
+ "utf8",
266
+ ).catch(() => {});
267
+ await stopProcessTreeImpl(previous.launcherPid);
268
+ const deadline = Date.now() + 5_000;
269
+ while ((await isProcessAliveImpl(previous.launcherPid)) && Date.now() < deadline)
270
+ await delayImpl(50);
271
+ if (await isProcessAliveImpl(previous.launcherPid))
272
+ throw new Error(
273
+ "Could not stop the superseded dashboard launcher before starting a new session.",
274
+ );
275
+ }
276
+ return claim;
277
+ } finally {
278
+ await rm(lockPath, { recursive: true, force: true });
279
+ }
280
+ }
281
+ export async function isCurrentDashboardLaunch(claim) {
282
+ const current = await readDashboardSession(launchPath);
283
+ return current?.kind === claim.kind && current?.claimId === claim.claimId;
284
+ }
285
+ export async function clearDashboardLaunch(claim) {
286
+ if (await isCurrentDashboardLaunch(claim)) await rm(launchPath, { force: true });
287
+ }
186
288
  async function writeJsonAtomic(path, value) {
187
289
  const directory = dirname(path);
188
290
  await mkdir(directory, { recursive: true });
@@ -269,11 +371,31 @@ export async function findReusableDashboardSession({
269
371
  session?.workspaceKey !== expectedWorkspaceKey ||
270
372
  typeof session?.token !== "string" ||
271
373
  session.token.length === 0 ||
272
- (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature) ||
273
- (expectedSnapshotState !== null && session?.snapshotState !== expectedSnapshotState)
374
+ (expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature)
274
375
  ) {
275
376
  return null;
276
377
  }
378
+ // Session metadata is written before the serving process finishes promoting
379
+ // a stale seed. Trust a state transition only after the owned server itself
380
+ // reports it; never reuse an unknown or still-updating generation as current.
381
+ if (expectedSnapshotState !== null && session.snapshotState !== expectedSnapshotState) {
382
+ if (!Number.isInteger(session.port)) return null;
383
+ try {
384
+ const response = await fetchImpl(dashboardHealthUrl(session.port), {
385
+ signal: AbortSignal.timeout(500),
386
+ });
387
+ const payload = await response.json();
388
+ if (
389
+ !response.ok ||
390
+ payload?.kind !== runtimeKind ||
391
+ payload?.workspaceKey !== expectedWorkspaceKey ||
392
+ payload?.snapshot_state !== expectedSnapshotState
393
+ )
394
+ return null;
395
+ } catch {
396
+ return null;
397
+ }
398
+ }
277
399
  if (
278
400
  Number.isInteger(session.port) &&
279
401
  (await probeDashboardPort(session.port, fetchImpl, expectedWorkspaceKey))
@@ -292,9 +414,14 @@ export async function findReusableDashboardSession({
292
414
  return port === undefined ? null : { ...session, port };
293
415
  }
294
416
 
295
- async function backgroundRefreshIsRunning() {
296
- return refreshIsRunning(join(sessionDirectory, "dashboard-sqlite-refresh.json"), isProcessAlive);
417
+ export function requestBackgroundProjectionRefresh(session, fetchImpl = fetch) {
418
+ return fetchImpl(`http://${host}:${session.port}/api/v2/refresh-projection`, {
419
+ method: "POST",
420
+ headers: { "x-zaip-session": session.token },
421
+ signal: AbortSignal.timeout(750),
422
+ }).catch(() => undefined);
297
423
  }
424
+
298
425
  export async function latestModifiedAt(paths, excludedSourcePaths = []) {
299
426
  let latest = 0;
300
427
  for (const path of paths) {
@@ -435,31 +562,22 @@ export async function refreshDashboardBuildTargets({
435
562
  return { artifacts: Object.values(targets).flatMap((target) => target.artifacts), builtTargets };
436
563
  }
437
564
  async function ensureFreshDashboardBuild(onProgress = () => {}) {
438
- // npm consumers receive a ready-to-run server and frontend bundle. They do
439
- // not receive development-only esbuild, so never turn the first dashboard
440
- // click into a pnpm-install requirement when that packaged runtime exists.
441
- const buildTool = resolve(workspaceRoot, "node_modules/esbuild");
442
- if (!(await exists(buildTool))) {
565
+ if (!(await hasDashboardSourceWorkspace(workspaceRoot))) {
443
566
  const packagedArtifacts = [installedServer, resolve(frontendRoot, "index.html")];
444
- if (
445
- await Promise.all(packagedArtifacts.map((path) => exists(path))).then((entries) =>
446
- entries.every(Boolean),
447
- )
448
- ) {
449
- buildProgress(
450
- onProgress,
451
- 39,
452
- "Usando runtime empacotado",
453
- "O pacote npm já inclui o dashboard pronto; nenhuma instalação de desenvolvimento é necessária.",
454
- );
455
- const signature = await Promise.all(
456
- [...packagedArtifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
457
- const details = await stat(path);
458
- return `${path}:${details.size}:${details.mtimeMs}`;
459
- }),
460
- );
461
- return createHash("sha256").update(signature.join("|")).digest("hex");
462
- }
567
+ await assertInstalledDashboardArtifacts(packagedArtifacts);
568
+ buildProgress(
569
+ onProgress,
570
+ 39,
571
+ "Usando runtime empacotado",
572
+ "O pacote npm já inclui o dashboard pronto; nenhuma instalação de desenvolvimento é necessária.",
573
+ );
574
+ const signature = await Promise.all(
575
+ [...packagedArtifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
576
+ const details = await stat(path);
577
+ return `${path}:${details.size}:${details.mtimeMs}`;
578
+ }),
579
+ );
580
+ return createHash("sha256").update(signature.join("|")).digest("hex");
463
581
  }
464
582
  const { artifacts } = await refreshDashboardBuildTargets({ onProgress });
465
583
  const artifactFingerprint = await Promise.all(
@@ -470,6 +588,16 @@ async function ensureFreshDashboardBuild(onProgress = () => {}) {
470
588
  );
471
589
  return createHash("sha256").update(artifactFingerprint.join("|")).digest("hex");
472
590
  }
591
+ export async function isDashboardSourceWorkspace(root = workspaceRoot) {
592
+ return hasDashboardSourceWorkspace(root);
593
+ }
594
+ export async function dashboardServerPathForWorkspace({
595
+ root = workspaceRoot,
596
+ sourcePath = sourceServer,
597
+ installedPath = installedServer,
598
+ } = {}) {
599
+ return selectDashboardServerPath({ root, sourcePath, installedPath });
600
+ }
473
601
  export async function withLauncherLock(
474
602
  action,
475
603
  {
@@ -478,6 +606,7 @@ export async function withLauncherLock(
478
606
  timeoutMs = lockTimeoutMs,
479
607
  staleAfterMs = staleLockMs,
480
608
  onWait = () => {},
609
+ newestClickWins = false,
481
610
  } = {},
482
611
  ) {
483
612
  await mkdir(directory, { recursive: true });
@@ -485,6 +614,9 @@ export async function withLauncherLock(
485
614
  while (Date.now() < deadline) {
486
615
  try {
487
616
  await mkdir(path);
617
+ const lockOwner = `${process.pid}-${randomBytes(8).toString("hex")}`;
618
+ const lockOwnerPath = join(path, "owner");
619
+ await writeFile(lockOwnerPath, lockOwner, { encoding: "utf8", mode: 0o600 });
488
620
  const heartbeatIntervalMs = Math.max(lockHeartbeatFloorMs, Math.floor(staleAfterMs / 3));
489
621
  const heartbeat = setInterval(() => {
490
622
  const now = new Date();
@@ -495,10 +627,20 @@ export async function withLauncherLock(
495
627
  return await action();
496
628
  } finally {
497
629
  clearInterval(heartbeat);
498
- await rm(path, { recursive: true, force: true });
630
+ // Fencing prevents a superseded owner from deleting the newer click's
631
+ // lock directory in its finally block.
632
+ if ((await readFile(lockOwnerPath, "utf8").catch(() => "")) === lockOwner)
633
+ await rm(path, { recursive: true, force: true });
499
634
  }
500
635
  } catch (error) {
501
636
  if (error?.code !== "EEXIST") throw error;
637
+ if (newestClickWins) {
638
+ // The previous holder checks its launch claim at every expensive
639
+ // boundary. Replacing the directory lets the latest executable click
640
+ // proceed immediately instead of presenting a waiting screen.
641
+ await rm(path, { recursive: true, force: true });
642
+ continue;
643
+ }
502
644
  const lockStat = await stat(path).catch(() => null);
503
645
  if (lockStat !== null && Date.now() - lockStat.mtimeMs > staleAfterMs) {
504
646
  await rm(path, { recursive: true, force: true });
@@ -512,7 +654,7 @@ export async function withLauncherLock(
512
654
  }
513
655
  export async function findAvailablePort(startPort = preferredPort()) {
514
656
  for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
515
- if (await isPortFree(port)) return port;
657
+ if (await isPortFree(host, port)) return port;
516
658
  }
517
659
  throw new Error(
518
660
  `No free dashboard port found between ${startPort} and ${Math.min(startPort + maxPortAttempts - 1, 65_535)}`,
@@ -528,7 +670,7 @@ export async function findAvailablePortWithFallback({
528
670
  );
529
671
  for (const startPort of fallbackStarts) {
530
672
  for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
531
- if (!excluded.has(port) && (await isPortFree(port))) return port;
673
+ if (!excluded.has(port) && (await isPortFree(host, port))) return port;
532
674
  }
533
675
  }
534
676
  for (let attempt = 0; attempt < 5; attempt += 1) {
@@ -630,10 +772,7 @@ async function redirectLaunchPage(launchPage, target) {
630
772
  launchPage.closeAfterHandoffGrace();
631
773
  }
632
774
  async function assertRuntimeReady() {
633
- const serverPath =
634
- (await exists(resolve(workspaceRoot, "node_modules/esbuild"))) && (await exists(sourceServer))
635
- ? sourceServer
636
- : installedServer;
775
+ const serverPath = await dashboardServerPathForWorkspace();
637
776
  if (!(await exists(serverPath))) {
638
777
  throw new Error(
639
778
  "Dashboard server bundle is missing; run the ZAIP release build or reinstall ZAIP-AIOS",
@@ -677,167 +816,30 @@ async function startDashboardServer(
677
816
  excludedPorts,
678
817
  initialPort = null,
679
818
  onProgress = () => {},
819
+ onChildStarted = () => {},
680
820
  ) {
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"),
821
+ return launchDashboardServer({
822
+ serverPath,
823
+ token,
824
+ excludedPorts,
825
+ initialPort,
826
+ onProgress,
827
+ onChildStarted,
828
+ maxAttempts: maxServerStartAttempts,
829
+ dbPath: sqliteReadModelPath,
831
830
  packageRoot,
832
- randomBytes,
833
- refreshLogPath,
834
- refreshStatusPath,
835
- sqliteDirectory: sqliteReadModelDirectory,
836
- stopProcessTree,
837
831
  workspaceRoot,
838
- writeJsonAtomic,
832
+ frontendRoot,
833
+ compileCacheDirectory: nodeCompileCacheDirectory,
834
+ findPort: findAvailablePortWithFallback,
835
+ waitForDashboard,
836
+ stopProcessTree,
837
+ sanitize: stripTerminalControlCodes,
838
+ formatError: formatLauncherError,
839
+ appendLog: appendLauncherLog,
839
840
  });
840
841
  }
842
+
841
843
  async function main() {
842
844
  let ownedChild = null;
843
845
  let currentSession = null;
@@ -851,6 +853,25 @@ async function main() {
851
853
  // probe mistake its intentional 503 for a server failure.
852
854
  launchPage = await startDashboardLaunchPage(0);
853
855
  const launchPort = launchPage.port;
856
+ const launchClaim = await claimDashboardLaunch({ launchPort, token });
857
+ let browserClosed = false;
858
+ void launchPage.waitForBrowserDeparture().then(() => {
859
+ if (launchPage === null) return;
860
+ browserClosed = true;
861
+ if (ownedChild !== null) void stopProcessTree(ownedChild.pid);
862
+ });
863
+ const assertCurrentLaunch = async () => {
864
+ if (browserClosed) {
865
+ const closed = new Error("The dashboard browser was closed during preparation.");
866
+ closed.code = "ZAIP_DASHBOARD_BROWSER_CLOSED";
867
+ throw closed;
868
+ }
869
+ if (!(await isCurrentDashboardLaunch(launchClaim))) {
870
+ const replacement = new Error("Dashboard launch was superseded by a newer executable click.");
871
+ replacement.code = "ZAIP_DASHBOARD_SUPERSEDED";
872
+ throw replacement;
873
+ }
874
+ };
854
875
  try {
855
876
  if (!(await openBrowser(dashboardUrl(launchPort, token)))) {
856
877
  throw new Error("Could not open the dashboard preparation page in the default browser.");
@@ -862,6 +883,7 @@ async function main() {
862
883
  "Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
863
884
  });
864
885
  await assertDashboardPrerequisites(workspaceRoot);
886
+ await assertCurrentLaunch();
865
887
  launchPage.setProgress({
866
888
  percent: 18,
867
889
  phase: "Preparando a sessão do dashboard",
@@ -870,6 +892,7 @@ async function main() {
870
892
  let reportedLockWait = false;
871
893
  await withLauncherLock(
872
894
  async () => {
895
+ await assertCurrentLaunch();
873
896
  phaseTiming("lock_acquired", launchStartedAt);
874
897
  const buildStartedAt = Date.now();
875
898
  launchPage.setProgress({
@@ -880,42 +903,16 @@ async function main() {
880
903
  const buildSignature = await ensureFreshDashboardBuild((progress) =>
881
904
  launchPage?.setProgress(progress),
882
905
  );
906
+ await assertCurrentLaunch();
883
907
  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
908
  const previousSessions = await previousDashboardSessions();
909
+ // The current executable is authoritative. Retire every verified
910
+ // previous owner before allocating the new generation, never reuse it.
911
+ for (const previous of previousSessions) {
912
+ const status = await stopOwnedDashboard(previous);
913
+ await recordPortStatus(previous, status);
914
+ }
915
+ await assertCurrentLaunch();
919
916
  const excludedPorts = new Set([
920
917
  ...previousSessions.map((session) => session.port),
921
918
  launchPort,
@@ -925,7 +922,7 @@ async function main() {
925
922
  launchPage.setProgress({
926
923
  percent: 42,
927
924
  phase: "Abrindo o banco local descartável",
928
- detail: "Criando uma cópia privada do read model SQLite para esta sessão.",
925
+ detail: "Abrindo o banco SQLite persistente deste workspace.",
929
926
  });
930
927
  const { child, port, dbPath, snapshotCache } = await startDashboardServer(
931
928
  serverPath,
@@ -933,6 +930,9 @@ async function main() {
933
930
  excludedPorts,
934
931
  null,
935
932
  (progress) => launchPage?.setProgress(progress),
933
+ (child) => {
934
+ ownedChild = child;
935
+ },
936
936
  );
937
937
  phaseTiming("server_ready", readyStartedAt, {
938
938
  server_pid: child.pid,
@@ -949,7 +949,7 @@ async function main() {
949
949
  : "Dashboard pronto",
950
950
  detail:
951
951
  snapshotCache === "stale_seed"
952
- ? "Os dados canônicos estão sendo atualizados em segundo plano para a próxima abertura."
952
+ ? "Os dados canônicos estão sendo atualizados em segundo plano nesta sessão."
953
953
  : "Validando saúde e dados iniciais antes de abrir a tela principal.",
954
954
  });
955
955
  if (snapshotCache === "stale_seed") await delay(350);
@@ -957,9 +957,9 @@ async function main() {
957
957
  launchPage,
958
958
  dashboardUrl(port, token, snapshotCache === "stale_seed"),
959
959
  );
960
+ await assertCurrentLaunch();
960
961
  phaseTiming("handoff_complete", launchStartedAt, { server_pid: child.pid, port });
961
962
  launchPage = null;
962
- if (snapshotCache === "stale_seed") await refreshSnapshotSeedInBackground();
963
963
  currentSession = {
964
964
  kind: runtimeKind,
965
965
  workspace: workspaceRoot,
@@ -971,19 +971,20 @@ async function main() {
971
971
  dbPath,
972
972
  buildSignature,
973
973
  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.
974
+ // The serving process owns the stale-seed refresh and promotes it
975
+ // into this session, so a later click can reuse the current
976
+ // generation after the health state flips.
977
977
  snapshotState: snapshotCache === "stale_seed" ? "updating" : "current",
978
978
  createdAt: new Date().toISOString(),
979
979
  };
980
+ await assertCurrentLaunch();
980
981
  await writeDashboardSession(currentSession);
982
+ await writeJsonAtomic(launchPath, { ...launchClaim, state: "ready", port });
981
983
  await recordPortStatus(currentSession, "active");
982
984
  const warnings = [];
983
985
  for (const previous of previousSessions) {
984
986
  const status = await stopOwnedDashboard(previous);
985
987
  await recordPortStatus(previous, status);
986
- if (status === "closed") await removeGeneratedReadModel(previous.dbPath);
987
988
  if (teardownWarning(status))
988
989
  warnings.push(`não foi possível fechar a porta anterior ${previous.port}`);
989
990
  else if (status === "skipped_unowned")
@@ -1004,6 +1005,7 @@ async function main() {
1004
1005
  }
1005
1006
  },
1006
1007
  {
1008
+ newestClickWins: true,
1007
1009
  onWait: () => {
1008
1010
  if (reportedLockWait) return;
1009
1011
  reportedLockWait = true;
@@ -1016,6 +1018,13 @@ async function main() {
1016
1018
  },
1017
1019
  );
1018
1020
  } catch (error) {
1021
+ if (
1022
+ error?.code === "ZAIP_DASHBOARD_SUPERSEDED" ||
1023
+ error?.code === "ZAIP_DASHBOARD_BROWSER_CLOSED"
1024
+ ) {
1025
+ await writeLauncherStatus("cancelled").catch(() => {});
1026
+ return;
1027
+ }
1019
1028
  launchPage = presentLaunchFailure(launchPage, error, formatLauncherError);
1020
1029
  throw error;
1021
1030
  }
@@ -1025,16 +1034,8 @@ async function main() {
1025
1034
  currentSession,
1026
1035
  clearOwnedDashboardSession,
1027
1036
  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
1037
  });
1038
+ await clearDashboardLaunch(launchClaim);
1038
1039
  }
1039
1040
  async function waitForDashboard(port, child) {
1040
1041
  // A cold SQLite read model has no meaningful fixed duration: canonical
@@ -1075,12 +1076,6 @@ async function waitForDashboard(port, child) {
1075
1076
  await delay(100);
1076
1077
  }
1077
1078
  }
1078
- function exists(path) {
1079
- return access(path).then(
1080
- () => true,
1081
- () => false,
1082
- );
1083
- }
1084
1079
  function delay(milliseconds) {
1085
1080
  return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
1086
1081
  }