@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
@@ -0,0 +1,68 @@
1
+ import type { WorkspaceFileSystem } from "@zaip-ai/core";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { reconcileOpenWork, reconciliationResult } from "./server-workspace.js";
4
+
5
+ describe("manual reconciliation report", () => {
6
+ it("reports delivered SPECs and PRDs even when no Task or Run changed", () => {
7
+ expect(
8
+ reconciliationResult({
9
+ reconciliations: [
10
+ {
11
+ status: "closed",
12
+ specs: [{ id: "SPEC-DONE", status: "delivered" }],
13
+ prds: [{ id: "PRD-DONE", status: "delivered" }],
14
+ },
15
+ ],
16
+ }),
17
+ ).toEqual({ reconciled_ids: ["PRD-DONE", "SPEC-DONE"], blocked: [], not_reconciled: [] });
18
+ });
19
+
20
+ it("reports the canonical owners of an orphan with their persisted blocking reasons", () => {
21
+ expect(
22
+ reconciliationResult({
23
+ orphaned_executions: ["EXEC-ORPHAN"],
24
+ orphaned_execution_entities: [
25
+ {
26
+ id: "EXEC-ORPHAN",
27
+ status: "blocked",
28
+ status_reason: "Host terminated without delivery evidence.",
29
+ },
30
+ ],
31
+ orphaned_owner_entities: [
32
+ { id: "TASK-ORPHAN", status: "blocked", status_reason: "Host interrupted." },
33
+ { id: "SPEC-ORPHAN", status: "blocked", status_reason: "Child attempt stopped." },
34
+ { id: "PRD-ORPHAN", status: "blocked", status_reason: "Child attempt stopped." },
35
+ ],
36
+ unresolved_running_run_ids: ["RUN-LIVE"],
37
+ }),
38
+ ).toEqual({
39
+ reconciled_ids: ["EXEC-ORPHAN", "PRD-ORPHAN", "SPEC-ORPHAN", "TASK-ORPHAN"],
40
+ blocked: [
41
+ { id: "EXEC-ORPHAN", reason: "Host terminated without delivery evidence." },
42
+ { id: "PRD-ORPHAN", reason: "Child attempt stopped." },
43
+ { id: "SPEC-ORPHAN", reason: "Child attempt stopped." },
44
+ { id: "TASK-ORPHAN", reason: "Host interrupted." },
45
+ ],
46
+ not_reconciled: [{ id: "RUN-LIVE", reason: "Invocation is still running." }],
47
+ });
48
+ });
49
+
50
+ it("does not advertise untouched draft, ready or active entities as reconciled", () => {
51
+ expect(
52
+ reconciliationResult({
53
+ orphaned_owner_entities: [
54
+ { id: "PRD-DRAFT", status: "draft" },
55
+ { id: "SPEC-READY", status: "ready" },
56
+ { id: "TASK-LIVE", status: "executing" },
57
+ ],
58
+ }),
59
+ ).toEqual({ reconciled_ids: [], blocked: [], not_reconciled: [] });
60
+ });
61
+
62
+ it("reports unavailable reconciliation instead of returning a successful empty result", async () => {
63
+ const filesystem = { exists: vi.fn(async () => false) } as unknown as WorkspaceFileSystem;
64
+ await expect(reconcileOpenWork(filesystem, "test-workspace")).rejects.toThrow(
65
+ "could not be reconciled",
66
+ );
67
+ });
68
+ });
@@ -0,0 +1,19 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { describe, expect, it } from "vitest";
3
+ import { attachRefreshWorkerCleanup } from "./server-snapshot-refresh.js";
4
+
5
+ describe("snapshot refresh shutdown", () => {
6
+ it("terminates an active refresh worker on runtime shutdown and removes its listener on completion", () => {
7
+ const processRef = new EventEmitter();
8
+ const calls: string[] = [];
9
+ const cleanup = attachRefreshWorkerCleanup(
10
+ { kill: (signal) => (calls.push(String(signal)), true) },
11
+ processRef,
12
+ );
13
+ processRef.emit("exit");
14
+ expect(calls).toEqual(["SIGTERM"]);
15
+ cleanup();
16
+ processRef.emit("exit");
17
+ expect(calls).toEqual(["SIGTERM"]);
18
+ });
19
+ });
@@ -1,25 +1,13 @@
1
1
  import { spawn } from "node:child_process";
2
- import { randomBytes } from "node:crypto";
3
2
  import { existsSync } from "node:fs";
4
- import { rm } from "node:fs/promises";
5
- import { dirname, resolve } from "node:path";
3
+ import { resolve } from "node:path";
6
4
  import {
7
5
  type SqliteReadModelFileSystem,
8
6
  sqliteReadModelPath,
9
7
  validateSqliteReadModelSchema,
10
8
  } from "./sqlite-read-model.js";
11
- import { activateStagedSqliteSnapshot, stageSqliteSnapshot } from "./sqlite-snapshot-install.js";
12
9
 
13
- /** Rebuilds an isolated SQLite generation so the serving event loop stays available. */
14
- export async function rebuildSnapshotInWorker(workspaceRoot: string, currentPath: string) {
15
- const target = resolve(
16
- dirname(currentPath),
17
- `refresh-server-${process.pid}-${randomBytes(4).toString("hex")}.sqlite`,
18
- );
19
- // A temporary/canonical workspace contains data, not necessarily a second
20
- // dashboard runtime checkout. The explicit candidates work for a launcher
21
- // opened at the workspace root and for fixture tests launched from the
22
- // runtime's source checkout, without relying on import.meta in the CJS build.
10
+ async function rebuildPersistentDatabaseInWorker(workspaceRoot: string, destination: string) {
23
11
  const scriptCandidates = [
24
12
  resolve(
25
13
  workspaceRoot,
@@ -40,58 +28,66 @@ export async function rebuildSnapshotInWorker(workspaceRoot: string, currentPath
40
28
  cwd: workspaceRoot,
41
29
  env: {
42
30
  ...process.env,
43
- // The script prioritizes ZAIP_WORKSPACE over cwd. Do not inherit a
44
- // launcher's root when this refresh was requested for another
45
- // canonical workspace (notably an isolated test fixture).
46
31
  ZAIP_WORKSPACE: workspaceRoot,
47
- ZAIP_SQLITE_DB_PATH: target,
32
+ ZAIP_SQLITE_DB_PATH: destination,
48
33
  ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "0",
49
34
  ZAIP_DASHBOARD_LAUNCH_PROGRESS: "0",
50
35
  },
51
36
  stdio: ["ignore", "ignore", "pipe"],
52
37
  windowsHide: true,
53
38
  });
39
+ // Lease shutdown may end the serving parent while a slow snapshot rebuild
40
+ // is still writing. Terminate the direct child synchronously during parent
41
+ // exit so the next launcher cannot overlap the persistent SQLite writer.
42
+ const cleanupWorker = attachRefreshWorkerCleanup(worker);
54
43
  worker.stderr?.setEncoding("utf8");
55
44
  worker.stderr?.on("data", (chunk: string) => {
56
- stderr += chunk;
45
+ stderr = `${stderr}${chunk}`.slice(-8_000);
57
46
  });
58
- worker.once("error", rejectWorker);
59
- worker.once("exit", (code) =>
60
- code === 0
47
+ worker.once("error", (error) => {
48
+ cleanupWorker();
49
+ rejectWorker(error);
50
+ });
51
+ worker.once("exit", (code) => {
52
+ cleanupWorker();
53
+ return code === 0
61
54
  ? resolveWorker()
62
55
  : rejectWorker(
63
56
  new Error(`SQLite refresh worker failed (${code}): ${stderr.trim() || "no stderr"}`),
64
- ),
65
- );
57
+ );
58
+ });
66
59
  });
67
- return target;
68
60
  }
69
61
 
70
- export async function removeRefreshSnapshot(path: string) {
71
- await rm(path, { force: true }).catch(() => {});
62
+ export function attachRefreshWorkerCleanup(
63
+ worker: { kill(signal?: NodeJS.Signals): boolean },
64
+ processRef: {
65
+ once(event: string, listener: () => void): unknown;
66
+ removeListener(event: string, listener: () => void): unknown;
67
+ } = process,
68
+ ): () => void {
69
+ const stopWorkerOnParentExit = () => {
70
+ try {
71
+ worker.kill("SIGTERM");
72
+ } catch {
73
+ /* already terminal */
74
+ }
75
+ };
76
+ processRef.once("exit", stopWorkerOnParentExit);
77
+ return () => processRef.removeListener("exit", stopWorkerOnParentExit);
72
78
  }
73
79
 
74
80
  /**
75
- * Builds and validates a replacement outside the serving process, then asks
76
- * the read-model filesystem to perform only the short close/rename/reopen
77
- * handoff. The current immutable generation remains readable while staging.
81
+ * The child process writes the single persistent database in place. WAL keeps
82
+ * the server reader usable until the worker commits; no staged SQLite file is
83
+ * created. On an error the worker rolls back and this reader stays untouched.
78
84
  */
79
85
  export async function refreshSqliteSnapshotInWorker(
80
86
  workspaceRoot: string,
81
87
  filesystem: SqliteReadModelFileSystem,
82
88
  ) {
83
89
  const destination = sqliteReadModelPath(workspaceRoot);
84
- const refreshedPath = await rebuildSnapshotInWorker(workspaceRoot, destination);
85
- try {
86
- const stagedPath = await stageSqliteSnapshot(
87
- refreshedPath,
88
- destination,
89
- validateSqliteReadModelSchema,
90
- );
91
- await filesystem.rebuildAndRefresh(async () => {
92
- activateStagedSqliteSnapshot(stagedPath, destination);
93
- });
94
- } finally {
95
- await removeRefreshSnapshot(refreshedPath);
96
- }
90
+ await rebuildPersistentDatabaseInWorker(workspaceRoot, destination);
91
+ validateSqliteReadModelSchema(destination);
92
+ filesystem.refresh();
97
93
  }
@@ -0,0 +1,42 @@
1
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join, resolve, sep } from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { expect, it } from "vitest";
6
+ import { readCommittedSnapshotRevision } from "./server-snapshot-revision.js";
7
+ import { sqliteReadModelPath } from "./sqlite-read-model.js";
8
+
9
+ it("publishes only committed metadata, preserving the revision through a rolled-back write", async () => {
10
+ const base = resolve(tmpdir());
11
+ const root = await mkdtemp(join(base, "zaip-revision-"));
12
+ if (!resolve(root).startsWith(base + sep))
13
+ throw new Error("Fixture cleanup outside temporary directory");
14
+ const databasePath = sqliteReadModelPath(root);
15
+ await mkdir(dirname(databasePath), { recursive: true });
16
+ const writer = new DatabaseSync(databasePath);
17
+ try {
18
+ writer.exec(`
19
+ PRAGMA journal_mode=WAL;
20
+ CREATE TABLE read_model_metadata (key TEXT PRIMARY KEY, value TEXT);
21
+ CREATE VIEW zaip_vw_read_model_metadata AS SELECT * FROM read_model_metadata;
22
+ INSERT INTO read_model_metadata VALUES ('schema_version', '1'), ('source_fingerprint', 'first');
23
+ `);
24
+ const original = readCommittedSnapshotRevision(root);
25
+ expect(original).toMatch(/^[a-f0-9]{64}$/);
26
+ writer.exec(
27
+ "BEGIN IMMEDIATE; UPDATE read_model_metadata SET value='second' WHERE key='source_fingerprint';",
28
+ );
29
+ expect(readCommittedSnapshotRevision(root)).toBe(original);
30
+ writer.exec("ROLLBACK;");
31
+ expect(readCommittedSnapshotRevision(root)).toBe(original);
32
+ writer.exec(
33
+ "BEGIN IMMEDIATE; UPDATE read_model_metadata SET value='second' WHERE key='source_fingerprint'; COMMIT;",
34
+ );
35
+ const committed = readCommittedSnapshotRevision(root);
36
+ expect(committed).not.toBe(original);
37
+ expect(readCommittedSnapshotRevision(root)).toBe(committed);
38
+ } finally {
39
+ writer.close();
40
+ await rm(root, { recursive: true, force: true });
41
+ }
42
+ });
@@ -0,0 +1,26 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DatabaseSync } from "node:sqlite";
3
+ import { sqliteReadModelPath } from "./sqlite-read-model.js";
4
+
5
+ /** Read committed inputs only. Canonical edits and rolled-back writes cannot advance the revision. */
6
+ export function readCommittedSnapshotRevision(workspaceRoot: string): string {
7
+ const database = new DatabaseSync(sqliteReadModelPath(workspaceRoot), { readOnly: true });
8
+ try {
9
+ const rows = database
10
+ .prepare(
11
+ "SELECT key, value FROM zaip_vw_read_model_metadata WHERE key IN ('schema_version', 'source_fingerprint')",
12
+ )
13
+ .all() as Array<{ key: string; value: string }>;
14
+ const metadata = new Map(rows.map((row) => [row.key, row.value]));
15
+ if (!metadata.has("schema_version") || !metadata.has("source_fingerprint")) {
16
+ throw new Error("Committed SQLite snapshot metadata is incomplete");
17
+ }
18
+ return createHash("sha256")
19
+ .update(metadata.get("schema_version") as string)
20
+ .update(":")
21
+ .update(metadata.get("source_fingerprint") as string)
22
+ .digest("hex");
23
+ } finally {
24
+ database.close();
25
+ }
26
+ }
@@ -0,0 +1,34 @@
1
+ import { watch } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ export function startSnapshotSynchronization(input: {
5
+ workspaceRoot: string;
6
+ staleSeed: boolean;
7
+ watchSources: boolean;
8
+ refresh: () => Promise<void>;
9
+ }): void {
10
+ if (input.staleSeed) void input.refresh().catch(() => undefined);
11
+ if (!input.watchSources || process.env.ZAIP_DASHBOARD_WATCH_SOURCES === "0") return;
12
+ let pendingRefresh: NodeJS.Timeout | undefined;
13
+ const scheduleRefresh = () => {
14
+ if (pendingRefresh !== undefined) clearTimeout(pendingRefresh);
15
+ pendingRefresh = setTimeout(() => {
16
+ pendingRefresh = undefined;
17
+ void input.refresh().catch(() => undefined);
18
+ }, 300);
19
+ pendingRefresh.unref();
20
+ };
21
+ for (const sourceRoot of [
22
+ join(input.workspaceRoot, ".agents", "records"),
23
+ join(input.workspaceRoot, ".agents", "config"),
24
+ ]) {
25
+ try {
26
+ const watcher = watch(sourceRoot, { recursive: true }, scheduleRefresh);
27
+ watcher.unref();
28
+ } catch {
29
+ // Reuse-triggered refresh remains the portable fallback.
30
+ }
31
+ }
32
+ const freshnessTimer = setInterval(scheduleRefresh, 60_000);
33
+ freshnessTimer.unref();
34
+ }
@@ -0,0 +1,30 @@
1
+ import type { rebuildSqliteReadModel } from "./sqlite-read-model.js";
2
+
3
+ export function writeStartupPhase(startedAt: number, phase: string): void {
4
+ process.stderr.write(
5
+ `${JSON.stringify({
6
+ event: "dashboard_sqlite_startup_phase",
7
+ phase,
8
+ elapsed_ms: Math.round(performance.now() - startedAt),
9
+ })}\n`,
10
+ );
11
+ }
12
+
13
+ export function writeReadModelStartup(
14
+ startedAt: number,
15
+ readModel: Awaited<ReturnType<typeof rebuildSqliteReadModel>>,
16
+ ): void {
17
+ // The parent launcher captures stderr even when stdout is intentionally
18
+ // hidden. This makes seed hit/miss and private-copy cost observable without
19
+ // exposing a shared handle or workspace payload.
20
+ process.stderr.write(
21
+ `${JSON.stringify({
22
+ event: "dashboard_sqlite_read_model",
23
+ elapsed_ms: Math.round(performance.now() - startedAt),
24
+ cache: readModel.cache,
25
+ fingerprint_ms: readModel.fingerprint_ms,
26
+ materialize_ms: readModel.materialize_ms,
27
+ source_files: readModel.files,
28
+ })}\n`,
29
+ );
30
+ }
@@ -0,0 +1,89 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ createWorkReconciliation,
4
+ startHostTerminalReconciliation,
5
+ waitForWorkReconciliation,
6
+ } from "./server-work-reconciliation.js";
7
+ import { type ReconciliationResult, reconciliationCommandTimeoutMs } from "./server-workspace.js";
8
+
9
+ const clean: ReconciliationResult = { reconciled_ids: [], blocked: [], not_reconciled: [] };
10
+ const changed: ReconciliationResult = {
11
+ ...clean,
12
+ reconciled_ids: ["TASK-TERMINAL", "SPEC-DELIVERED", "PRD-DELIVERED"],
13
+ };
14
+ afterEach(() => vi.useRealTimers());
15
+
16
+ describe("canonical host recovery", () => {
17
+ it("serializes manual recovery after host-only work and deduplicates concurrent clicks", async () => {
18
+ let release!: (value: ReconciliationResult) => void;
19
+ const first = new Promise<ReconciliationResult>((resolve) => {
20
+ release = resolve;
21
+ });
22
+ const reconcile = vi.fn().mockReturnValueOnce(first).mockResolvedValue(changed);
23
+ const refresh = vi.fn().mockResolvedValue(undefined);
24
+ const run = createWorkReconciliation({ reconcile, refresh });
25
+ const automatic = run("host-terminal");
26
+ await Promise.resolve();
27
+ const manual = run("manual");
28
+ expect(run("manual")).toBe(manual);
29
+ expect(run("host-terminal")).toBe(manual);
30
+ expect(reconcile.mock.calls).toEqual([["host-terminal"]]);
31
+ release(clean);
32
+ await automatic;
33
+ await expect(manual).resolves.toEqual(changed);
34
+ expect(reconcile.mock.calls).toEqual([["host-terminal"], ["manual"]]);
35
+ expect(refresh).toHaveBeenCalledOnce();
36
+ });
37
+
38
+ it("promotes partial changes after a failure and allows the next attempt", async () => {
39
+ const reconcile = vi
40
+ .fn()
41
+ .mockRejectedValueOnce(new Error("writer interrupted"))
42
+ .mockResolvedValue(clean);
43
+ const refresh = vi.fn().mockResolvedValue(undefined);
44
+ const run = createWorkReconciliation({ reconcile, refresh });
45
+ await expect(run("manual")).rejects.toThrow("writer interrupted");
46
+ await expect(run("manual")).resolves.toEqual(clean);
47
+ expect(refresh).toHaveBeenCalledOnce();
48
+ });
49
+
50
+ it("recovers at startup and retries after failure without overlapping work", async () => {
51
+ vi.useFakeTimers();
52
+ let release!: () => void;
53
+ const reconcile = vi
54
+ .fn()
55
+ .mockImplementationOnce(
56
+ () =>
57
+ new Promise<void>((resolve) => {
58
+ release = resolve;
59
+ }),
60
+ )
61
+ .mockRejectedValueOnce(new Error("busy"))
62
+ .mockResolvedValue(undefined);
63
+ const stop = startHostTerminalReconciliation(reconcile);
64
+ await vi.advanceTimersByTimeAsync(1_000);
65
+ await vi.advanceTimersByTimeAsync(90_000);
66
+ expect(reconcile).toHaveBeenCalledOnce();
67
+ release();
68
+ await vi.advanceTimersByTimeAsync(30_000);
69
+ expect(reconcile).toHaveBeenCalledTimes(2);
70
+ await vi.advanceTimersByTimeAsync(30_000);
71
+ expect(reconcile).toHaveBeenCalledTimes(3);
72
+ stop();
73
+ await vi.advanceTimersByTimeAsync(60_000);
74
+ expect(reconcile).toHaveBeenCalledTimes(3);
75
+ });
76
+
77
+ it("bounds total request time even while the canonical writer is queued", async () => {
78
+ vi.useFakeTimers();
79
+ let release!: (value: ReconciliationResult) => void;
80
+ const pending = new Promise<ReconciliationResult>((resolve) => {
81
+ release = resolve;
82
+ });
83
+ const bounded = expect(waitForWorkReconciliation(pending)).rejects.toThrow("exceeded");
84
+ await vi.advanceTimersByTimeAsync(reconciliationCommandTimeoutMs);
85
+ await bounded;
86
+ release(changed);
87
+ await expect(pending).resolves.toEqual(changed);
88
+ });
89
+ });
@@ -0,0 +1,88 @@
1
+ import {
2
+ ReconciliationCommandTimeoutError,
3
+ type ReconciliationResult,
4
+ reconciliationCommandTimeoutMs,
5
+ } from "./server-workspace.js";
6
+
7
+ export function reconciliationNeedsRefresh(result: ReconciliationResult): boolean {
8
+ return result.reconciled_ids.length > 0 || result.blocked.length > 0;
9
+ }
10
+
11
+ export type ReconciliationMode = "manual" | "host-terminal";
12
+
13
+ /** Include queue time in the HTTP deadline; the serialized job remains retryable. */
14
+ export async function waitForWorkReconciliation(
15
+ work: Promise<ReconciliationResult>,
16
+ onDeadline: () => void = () => undefined,
17
+ ): Promise<ReconciliationResult> {
18
+ let timer: ReturnType<typeof setTimeout> | undefined;
19
+ try {
20
+ return await Promise.race([
21
+ work,
22
+ new Promise<never>((_resolve, reject) => {
23
+ timer = setTimeout(() => {
24
+ onDeadline();
25
+ reject(new ReconciliationCommandTimeoutError());
26
+ }, reconciliationCommandTimeoutMs);
27
+ timer.unref();
28
+ }),
29
+ ]);
30
+ } finally {
31
+ if (timer !== undefined) clearTimeout(timer);
32
+ }
33
+ }
34
+
35
+ /** Serialize canonical mutations from the button and the host recovery timer. */
36
+ export function createWorkReconciliation(input: {
37
+ reconcile: (mode: ReconciliationMode) => Promise<ReconciliationResult>;
38
+ refresh: () => Promise<void>;
39
+ }) {
40
+ let tail = Promise.resolve();
41
+ const pending = new Map<ReconciliationMode, Promise<ReconciliationResult>>();
42
+ const refresh = () => void input.refresh().catch(() => undefined);
43
+ return (mode: ReconciliationMode): Promise<ReconciliationResult> => {
44
+ const current = pending.get("manual") ?? pending.get(mode);
45
+ if (current !== undefined) return current;
46
+ const job = tail
47
+ .then(() => input.reconcile(mode))
48
+ .then(
49
+ (result) => {
50
+ if (result.reconciled_ids.length > 0 || result.blocked.length > 0) refresh();
51
+ return result;
52
+ },
53
+ (error: unknown) => {
54
+ // A failed command may already have persisted part of the graph.
55
+ refresh();
56
+ throw error;
57
+ },
58
+ )
59
+ .finally(() => pending.delete(mode));
60
+ pending.set(mode, job);
61
+ tail = job.then(
62
+ () => undefined,
63
+ () => undefined,
64
+ );
65
+ return job;
66
+ };
67
+ }
68
+
69
+ /** Recover missed host-stop callbacks without relying on an open browser tab. */
70
+ export function startHostTerminalReconciliation(reconcile: () => Promise<unknown>): () => void {
71
+ let stopped = false;
72
+ let timer: ReturnType<typeof setTimeout>;
73
+ const schedule = (delay: number) => {
74
+ timer = setTimeout(() => {
75
+ void reconcile()
76
+ .catch(() => undefined)
77
+ .finally(() => {
78
+ if (!stopped) schedule(30_000);
79
+ });
80
+ }, delay);
81
+ timer.unref();
82
+ };
83
+ schedule(1_000);
84
+ return () => {
85
+ stopped = true;
86
+ clearTimeout(timer);
87
+ };
88
+ }
@@ -68,19 +68,52 @@ export interface ReconciliationResult {
68
68
  not_reconciled: ReconciliationItem[];
69
69
  }
70
70
 
71
+ export const reconciliationCommandTimeoutMs = 170_000;
72
+
73
+ export class ReconciliationCommandTimeoutError extends Error {
74
+ constructor() {
75
+ super(
76
+ `Open-work reconciliation exceeded ${reconciliationCommandTimeoutMs}ms. Canonical records may contain partial changes; the dashboard projection is being refreshed.`,
77
+ );
78
+ this.name = "ReconciliationCommandTimeoutError";
79
+ }
80
+ }
81
+
71
82
  export async function reconcileOpenWork(
72
83
  filesystem: WorkspaceFileSystem,
73
84
  workspaceRoot: string,
85
+ options: { hostTerminalOnly?: boolean } = {},
74
86
  ): Promise<ReconciliationResult> {
75
87
  if (!(await filesystem.exists(".agents/runtime/bin/zaip.mjs"))) {
76
- return { reconciled_ids: [], blocked: [], not_reconciled: [] };
88
+ throw new Error("ZAIP runtime is unavailable; open work could not be reconciled.");
77
89
  }
78
90
  const cliPath = resolve(workspaceRoot, ".agents", "runtime", "bin", "zaip.mjs");
79
- const { stdout } = await execFileAsync(
80
- process.execPath,
81
- [cliPath, "run", "reconcile", "--open", "--json"],
82
- { cwd: workspaceRoot, windowsHide: true, timeout: 120_000, maxBuffer: 10 * 1_024 * 1_024 },
83
- );
91
+ let stdout: string;
92
+ try {
93
+ ({ stdout } = await execFileAsync(
94
+ process.execPath,
95
+ [
96
+ cliPath,
97
+ "run",
98
+ "reconcile",
99
+ "--open",
100
+ "--json",
101
+ ...(options.hostTerminalOnly ? ["--host-terminal-only"] : []),
102
+ ],
103
+ {
104
+ cwd: workspaceRoot,
105
+ windowsHide: true,
106
+ maxBuffer: 10 * 1_024 * 1_024,
107
+ timeout: reconciliationCommandTimeoutMs,
108
+ killSignal: "SIGTERM",
109
+ },
110
+ ));
111
+ } catch (error) {
112
+ if (typeof error === "object" && error !== null && "killed" in error && error.killed === true) {
113
+ throw new ReconciliationCommandTimeoutError();
114
+ }
115
+ throw error;
116
+ }
84
117
  return reconciliationResult(JSON.parse(stdout) as unknown);
85
118
  }
86
119
 
@@ -94,6 +127,7 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
94
127
  };
95
128
  const records =
96
129
  typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
130
+ const terminalStatuses = new Set(["completed", "delivered", "blocked", "cancelled", "failed"]);
97
131
  const reconciliations = Array.isArray(records.reconciliations) ? records.reconciliations : [];
98
132
  for (const candidate of reconciliations) {
99
133
  if (typeof candidate !== "object" || candidate === null) continue;
@@ -107,7 +141,7 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
107
141
  const record = entity as Record<string, unknown>;
108
142
  if (
109
143
  item.status === "closed" &&
110
- ["completed", "blocked", "cancelled", "failed"].includes(record.status as string) &&
144
+ terminalStatuses.has(record.status as string) &&
111
145
  typeof record.id === "string"
112
146
  ) {
113
147
  reconciled.add(record.id);
@@ -119,12 +153,39 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
119
153
  }
120
154
  }
121
155
  }
156
+ // A terminal conversation/root can change canonical telemetry without a
157
+ // Task graph. It still requires a snapshot promotion so Cost Monitor does
158
+ // not keep serving the generation that preceded its Run closure.
159
+ const closedRuns = Array.isArray(records.runs) ? records.runs : [];
160
+ closedRuns.forEach((candidate) => {
161
+ if (typeof candidate !== "object" || candidate === null) return;
162
+ const runId = (candidate as { run?: { run_id?: unknown } }).run?.run_id;
163
+ if (typeof runId === "string") reconciled.add(runId);
164
+ });
122
165
  const orphanedExecutions = Array.isArray(records.orphaned_executions)
123
166
  ? records.orphaned_executions
124
167
  : [];
125
168
  orphanedExecutions.forEach((id) => {
126
169
  remember(blocked, id, "Execution orphaned after the reconciliation grace period.");
127
170
  });
171
+ const orphanedInteractionExecutions = Array.isArray(records.orphaned_interaction_executions)
172
+ ? records.orphaned_interaction_executions
173
+ : [];
174
+ orphanedInteractionExecutions.forEach((id) => {
175
+ remember(blocked, id, "Interaction Execution orphaned after the reconciliation grace period.");
176
+ });
177
+ // Orphan recovery can settle owners without returning a Run reconciliation.
178
+ // Surface every persisted owner, including macro-only `delivered` outcomes,
179
+ // so the button reports the changed entities and refreshes their snapshot.
180
+ for (const key of ["orphaned_owner_entities", "orphaned_execution_entities"]) {
181
+ for (const candidate of Array.isArray(records[key]) ? records[key] : []) {
182
+ if (typeof candidate !== "object" || candidate === null) continue;
183
+ const entity = candidate as Record<string, unknown>;
184
+ if (typeof entity.id !== "string" || !terminalStatuses.has(entity.status as string)) continue;
185
+ reconciled.add(entity.id);
186
+ if (entity.status === "blocked") remember(blocked, entity.id, entity.status_reason);
187
+ }
188
+ }
128
189
  const errors = Array.isArray(records.reconciliation_errors) ? records.reconciliation_errors : [];
129
190
  errors.forEach((candidate) => {
130
191
  if (typeof candidate !== "object" || candidate === null) return;
@@ -247,15 +247,21 @@ describe("dashboard backend", () => {
247
247
  headers: { "x-zaip-session": "test-session" },
248
248
  });
249
249
  expect(response.status, await response.clone().text()).toBe(200);
250
- await expect(response.json()).resolves.toMatchObject({
251
- reconciliation: { reconciled_ids: ["TASK-RECONCILED"] },
252
- });
250
+ const receipt = (await response.json()) as Record<string, unknown>;
251
+ expect(receipt).toMatchObject({ reconciliation: { reconciled_ids: ["TASK-RECONCILED"] } });
252
+ expect(Object.keys(receipt).sort()).toEqual([
253
+ "projection_refresh_completed",
254
+ "reconciliation",
255
+ "snapshot_state",
256
+ ]);
253
257
  expect(reconcile).toHaveBeenCalledOnce();
254
- expect(reconcile).toHaveBeenCalledWith(canonicalFilesystem, workspaceRoot);
258
+ expect(reconcile).toHaveBeenCalledWith(canonicalFilesystem, workspaceRoot, {
259
+ hostTerminalOnly: false,
260
+ });
255
261
  expect(refresh).toHaveBeenCalledOnce();
256
262
  const rejected = await app.request("/api/v2/reconcile-open-work", { method: "POST" });
257
263
  expect(rejected.status).toBe(401);
258
- }, 130_000);
264
+ });
259
265
  it("rebuilds a stale immutable projection without reconciling lifecycle work", async () => {
260
266
  const fixtureRoot = await mkdtemp(join(tmpdir(), "zaip-dashboard-stale-projection-"));
261
267
  const taskIndexPath = join(