@zaip-ai/cli 1.3.7 → 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.
- package/README.md +86 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/services/adapters.js +4 -4
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-config-template.d.ts +2 -2
- package/dist/services/codex-config-template.d.ts.map +1 -1
- package/dist/services/codex-config-template.js +3 -3
- package/dist/services/codex-config-template.js.map +1 -1
- package/dist/services/codex-rules.d.ts +1 -1
- package/dist/services/codex-rules.d.ts.map +1 -1
- package/dist/services/codex-rules.js +9 -24
- package/dist/services/codex-rules.js.map +1 -1
- package/dist/services/delivery-git-preflight.d.ts +23 -0
- package/dist/services/delivery-git-preflight.d.ts.map +1 -0
- package/dist/services/delivery-git-preflight.js +69 -0
- package/dist/services/delivery-git-preflight.js.map +1 -0
- package/dist/services/doctor.d.ts.map +1 -1
- package/dist/services/doctor.js +13 -1
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +22 -28
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/install-profile.d.ts +8 -0
- package/dist/services/install-profile.d.ts.map +1 -0
- package/dist/services/install-profile.js +56 -0
- package/dist/services/install-profile.js.map +1 -0
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +6 -5
- package/dist/services/logger.js.map +1 -1
- package/dist/services/stack-git.d.ts +9 -0
- package/dist/services/stack-git.d.ts.map +1 -0
- package/dist/services/stack-git.js +188 -0
- package/dist/services/stack-git.js.map +1 -0
- package/dist/services/stack-install.d.ts +14 -0
- package/dist/services/stack-install.d.ts.map +1 -0
- package/dist/services/stack-install.js +176 -0
- package/dist/services/stack-install.js.map +1 -0
- package/dist/services/stack-node.d.ts +12 -0
- package/dist/services/stack-node.d.ts.map +1 -0
- package/dist/services/stack-node.js +150 -0
- package/dist/services/stack-node.js.map +1 -0
- package/dist/services/stack-package-manager.d.ts +12 -0
- package/dist/services/stack-package-manager.d.ts.map +1 -0
- package/dist/services/stack-package-manager.js +178 -0
- package/dist/services/stack-package-manager.js.map +1 -0
- package/dist/services/stack-plan.d.ts +35 -0
- package/dist/services/stack-plan.d.ts.map +1 -0
- package/dist/services/stack-plan.js +130 -0
- package/dist/services/stack-plan.js.map +1 -0
- package/dist/services/stack-terraform.d.ts +13 -0
- package/dist/services/stack-terraform.d.ts.map +1 -0
- package/dist/services/stack-terraform.js +274 -0
- package/dist/services/stack-terraform.js.map +1 -0
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +14 -1
- package/dist/services/template-manifest.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/.env.example +34 -0
- package/templates/{README.md → .agents/README.md} +124 -60
- package/templates/.agents/config/agents/catalog.yaml +23 -23
- package/templates/.agents/config/clients/codex/agents/zaip-agent-analyst.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-auditor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-backend.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-builder.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-cloud-engineer.toml +10 -7
- package/templates/.agents/config/clients/codex/agents/zaip-agent-data-analyst.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-frontend.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-integrator.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-refactoring-specialist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-reviewer.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-security-auditor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-task-executor.toml +1 -0
- package/templates/.agents/config/clients/codex/agents/zaip-agent-website-developer.toml +5 -4
- package/templates/.agents/config/clients/codex/agents/zaip-agent-writer.toml +1 -0
- package/templates/.agents/config/pricing/models.json +25 -1
- package/templates/.agents/config/project.yaml +2 -0
- package/templates/.agents/dashboard/.zaip-managed.json +214 -94
- package/templates/.agents/dashboard/DATA-DICTIONARY.md +1 -1
- package/templates/.agents/dashboard/README.md +58 -19
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-DwVZOmMV.js → CostCalculationGuidePage-snUCQYTC.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog--czwAlcH.js → DashboardHelpDialog-oZVlf-l5.js} +787 -723
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-CyjMXkm8.js → PricingSettingsDialog-BPtVUGgs.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-3Awyaovx.js +3 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-BomxqCBU.css → ProjectSettingsPage-GgzpZ_PY.css} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CAqYVin1.js → TraceabilityWorkspace-BYjVOHI_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DbaLbU2q.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DXBOtWNI.js → icons-vendor-TPUT2IUt.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Cmlx8OYH.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DaNXQPyk.js → react-vendor-Ct48XHGL.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-I4AKqVDX.js → traceability-BDe4_Ubl.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +73 -67
- package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +35 -7
- package/templates/.agents/dashboard/apps/frontend/src/AppWorkBoard.ts +55 -0
- package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +15 -2
- package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +33 -6
- package/templates/.agents/dashboard/apps/frontend/src/app-loading-boundary.css +98 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +5 -5
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-reconciliation.ts +33 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +75 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +9 -8
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +15 -3
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +0 -104
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +61 -0
- package/templates/.agents/dashboard/apps/frontend/src/project-stack-versions.css +117 -0
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +78 -2
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +28 -0
- package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +8 -5
- package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +180 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +7 -4
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +13 -7
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +14 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTableDisplay.ts +211 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +152 -157
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +155 -142
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/InvocationManifestQuery.test.ts +145 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +7 -5
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +4 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +4 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +79 -1
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +105 -11
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +477 -327
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +430 -285
- package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +6 -26
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-persistent-launch.mjs +268 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +482 -332
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +243 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +252 -41
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-assets.ts +39 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-browser-lease.ts +33 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts +148 -11
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +68 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts +19 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.ts +39 -43
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.ts +26 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +34 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-startup.ts +30 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +89 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +88 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +68 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +11 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +129 -99
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-agent-productivity.ts +140 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.test.ts +127 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +145 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-aggregates.ts +156 -23
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-group-projection.ts +18 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-invocation-projection.ts +52 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-first-fold-persistence.ts +35 -13
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-force-canonical.test.ts +56 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.test.ts +29 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-cache.ts +136 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +251 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-operations-projection.ts +22 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-cache.test-support.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical-parsing.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +793 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-duration.test.ts +270 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider-fixture.ts +3 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +18 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +105 -16
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +131 -59
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1000 -1115
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-cache.ts +13 -92
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-claims.test.mjs +79 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +27 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +6 -6
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-refresh.mjs +2 -10
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +142 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +231 -239
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +18 -29
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +6 -0
- package/templates/.agents/dashboard/stack-dependencies.json +14 -0
- package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +47 -0
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +2 -2
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/autonomy-decision.md +1 -1
- package/templates/.agents/rules/cloud-hostinger-autonomy.md +21 -0
- package/templates/.agents/rules/core.md +5 -4
- package/templates/.agents/rules/local-sqlite-storage.md +18 -0
- package/templates/.agents/rules/meta-mcp.md +21 -0
- package/templates/.agents/rules/workflow.md +1 -0
- package/templates/.agents/runtime/bin/zaip.mjs +305 -137
- package/templates/.agents/runtime/checksums.sha256 +2 -2
- package/templates/.agents/runtime/provenance.json +2 -2
- package/templates/.agents/skills/zaip-skill-api-to-mcp/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +25 -9
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +2 -2
- package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +2 -2
- package/templates/.agents/skills/zaip-skill-change/references/observable-evidence.md +15 -0
- package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +13 -5
- package/templates/.agents/skills/zaip-skill-data-analyst/SKILL.md +10 -5
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +5 -4
- package/templates/.agents/skills/zaip-skill-frontend-launcher/SKILL.md +4 -3
- package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +5 -3
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +5 -5
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.mjs +78 -13
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/preflight.test.mjs +59 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/yaml-parser.mjs +157 -0
- package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +11 -6
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/SKILL.md +17 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/agents/openai.yaml +7 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-cloud-inputs.example.json +41 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/autonomous-runner.md +84 -0
- package/templates/.agents/skills/zaip-skill-hostinger-cloudflare-terraform/references/operational-contract.md +19 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/SKILL.md +43 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/agents/openai.yaml +6 -0
- package/templates/.agents/skills/zaip-skill-local-credentials/references/dotenv-resolution.md +47 -0
- package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +8 -3
- package/templates/.agents/skills/zaip-skill-meta-ads/SKILL.md +16 -0
- package/templates/.agents/skills/zaip-skill-meta-ads/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-ads/references/setup.md +3 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/SKILL.md +24 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/diagnostics.md +13 -0
- package/templates/.agents/skills/zaip-skill-meta-ads-setup/references/official-sources.md +10 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-health/references/upstream.md +77 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-api-integration/references/upstream.md +126 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-health-check/references/upstream.md +67 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-app-review-prep/references/upstream.md +69 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-compliance-check/references/upstream.md +79 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/SKILL.md +14 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-access-token/references/upstream.md +144 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-debug-webhooks/references/upstream.md +83 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/SKILL.md +10 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/setup.md +11 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-search-docs/references/upstream.md +55 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/SKILL.md +12 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/LICENSE +21 -0
- package/templates/.agents/skills/zaip-skill-meta-devtools-webhook-setup/references/upstream.md +56 -0
- package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +12 -7
- package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +11 -4
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +6 -4
- package/templates/.agents/skills/zaip-skill-tool-setup/references/codex-config.md +4 -2
- package/templates/.agents/skills/zaip-skill-tool-setup/references/secrets.md +1 -0
- package/templates/.agents/tools/catalog.yaml +6 -0
- package/templates/.agents/tools/cloud-runner-dns.mjs +28 -0
- package/templates/.agents/tools/cloud-runner-terraform.mjs +212 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +239 -0
- package/templates/.agents/tools/cloud-runner.mjs +786 -0
- package/templates/.agents/tools/cloud-runner.test.mjs +534 -0
- package/templates/.agents/tools/cloud-terraform-plan-policy.mjs +262 -0
- package/templates/.agents/tools/mcp/development/meta-devtools/README.md +14 -0
- package/templates/.agents/tools/mcp/development/meta-devtools/manifest.yaml +28 -0
- package/templates/.agents/tools/mcp/infra/cloudflare/manifest.yaml +2 -3
- package/templates/.agents/tools/mcp/infra/hostinger/manifest.yaml +1 -3
- package/templates/.agents/tools/mcp/marketing/meta-ads/README.md +15 -13
- package/templates/.agents/tools/mcp/marketing/meta-ads/manifest.yaml +15 -10
- package/templates/.agents/tools/profiles/development.yaml +3 -0
- package/templates/infra/terraform/.terraform.lock.hcl +42 -0
- package/templates/infra/terraform/README.md +31 -0
- package/templates/infra/terraform/backend.tf.json +13 -0
- package/templates/infra/terraform/deployment-inputs.example.yaml +22 -0
- package/templates/infra/terraform/fixtures/approved-targets.json +32 -0
- package/templates/infra/terraform/fixtures/plan-approved.json +67 -0
- package/templates/infra/terraform/main.tf +77 -0
- package/templates/infra/terraform/outputs.tf +20 -0
- package/templates/infra/terraform/terraform.tfvars.example +26 -0
- package/templates/infra/terraform/variables.tf +91 -0
- package/templates/infra/terraform/versions.tf +18 -0
- package/templates/scripts/validate-cloud-hostinger-contract.mjs +89 -0
- package/templates/scripts/validate-cloud-terraform-plan.mjs +1 -0
- package/templates/template-manifest.json +613 -173
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-B_Oj6Dlf.js +0 -3
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CblPpexd.js +0 -2
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +0 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +0 -50
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Hono } from "hono";
|
|
2
|
+
import { streamSSE } from "hono/streaming";
|
|
3
|
+
|
|
4
|
+
export function registerBrowserLeaseRoute(
|
|
5
|
+
app: Hono,
|
|
6
|
+
apiPrefix: string,
|
|
7
|
+
launcherOwned = false,
|
|
8
|
+
): void {
|
|
9
|
+
const browserLeases = new Set<object>();
|
|
10
|
+
let browserLeaseDeadline: NodeJS.Timeout | undefined;
|
|
11
|
+
if (launcherOwned) {
|
|
12
|
+
browserLeaseDeadline = setTimeout(() => process.exit(0), 12_000);
|
|
13
|
+
browserLeaseDeadline.unref();
|
|
14
|
+
}
|
|
15
|
+
const releaseBrowserLease = (lease: object) => {
|
|
16
|
+
browserLeases.delete(lease);
|
|
17
|
+
if (!launcherOwned || browserLeases.size !== 0) return;
|
|
18
|
+
if (browserLeaseDeadline !== undefined) clearTimeout(browserLeaseDeadline);
|
|
19
|
+
browserLeaseDeadline = setTimeout(() => process.exit(0), 12_000);
|
|
20
|
+
browserLeaseDeadline.unref();
|
|
21
|
+
};
|
|
22
|
+
app.get(`${apiPrefix}/browser-lease`, (context) => {
|
|
23
|
+
const lease = {};
|
|
24
|
+
if (browserLeaseDeadline !== undefined) clearTimeout(browserLeaseDeadline);
|
|
25
|
+
browserLeaseDeadline = undefined;
|
|
26
|
+
browserLeases.add(lease);
|
|
27
|
+
return streamSSE(context, async (stream) => {
|
|
28
|
+
stream.onAbort(() => releaseBrowserLease(lease));
|
|
29
|
+
await stream.writeSSE({ event: "lease", data: "active" });
|
|
30
|
+
await new Promise(() => {});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-refresh.test.ts
CHANGED
|
@@ -1,12 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
2
3
|
import { WorkspaceFileSystem, type WorkspaceLayout } from "@zaip-ai/core";
|
|
3
|
-
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
5
|
import { createDashboardApp, reconciliationNeedsRefresh } from "./server.js";
|
|
6
|
+
import { ReconciliationCommandTimeoutError, reconciliationResult } from "./server-workspace.js";
|
|
7
|
+
import { rebuildSqliteReadModel, SqliteReadModelFileSystem } from "./sqlite-read-model.js";
|
|
5
8
|
|
|
6
9
|
const workspaceRoot = resolve(import.meta.dirname, "../../../../..");
|
|
7
10
|
const v2Layout = { version: 3 } as WorkspaceLayout;
|
|
11
|
+
const fixtureRoots: string[] = [];
|
|
12
|
+
const fixtureBase = join(workspaceRoot, ".tmp", "server-reconciliation-refresh-tests");
|
|
13
|
+
|
|
14
|
+
async function createSqliteFixture(): Promise<string> {
|
|
15
|
+
await mkdir(fixtureBase, { recursive: true });
|
|
16
|
+
const root = await mkdtemp(join(fixtureBase, "fixture-"));
|
|
17
|
+
fixtureRoots.push(root);
|
|
18
|
+
await mkdir(join(root, ".agents", "config"), { recursive: true });
|
|
19
|
+
await Promise.all([
|
|
20
|
+
readFile(join(workspaceRoot, ".agents", "config", "project.yaml"), "utf8").then((content) =>
|
|
21
|
+
writeFile(join(root, ".agents", "config", "project.yaml"), content),
|
|
22
|
+
),
|
|
23
|
+
readFile(join(workspaceRoot, ".agents", "config", "framework.lock.yaml"), "utf8").then(
|
|
24
|
+
(content) => writeFile(join(root, ".agents", "config", "framework.lock.yaml"), content),
|
|
25
|
+
),
|
|
26
|
+
]);
|
|
27
|
+
await rebuildSqliteReadModel(root);
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
afterEach(async () => {
|
|
32
|
+
await Promise.all(
|
|
33
|
+
fixtureRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
|
34
|
+
);
|
|
35
|
+
});
|
|
8
36
|
|
|
9
37
|
describe("reconciliation snapshot refresh", () => {
|
|
38
|
+
it("publishes a stable readiness revision and changes it after a status-only snapshot commit", async () => {
|
|
39
|
+
const fixtureRoot = await createSqliteFixture();
|
|
40
|
+
const taskPath = join(fixtureRoot, ".agents/records/tasks/TASK-REVISION.md");
|
|
41
|
+
await mkdir(join(fixtureRoot, ".agents/records/tasks"), { recursive: true });
|
|
42
|
+
const task = (status: string) => `---
|
|
43
|
+
schema_version: 2
|
|
44
|
+
id: TASK-REVISION
|
|
45
|
+
title: Snapshot status revision
|
|
46
|
+
status: ${status}
|
|
47
|
+
created_at: 2026-09-05T10:00:00.000Z
|
|
48
|
+
---
|
|
49
|
+
# Task
|
|
50
|
+
`;
|
|
51
|
+
await writeFile(taskPath, task("executing"));
|
|
52
|
+
await rebuildSqliteReadModel(fixtureRoot);
|
|
53
|
+
const filesystem = new SqliteReadModelFileSystem(fixtureRoot);
|
|
54
|
+
const refresh = vi.fn(async () => undefined);
|
|
55
|
+
const app = createDashboardApp(fixtureRoot, "test-session", undefined, {
|
|
56
|
+
snapshotReadOnly: true,
|
|
57
|
+
filesystem,
|
|
58
|
+
refreshReadModel: refresh,
|
|
59
|
+
layout: v2Layout,
|
|
60
|
+
});
|
|
61
|
+
const ready = async () => {
|
|
62
|
+
const response = await app.request("/api/v2/ready", {
|
|
63
|
+
headers: { "x-zaip-session": "test-session" },
|
|
64
|
+
});
|
|
65
|
+
expect(response.status).toBe(200);
|
|
66
|
+
return response.json() as Promise<{
|
|
67
|
+
snapshot_revision: string;
|
|
68
|
+
activity_count: number;
|
|
69
|
+
day_count: number;
|
|
70
|
+
}>;
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
const before = await ready();
|
|
74
|
+
expect(before.snapshot_revision).toMatch(/^[a-f0-9]{64}$/);
|
|
75
|
+
await expect(ready()).resolves.toEqual(before);
|
|
76
|
+
await writeFile(taskPath, task("completed"));
|
|
77
|
+
// Canonical edits do not pretend that the opened snapshot was promoted.
|
|
78
|
+
await expect(ready()).resolves.toEqual(before);
|
|
79
|
+
// Use the production refresh boundary, which releases Windows readers
|
|
80
|
+
// before replacing an older snapshot generation.
|
|
81
|
+
await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(fixtureRoot));
|
|
82
|
+
const after = await ready();
|
|
83
|
+
expect(after.snapshot_revision).not.toBe(before.snapshot_revision);
|
|
84
|
+
expect(after.activity_count).toBe(before.activity_count);
|
|
85
|
+
expect(after.day_count).toBe(before.day_count);
|
|
86
|
+
await filesystem.rebuildAndRefresh(() => rebuildSqliteReadModel(fixtureRoot));
|
|
87
|
+
await expect(ready()).resolves.toEqual(after);
|
|
88
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
89
|
+
} finally {
|
|
90
|
+
filesystem.close();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
10
94
|
it("refreshes only when reconciliation changed canonical work", () => {
|
|
11
95
|
expect(
|
|
12
96
|
reconciliationNeedsRefresh({ reconciled_ids: [], blocked: [], not_reconciled: [] }),
|
|
@@ -25,9 +109,35 @@ describe("reconciliation snapshot refresh", () => {
|
|
|
25
109
|
not_reconciled: [],
|
|
26
110
|
}),
|
|
27
111
|
).toBe(true);
|
|
112
|
+
expect(
|
|
113
|
+
reconciliationNeedsRefresh(
|
|
114
|
+
reconciliationResult({
|
|
115
|
+
reconciliations: [
|
|
116
|
+
{
|
|
117
|
+
status: "closed",
|
|
118
|
+
specs: [{ id: "SPEC-DELIVERED", status: "delivered" }],
|
|
119
|
+
prds: [{ id: "PRD-DELIVERED", status: "delivered" }],
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
).toBe(true);
|
|
28
125
|
});
|
|
29
126
|
|
|
30
|
-
it("
|
|
127
|
+
it("reports a closed Run as a snapshot-changing reconciliation without a Task graph", () => {
|
|
128
|
+
expect(
|
|
129
|
+
reconciliationNeedsRefresh(
|
|
130
|
+
reconciliationResult({
|
|
131
|
+
runs: [{ run: { run_id: "RUN-CONVERSATION-CLOSED" } }],
|
|
132
|
+
reconciliations: [],
|
|
133
|
+
}),
|
|
134
|
+
),
|
|
135
|
+
).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("returns a compact receipt while a changed reconciliation refreshes in a worker", async () => {
|
|
139
|
+
const fixtureRoot = await createSqliteFixture();
|
|
140
|
+
const filesystem = new SqliteReadModelFileSystem(fixtureRoot);
|
|
31
141
|
let rejectRefresh: ((error: Error) => void) | undefined;
|
|
32
142
|
const refresh = vi.fn(
|
|
33
143
|
() =>
|
|
@@ -35,9 +145,10 @@ describe("reconciliation snapshot refresh", () => {
|
|
|
35
145
|
rejectRefresh = reject;
|
|
36
146
|
}),
|
|
37
147
|
);
|
|
38
|
-
const app = createDashboardApp(
|
|
148
|
+
const app = createDashboardApp(fixtureRoot, "test-session", undefined, {
|
|
39
149
|
snapshotReadOnly: true,
|
|
40
|
-
|
|
150
|
+
filesystem,
|
|
151
|
+
canonicalFilesystem: new WorkspaceFileSystem(fixtureRoot),
|
|
41
152
|
reconcileOpenWork: async () => ({
|
|
42
153
|
reconciled_ids: ["TASK-CHANGED"],
|
|
43
154
|
blocked: [],
|
|
@@ -50,17 +161,43 @@ describe("reconciliation snapshot refresh", () => {
|
|
|
50
161
|
method: "POST",
|
|
51
162
|
headers: { "x-zaip-session": "test-session" },
|
|
52
163
|
});
|
|
164
|
+
const response = await reconciliation;
|
|
165
|
+
expect(response.status, await response.clone().text()).toBe(200);
|
|
166
|
+
await expect(response.json()).resolves.toMatchObject({
|
|
167
|
+
snapshot_state: "updating",
|
|
168
|
+
projection_refresh_completed: false,
|
|
169
|
+
reconciliation: { reconciled_ids: ["TASK-CHANGED"] },
|
|
170
|
+
});
|
|
53
171
|
await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
|
|
54
172
|
await expect(
|
|
55
173
|
app.request("/api/v2/health", { headers: { "x-zaip-session": "test-session" } }),
|
|
56
|
-
).resolves.toMatchObject({
|
|
57
|
-
status: 200,
|
|
58
|
-
});
|
|
174
|
+
).resolves.toMatchObject({ status: 200 });
|
|
59
175
|
rejectRefresh?.(new Error("simulated worker refresh failure"));
|
|
60
|
-
|
|
61
|
-
|
|
176
|
+
await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
|
|
177
|
+
filesystem.close();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns a finite partial-reconciliation error and refreshes the projection", async () => {
|
|
181
|
+
const refresh = vi.fn(async () => undefined);
|
|
182
|
+
const app = createDashboardApp(workspaceRoot, "test-session", undefined, {
|
|
183
|
+
snapshotReadOnly: true,
|
|
184
|
+
canonicalFilesystem: new WorkspaceFileSystem(workspaceRoot),
|
|
185
|
+
reconcileOpenWork: async () => {
|
|
186
|
+
throw new ReconciliationCommandTimeoutError();
|
|
187
|
+
},
|
|
188
|
+
refreshReadModel: refresh,
|
|
189
|
+
layout: v2Layout,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const response = await app.request("/api/v2/reconcile-open-work", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
headers: { "x-zaip-session": "test-session" },
|
|
195
|
+
});
|
|
196
|
+
expect(response.status).toBe(408);
|
|
62
197
|
await expect(response.json()).resolves.toMatchObject({
|
|
63
|
-
|
|
198
|
+
reconciliation_may_be_partial: true,
|
|
199
|
+
projection_refresh_started: true,
|
|
64
200
|
});
|
|
201
|
+
await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
|
|
65
202
|
});
|
|
66
203
|
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts
ADDED
|
@@ -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
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-refresh.test.ts
ADDED
|
@@ -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 {
|
|
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
|
-
|
|
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:
|
|
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
|
|
45
|
+
stderr = `${stderr}${chunk}`.slice(-8_000);
|
|
57
46
|
});
|
|
58
|
-
worker.once("error",
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
71
|
-
|
|
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
|
-
*
|
|
76
|
-
* the
|
|
77
|
-
*
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-revision.test.ts
ADDED
|
@@ -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
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts
ADDED
|
@@ -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
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts
ADDED
|
@@ -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
|
+
});
|