@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.
- package/README.md +58 -3
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +17 -6
- 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 +2 -0
- 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-plan.d.ts +3 -1
- package/dist/services/stack-plan.d.ts.map +1 -1
- package/dist/services/stack-plan.js +15 -1
- package/dist/services/stack-plan.js.map +1 -1
- 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 +1 -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 +200 -88
- 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-VcLx9iOO.js → DashboardHelpDialog-oZVlf-l5.js} +72 -8
- 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/{TraceabilityWorkspace-DFZwIXSC.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/ProjectStackEditor.tsx +11 -0
- 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 +7 -8
- package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +14 -2
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +35 -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 +482 -332
- 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 +90 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +112 -22
- 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/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 +599 -167
- package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BwnJpl02.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-Dyo_wpL4.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
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
access,
|
|
6
|
-
mkdir,
|
|
7
|
-
readdir,
|
|
8
|
-
readFile,
|
|
9
|
-
rename,
|
|
10
|
-
rm,
|
|
11
|
-
stat,
|
|
12
|
-
utimes,
|
|
13
|
-
writeFile,
|
|
14
|
-
} from "node:fs/promises";
|
|
15
|
-
import { createServer } from "node:net";
|
|
4
|
+
import { mkdir, readdir, readFile, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
16
5
|
import { tmpdir } from "node:os";
|
|
17
|
-
import { dirname, join,
|
|
6
|
+
import { dirname, join, resolve, win32 } from "node:path";
|
|
18
7
|
import process from "node:process";
|
|
19
8
|
import { fileURLToPath } from "node:url";
|
|
20
9
|
import { collectPreviousDashboardSessions } from "./start-dashboard-history.mjs";
|
|
@@ -25,14 +14,18 @@ import {
|
|
|
25
14
|
presentLaunchFailure,
|
|
26
15
|
writeLauncherStatus,
|
|
27
16
|
} from "./start-dashboard-lifecycle.mjs";
|
|
28
|
-
import { assertDashboardPrerequisites } from "./start-dashboard-prerequisites.mjs";
|
|
17
|
+
import { assertDashboardPrerequisites, exists } from "./start-dashboard-prerequisites.mjs";
|
|
29
18
|
import {
|
|
19
|
+
isKnownDashboardLauncher,
|
|
30
20
|
isProcessAlive,
|
|
31
21
|
stopOwnedDashboardProcess,
|
|
32
22
|
stopProcessTree,
|
|
33
23
|
teardownWarning,
|
|
34
24
|
} from "./start-dashboard-processes.mjs";
|
|
35
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
isPortFree,
|
|
27
|
+
startDashboardServer as launchDashboardServer,
|
|
28
|
+
} from "./start-dashboard-server.mjs";
|
|
36
29
|
|
|
37
30
|
export {
|
|
38
31
|
isAncestorProcess,
|
|
@@ -53,15 +46,22 @@ export const workspaceKey = createHash("sha256")
|
|
|
53
46
|
.slice(0, 16);
|
|
54
47
|
export const sessionDirectory = join(tmpdir(), "zaip-aios", `dashboard-sqlite-${workspaceKey}`);
|
|
55
48
|
export const sessionPath = join(sessionDirectory, "dashboard-sqlite-session.json");
|
|
49
|
+
// This small, token-free record is the observable ownership contract while a
|
|
50
|
+
// dashboard is still preparing. It deliberately does not replace the active
|
|
51
|
+
// runtime session, so a newer click can take priority without losing the PID
|
|
52
|
+
// needed to stop the older runtime.
|
|
53
|
+
export const launchPath = join(sessionDirectory, "dashboard-sqlite-launch.json");
|
|
54
|
+
const claimLockPath = join(sessionDirectory, "dashboard-sqlite-claim.lock");
|
|
55
|
+
const claimLockTimeoutMs = 5_000;
|
|
56
|
+
const claimLockStaleMs = 30_000;
|
|
56
57
|
export const portRegistryPath = join(sessionDirectory, "dashboard-sqlite-ports.json");
|
|
57
58
|
export const lockPath = join(sessionDirectory, "dashboard-sqlite-launcher.lock");
|
|
58
59
|
export const launcherLogPath = join(sessionDirectory, "dashboard-sqlite-launcher.log");
|
|
59
60
|
const frontendRoot = resolve(workspaceRoot, ".agents/dashboard/apps/frontend/dist");
|
|
60
|
-
const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const nodeCompileCacheDirectory = join(sqliteReadModelDirectory, "node-compile-cache");
|
|
61
|
+
const sqliteReadModelDirectory = resolve(workspaceRoot, ".local", "sqlite");
|
|
62
|
+
const sqliteReadModelPath = join(sqliteReadModelDirectory, "zaip-aios-dashboard.sqlite");
|
|
63
|
+
// Bytecode is disposable session data, separate from the persistent database.
|
|
64
|
+
const nodeCompileCacheDirectory = join(sessionDirectory, "node-compile-cache");
|
|
65
65
|
const installedServer = resolve(packageRoot, "server.mjs");
|
|
66
66
|
const sourceServer = resolve(packageRoot, "dist/server.cjs");
|
|
67
67
|
const healthPath = "/api/v2/health";
|
|
@@ -183,6 +183,103 @@ export async function readDashboardSession(path = sessionPath) {
|
|
|
183
183
|
export async function writeDashboardSession(session, path = sessionPath) {
|
|
184
184
|
await writeJsonAtomic(path, session);
|
|
185
185
|
}
|
|
186
|
+
export async function claimDashboardLaunch(
|
|
187
|
+
{ launchPort, token },
|
|
188
|
+
{
|
|
189
|
+
path = launchPath,
|
|
190
|
+
lockPath = claimLockPath,
|
|
191
|
+
stopProcessTreeImpl = stopProcessTree,
|
|
192
|
+
isProcessAliveImpl = isProcessAlive,
|
|
193
|
+
isKnownDashboardLauncherImpl = isKnownDashboardLauncher,
|
|
194
|
+
delayImpl = delay,
|
|
195
|
+
} = {},
|
|
196
|
+
) {
|
|
197
|
+
await mkdir(sessionDirectory, { recursive: true });
|
|
198
|
+
// This lock is deliberately short: it serializes only ownership transfer,
|
|
199
|
+
// never the SQLite build. A later click therefore observes and cancels the
|
|
200
|
+
// immediately preceding claim rather than a stale common predecessor.
|
|
201
|
+
const claimDeadline = Date.now() + claimLockTimeoutMs;
|
|
202
|
+
while (true) {
|
|
203
|
+
try {
|
|
204
|
+
await mkdir(lockPath);
|
|
205
|
+
await writeFile(join(lockPath, "owner"), String(process.pid), "utf8");
|
|
206
|
+
break;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error?.code !== "EEXIST") throw error;
|
|
209
|
+
const details = await stat(lockPath).catch(() => null);
|
|
210
|
+
const ownerPid = Number.parseInt(
|
|
211
|
+
await readFile(join(lockPath, "owner"), "utf8").catch(() => ""),
|
|
212
|
+
10,
|
|
213
|
+
);
|
|
214
|
+
if (Number.isInteger(ownerPid) && ownerPid > 0 && !(await isProcessAliveImpl(ownerPid))) {
|
|
215
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (details !== null && Date.now() - details.mtimeMs > claimLockStaleMs) {
|
|
219
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (Date.now() >= claimDeadline)
|
|
223
|
+
throw new Error("Timed out waiting for the dashboard ownership claim lock.");
|
|
224
|
+
await delayImpl(25);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const previous = await readDashboardSession(path);
|
|
229
|
+
const claim = {
|
|
230
|
+
kind: "zaip-dashboard-sqlite-launch",
|
|
231
|
+
workspace: workspaceRoot,
|
|
232
|
+
workspaceKey,
|
|
233
|
+
launcherPid: process.pid,
|
|
234
|
+
launchPort,
|
|
235
|
+
// Only a non-secret correlation id is persisted for diagnostics.
|
|
236
|
+
claimId: createHash("sha256").update(`${token}:${process.pid}`).digest("hex").slice(0, 16),
|
|
237
|
+
state: "preparing",
|
|
238
|
+
claimedAt: new Date().toISOString(),
|
|
239
|
+
statusPath: process.env.ZAIP_LAUNCH_STATUS ?? null,
|
|
240
|
+
};
|
|
241
|
+
await writeJsonAtomic(path, claim);
|
|
242
|
+
if (
|
|
243
|
+
previous?.kind === claim.kind &&
|
|
244
|
+
previous?.workspaceKey === workspaceKey &&
|
|
245
|
+
Number.isInteger(previous?.launcherPid) &&
|
|
246
|
+
previous.launcherPid > 0 &&
|
|
247
|
+
previous.launcherPid !== process.pid
|
|
248
|
+
) {
|
|
249
|
+
// A claim replacement is also an active cancellation boundary. Do not
|
|
250
|
+
// merely fence stale cleanup while its expensive SQLite build continues.
|
|
251
|
+
if (!(await isKnownDashboardLauncherImpl(previous.launcherPid, packageRoot))) {
|
|
252
|
+
// The PID may have exited or been reused by an unrelated process. Its
|
|
253
|
+
// record is stale; never kill it and let this claim continue.
|
|
254
|
+
return claim;
|
|
255
|
+
}
|
|
256
|
+
if (typeof previous.statusPath === "string" && previous.statusPath.startsWith(tmpdir()))
|
|
257
|
+
await writeFile(
|
|
258
|
+
previous.statusPath,
|
|
259
|
+
"cancelled\nSuperseded by a newer dashboard click.",
|
|
260
|
+
"utf8",
|
|
261
|
+
).catch(() => {});
|
|
262
|
+
await stopProcessTreeImpl(previous.launcherPid);
|
|
263
|
+
const deadline = Date.now() + 5_000;
|
|
264
|
+
while ((await isProcessAliveImpl(previous.launcherPid)) && Date.now() < deadline)
|
|
265
|
+
await delayImpl(50);
|
|
266
|
+
if (await isProcessAliveImpl(previous.launcherPid))
|
|
267
|
+
throw new Error(
|
|
268
|
+
"Could not stop the superseded dashboard launcher before starting a new session.",
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
return claim;
|
|
272
|
+
} finally {
|
|
273
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
export async function isCurrentDashboardLaunch(claim) {
|
|
277
|
+
const current = await readDashboardSession(launchPath);
|
|
278
|
+
return current?.kind === claim.kind && current?.claimId === claim.claimId;
|
|
279
|
+
}
|
|
280
|
+
export async function clearDashboardLaunch(claim) {
|
|
281
|
+
if (await isCurrentDashboardLaunch(claim)) await rm(launchPath, { force: true });
|
|
282
|
+
}
|
|
186
283
|
async function writeJsonAtomic(path, value) {
|
|
187
284
|
const directory = dirname(path);
|
|
188
285
|
await mkdir(directory, { recursive: true });
|
|
@@ -269,11 +366,31 @@ export async function findReusableDashboardSession({
|
|
|
269
366
|
session?.workspaceKey !== expectedWorkspaceKey ||
|
|
270
367
|
typeof session?.token !== "string" ||
|
|
271
368
|
session.token.length === 0 ||
|
|
272
|
-
(expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature)
|
|
273
|
-
(expectedSnapshotState !== null && session?.snapshotState !== expectedSnapshotState)
|
|
369
|
+
(expectedBuildSignature !== null && session?.buildSignature !== expectedBuildSignature)
|
|
274
370
|
) {
|
|
275
371
|
return null;
|
|
276
372
|
}
|
|
373
|
+
// Session metadata is written before the serving process finishes promoting
|
|
374
|
+
// a stale seed. Trust a state transition only after the owned server itself
|
|
375
|
+
// reports it; never reuse an unknown or still-updating generation as current.
|
|
376
|
+
if (expectedSnapshotState !== null && session.snapshotState !== expectedSnapshotState) {
|
|
377
|
+
if (!Number.isInteger(session.port)) return null;
|
|
378
|
+
try {
|
|
379
|
+
const response = await fetchImpl(dashboardHealthUrl(session.port), {
|
|
380
|
+
signal: AbortSignal.timeout(500),
|
|
381
|
+
});
|
|
382
|
+
const payload = await response.json();
|
|
383
|
+
if (
|
|
384
|
+
!response.ok ||
|
|
385
|
+
payload?.kind !== runtimeKind ||
|
|
386
|
+
payload?.workspaceKey !== expectedWorkspaceKey ||
|
|
387
|
+
payload?.snapshot_state !== expectedSnapshotState
|
|
388
|
+
)
|
|
389
|
+
return null;
|
|
390
|
+
} catch {
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
277
394
|
if (
|
|
278
395
|
Number.isInteger(session.port) &&
|
|
279
396
|
(await probeDashboardPort(session.port, fetchImpl, expectedWorkspaceKey))
|
|
@@ -292,9 +409,14 @@ export async function findReusableDashboardSession({
|
|
|
292
409
|
return port === undefined ? null : { ...session, port };
|
|
293
410
|
}
|
|
294
411
|
|
|
295
|
-
|
|
296
|
-
return
|
|
412
|
+
export function requestBackgroundProjectionRefresh(session, fetchImpl = fetch) {
|
|
413
|
+
return fetchImpl(`http://${host}:${session.port}/api/v2/refresh-projection`, {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: { "x-zaip-session": session.token },
|
|
416
|
+
signal: AbortSignal.timeout(750),
|
|
417
|
+
}).catch(() => undefined);
|
|
297
418
|
}
|
|
419
|
+
|
|
298
420
|
export async function latestModifiedAt(paths, excludedSourcePaths = []) {
|
|
299
421
|
let latest = 0;
|
|
300
422
|
for (const path of paths) {
|
|
@@ -478,6 +600,7 @@ export async function withLauncherLock(
|
|
|
478
600
|
timeoutMs = lockTimeoutMs,
|
|
479
601
|
staleAfterMs = staleLockMs,
|
|
480
602
|
onWait = () => {},
|
|
603
|
+
newestClickWins = false,
|
|
481
604
|
} = {},
|
|
482
605
|
) {
|
|
483
606
|
await mkdir(directory, { recursive: true });
|
|
@@ -485,6 +608,9 @@ export async function withLauncherLock(
|
|
|
485
608
|
while (Date.now() < deadline) {
|
|
486
609
|
try {
|
|
487
610
|
await mkdir(path);
|
|
611
|
+
const lockOwner = `${process.pid}-${randomBytes(8).toString("hex")}`;
|
|
612
|
+
const lockOwnerPath = join(path, "owner");
|
|
613
|
+
await writeFile(lockOwnerPath, lockOwner, { encoding: "utf8", mode: 0o600 });
|
|
488
614
|
const heartbeatIntervalMs = Math.max(lockHeartbeatFloorMs, Math.floor(staleAfterMs / 3));
|
|
489
615
|
const heartbeat = setInterval(() => {
|
|
490
616
|
const now = new Date();
|
|
@@ -495,10 +621,20 @@ export async function withLauncherLock(
|
|
|
495
621
|
return await action();
|
|
496
622
|
} finally {
|
|
497
623
|
clearInterval(heartbeat);
|
|
498
|
-
|
|
624
|
+
// Fencing prevents a superseded owner from deleting the newer click's
|
|
625
|
+
// lock directory in its finally block.
|
|
626
|
+
if ((await readFile(lockOwnerPath, "utf8").catch(() => "")) === lockOwner)
|
|
627
|
+
await rm(path, { recursive: true, force: true });
|
|
499
628
|
}
|
|
500
629
|
} catch (error) {
|
|
501
630
|
if (error?.code !== "EEXIST") throw error;
|
|
631
|
+
if (newestClickWins) {
|
|
632
|
+
// The previous holder checks its launch claim at every expensive
|
|
633
|
+
// boundary. Replacing the directory lets the latest executable click
|
|
634
|
+
// proceed immediately instead of presenting a waiting screen.
|
|
635
|
+
await rm(path, { recursive: true, force: true });
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
502
638
|
const lockStat = await stat(path).catch(() => null);
|
|
503
639
|
if (lockStat !== null && Date.now() - lockStat.mtimeMs > staleAfterMs) {
|
|
504
640
|
await rm(path, { recursive: true, force: true });
|
|
@@ -512,7 +648,7 @@ export async function withLauncherLock(
|
|
|
512
648
|
}
|
|
513
649
|
export async function findAvailablePort(startPort = preferredPort()) {
|
|
514
650
|
for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
|
|
515
|
-
if (await isPortFree(port)) return port;
|
|
651
|
+
if (await isPortFree(host, port)) return port;
|
|
516
652
|
}
|
|
517
653
|
throw new Error(
|
|
518
654
|
`No free dashboard port found between ${startPort} and ${Math.min(startPort + maxPortAttempts - 1, 65_535)}`,
|
|
@@ -528,7 +664,7 @@ export async function findAvailablePortWithFallback({
|
|
|
528
664
|
);
|
|
529
665
|
for (const startPort of fallbackStarts) {
|
|
530
666
|
for (let port = startPort; port < Math.min(startPort + maxPortAttempts, 65_536); port += 1) {
|
|
531
|
-
if (!excluded.has(port) && (await isPortFree(port))) return port;
|
|
667
|
+
if (!excluded.has(port) && (await isPortFree(host, port))) return port;
|
|
532
668
|
}
|
|
533
669
|
}
|
|
534
670
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
@@ -677,167 +813,30 @@ async function startDashboardServer(
|
|
|
677
813
|
excludedPorts,
|
|
678
814
|
initialPort = null,
|
|
679
815
|
onProgress = () => {},
|
|
816
|
+
onChildStarted = () => {},
|
|
680
817
|
) {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
`read-model-${port}-${process.pid}-${attempt}.sqlite`,
|
|
691
|
-
);
|
|
692
|
-
let stderr = "";
|
|
693
|
-
let startupTelemetryBuffer = "";
|
|
694
|
-
let snapshotCache = null;
|
|
695
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
696
|
-
cwd: packageRoot,
|
|
697
|
-
env: {
|
|
698
|
-
...process.env,
|
|
699
|
-
ZAIP_WORKSPACE: workspaceRoot,
|
|
700
|
-
ZAIP_UI_ROOT: frontendRoot,
|
|
701
|
-
ZAIP_UI_TOKEN: token,
|
|
702
|
-
ZAIP_SERVER_PORT: String(port),
|
|
703
|
-
NODE_COMPILE_CACHE: nodeCompileCacheDirectory,
|
|
704
|
-
NODE_NO_WARNINGS: "1",
|
|
705
|
-
ZAIP_DASHBOARD_LAUNCH_PROGRESS: "1",
|
|
706
|
-
ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "1",
|
|
707
|
-
ZAIP_SQLITE_CACHE_DIR: sqliteSnapshotCacheDirectory,
|
|
708
|
-
// A retry or a fresh-port handoff never reuses a live SQLite file.
|
|
709
|
-
// Windows keeps an open read-only handle locked, so each child owns a
|
|
710
|
-
// generation identified by launcher PID, attempt and selected port.
|
|
711
|
-
ZAIP_SQLITE_DB_PATH: dbPath,
|
|
712
|
-
},
|
|
713
|
-
stdio: ["ignore", "ignore", "pipe"],
|
|
714
|
-
windowsHide: true,
|
|
715
|
-
});
|
|
716
|
-
child.stderr?.setEncoding("utf8");
|
|
717
|
-
child.stderr?.on("data", (chunk) => {
|
|
718
|
-
stderr = `${stderr}${chunk}`.slice(-4_000);
|
|
719
|
-
startupTelemetryBuffer = `${startupTelemetryBuffer}${String(chunk)}`;
|
|
720
|
-
const lines = startupTelemetryBuffer.split("\n");
|
|
721
|
-
startupTelemetryBuffer = lines.pop() ?? "";
|
|
722
|
-
for (const line of lines) {
|
|
723
|
-
try {
|
|
724
|
-
const event = JSON.parse(line);
|
|
725
|
-
if (event?.event === "dashboard_sqlite_rebuild_progress")
|
|
726
|
-
onProgress({
|
|
727
|
-
percent: event.percent,
|
|
728
|
-
phase: "Configurando o banco SQLite local",
|
|
729
|
-
detail:
|
|
730
|
-
"Esta é a instalação/atualização da cópia local descartável; nenhum servidor externo é instalado.",
|
|
731
|
-
});
|
|
732
|
-
if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "main_entered")
|
|
733
|
-
onProgress({
|
|
734
|
-
percent: 50,
|
|
735
|
-
phase: "Lendo os registros do projeto",
|
|
736
|
-
detail: "Preparando o read model SQLite local.",
|
|
737
|
-
});
|
|
738
|
-
if (event?.event === "dashboard_sqlite_read_model") snapshotCache = event.cache;
|
|
739
|
-
if (event?.event === "dashboard_sqlite_read_model")
|
|
740
|
-
onProgress({
|
|
741
|
-
percent: 82,
|
|
742
|
-
phase: "Banco local pronto",
|
|
743
|
-
detail:
|
|
744
|
-
event.cache === "rebuilt"
|
|
745
|
-
? "O read model SQLite foi reconstruído para os dados atuais."
|
|
746
|
-
: "Uma cópia local do read model foi preparada.",
|
|
747
|
-
});
|
|
748
|
-
if (event?.event === "dashboard_sqlite_startup_phase" && event.phase === "listening")
|
|
749
|
-
onProgress({
|
|
750
|
-
percent: 88,
|
|
751
|
-
phase: "Iniciando a interface",
|
|
752
|
-
detail: "Verificando saúde e disponibilidade dos dados.",
|
|
753
|
-
});
|
|
754
|
-
} catch {
|
|
755
|
-
// Only structured startup telemetry affects the user-facing progress.
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
try {
|
|
760
|
-
await waitForDashboard(port, child);
|
|
761
|
-
const timings = stripTerminalControlCodes(stderr)
|
|
762
|
-
.split("\n")
|
|
763
|
-
.filter(
|
|
764
|
-
(line) =>
|
|
765
|
-
line.includes('"event":"dashboard_sqlite_read_model"') ||
|
|
766
|
-
line.includes('"event":"dashboard_sqlite_startup_phase"'),
|
|
767
|
-
);
|
|
768
|
-
for (const timing of timings) appendLauncherLog(timing);
|
|
769
|
-
return { child, port, dbPath, snapshotCache };
|
|
770
|
-
} catch (error) {
|
|
771
|
-
await stopProcessTree(child.pid);
|
|
772
|
-
await removeGeneratedReadModel(dbPath);
|
|
773
|
-
const detail = stripTerminalControlCodes(stderr)
|
|
774
|
-
.split("\n")
|
|
775
|
-
.filter(
|
|
776
|
-
(line) =>
|
|
777
|
-
!line.includes("ExperimentalWarning") && !line.includes("node --trace-warnings"),
|
|
778
|
-
)
|
|
779
|
-
.join("\n")
|
|
780
|
-
.trim();
|
|
781
|
-
failures.push(
|
|
782
|
-
`porta ${port}: ${error.message}${detail ? ` — ${formatLauncherError(detail, 600)}` : ""}`,
|
|
783
|
-
);
|
|
784
|
-
appendLauncherLog(
|
|
785
|
-
`Dashboard start attempt ${attempt} failed on port ${port}: ${error.message}`,
|
|
786
|
-
);
|
|
787
|
-
if (error.retryable === false) break;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
throw new Error(
|
|
791
|
-
`Não foi possível iniciar o dashboard após os fallbacks. ${failures.join(" | ")}`,
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
async function removeGeneratedReadModel(candidate) {
|
|
796
|
-
if (typeof candidate !== "string") return false;
|
|
797
|
-
const resolvedCandidate = resolve(candidate);
|
|
798
|
-
const fromReadModelDirectory = relative(sqliteReadModelDirectory, resolvedCandidate);
|
|
799
|
-
const generatedName = /^read-model-\d+-\d+-\d+\.sqlite$/;
|
|
800
|
-
if (
|
|
801
|
-
fromReadModelDirectory === "" ||
|
|
802
|
-
fromReadModelDirectory.startsWith("..") ||
|
|
803
|
-
fromReadModelDirectory.includes("/") ||
|
|
804
|
-
fromReadModelDirectory.includes("\\") ||
|
|
805
|
-
!generatedName.test(fromReadModelDirectory)
|
|
806
|
-
)
|
|
807
|
-
return false;
|
|
808
|
-
for (let attempt = 1; attempt <= 20; attempt += 1) {
|
|
809
|
-
try {
|
|
810
|
-
await rm(resolvedCandidate, { force: true });
|
|
811
|
-
return true;
|
|
812
|
-
} catch (error) {
|
|
813
|
-
if (error?.code !== "EBUSY" && error?.code !== "EPERM") throw error;
|
|
814
|
-
if (attempt < 20) await delay(250);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
appendLauncherLog(
|
|
818
|
-
`Could not remove closed SQLite read model after retries: ${resolvedCandidate}`,
|
|
819
|
-
);
|
|
820
|
-
return false;
|
|
821
|
-
}
|
|
822
|
-
async function refreshSnapshotSeedInBackground() {
|
|
823
|
-
const refreshStatusPath = join(sessionDirectory, "dashboard-sqlite-refresh.json");
|
|
824
|
-
const refreshLogPath = join(sessionDirectory, "dashboard-sqlite-refresh.log");
|
|
825
|
-
return startSeedRefresh({
|
|
826
|
-
appendLog: appendLauncherLog,
|
|
827
|
-
cacheDirectory: sqliteSnapshotCacheDirectory,
|
|
828
|
-
isRunning: backgroundRefreshIsRunning,
|
|
829
|
-
isProcessAlive,
|
|
830
|
-
lockPath: join(sessionDirectory, "dashboard-sqlite-refresh.lock"),
|
|
818
|
+
return launchDashboardServer({
|
|
819
|
+
serverPath,
|
|
820
|
+
token,
|
|
821
|
+
excludedPorts,
|
|
822
|
+
initialPort,
|
|
823
|
+
onProgress,
|
|
824
|
+
onChildStarted,
|
|
825
|
+
maxAttempts: maxServerStartAttempts,
|
|
826
|
+
dbPath: sqliteReadModelPath,
|
|
831
827
|
packageRoot,
|
|
832
|
-
randomBytes,
|
|
833
|
-
refreshLogPath,
|
|
834
|
-
refreshStatusPath,
|
|
835
|
-
sqliteDirectory: sqliteReadModelDirectory,
|
|
836
|
-
stopProcessTree,
|
|
837
828
|
workspaceRoot,
|
|
838
|
-
|
|
829
|
+
frontendRoot,
|
|
830
|
+
compileCacheDirectory: nodeCompileCacheDirectory,
|
|
831
|
+
findPort: findAvailablePortWithFallback,
|
|
832
|
+
waitForDashboard,
|
|
833
|
+
stopProcessTree,
|
|
834
|
+
sanitize: stripTerminalControlCodes,
|
|
835
|
+
formatError: formatLauncherError,
|
|
836
|
+
appendLog: appendLauncherLog,
|
|
839
837
|
});
|
|
840
838
|
}
|
|
839
|
+
|
|
841
840
|
async function main() {
|
|
842
841
|
let ownedChild = null;
|
|
843
842
|
let currentSession = null;
|
|
@@ -851,6 +850,25 @@ async function main() {
|
|
|
851
850
|
// probe mistake its intentional 503 for a server failure.
|
|
852
851
|
launchPage = await startDashboardLaunchPage(0);
|
|
853
852
|
const launchPort = launchPage.port;
|
|
853
|
+
const launchClaim = await claimDashboardLaunch({ launchPort, token });
|
|
854
|
+
let browserClosed = false;
|
|
855
|
+
void launchPage.waitForBrowserDeparture().then(() => {
|
|
856
|
+
if (launchPage === null) return;
|
|
857
|
+
browserClosed = true;
|
|
858
|
+
if (ownedChild !== null) void stopProcessTree(ownedChild.pid);
|
|
859
|
+
});
|
|
860
|
+
const assertCurrentLaunch = async () => {
|
|
861
|
+
if (browserClosed) {
|
|
862
|
+
const closed = new Error("The dashboard browser was closed during preparation.");
|
|
863
|
+
closed.code = "ZAIP_DASHBOARD_BROWSER_CLOSED";
|
|
864
|
+
throw closed;
|
|
865
|
+
}
|
|
866
|
+
if (!(await isCurrentDashboardLaunch(launchClaim))) {
|
|
867
|
+
const replacement = new Error("Dashboard launch was superseded by a newer executable click.");
|
|
868
|
+
replacement.code = "ZAIP_DASHBOARD_SUPERSEDED";
|
|
869
|
+
throw replacement;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
854
872
|
try {
|
|
855
873
|
if (!(await openBrowser(dashboardUrl(launchPort, token)))) {
|
|
856
874
|
throw new Error("Could not open the dashboard preparation page in the default browser.");
|
|
@@ -862,6 +880,7 @@ async function main() {
|
|
|
862
880
|
"Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
|
|
863
881
|
});
|
|
864
882
|
await assertDashboardPrerequisites(workspaceRoot);
|
|
883
|
+
await assertCurrentLaunch();
|
|
865
884
|
launchPage.setProgress({
|
|
866
885
|
percent: 18,
|
|
867
886
|
phase: "Preparando a sessão do dashboard",
|
|
@@ -870,6 +889,7 @@ async function main() {
|
|
|
870
889
|
let reportedLockWait = false;
|
|
871
890
|
await withLauncherLock(
|
|
872
891
|
async () => {
|
|
892
|
+
await assertCurrentLaunch();
|
|
873
893
|
phaseTiming("lock_acquired", launchStartedAt);
|
|
874
894
|
const buildStartedAt = Date.now();
|
|
875
895
|
launchPage.setProgress({
|
|
@@ -880,42 +900,16 @@ async function main() {
|
|
|
880
900
|
const buildSignature = await ensureFreshDashboardBuild((progress) =>
|
|
881
901
|
launchPage?.setProgress(progress),
|
|
882
902
|
);
|
|
903
|
+
await assertCurrentLaunch();
|
|
883
904
|
phaseTiming("build_complete", buildStartedAt, { build_signature: buildSignature });
|
|
884
|
-
// A second deliberate click must use the already healthy compatible
|
|
885
|
-
// runtime. Build signature makes this safe across dashboard updates;
|
|
886
|
-
// an incompatible session continues through a replacement handoff.
|
|
887
|
-
const reusable = await findReusableDashboardSession({
|
|
888
|
-
expectedBuildSignature: buildSignature,
|
|
889
|
-
expectedSnapshotState: "current",
|
|
890
|
-
});
|
|
891
|
-
if (reusable !== null) {
|
|
892
|
-
appendLauncherLog(
|
|
893
|
-
`Reused healthy compatible dashboard session on port ${reusable.port}, PID ${reusable.serverPid ?? "unknown"}.`,
|
|
894
|
-
);
|
|
895
|
-
await redirectLaunchPage(launchPage, dashboardUrl(reusable.port, reusable.token));
|
|
896
|
-
launchPage = null;
|
|
897
|
-
await writeLauncherStatus("ok");
|
|
898
|
-
return;
|
|
899
|
-
}
|
|
900
|
-
// A stale seed is still a valid read-only dashboard while its one
|
|
901
|
-
// canonical refresh runs. Reuse it instead of turning repeated clicks
|
|
902
|
-
// into competing full SQLite rebuilds and private server generations.
|
|
903
|
-
if (await backgroundRefreshIsRunning()) {
|
|
904
|
-
const updating = await findReusableDashboardSession({
|
|
905
|
-
expectedBuildSignature: buildSignature,
|
|
906
|
-
expectedSnapshotState: "updating",
|
|
907
|
-
});
|
|
908
|
-
if (updating !== null) {
|
|
909
|
-
appendLauncherLog(
|
|
910
|
-
`Reused updating dashboard session on port ${updating.port}; canonical seed refresh is already running.`,
|
|
911
|
-
);
|
|
912
|
-
await redirectLaunchPage(launchPage, dashboardUrl(updating.port, updating.token, true));
|
|
913
|
-
launchPage = null;
|
|
914
|
-
await writeLauncherStatus("ok");
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
905
|
const previousSessions = await previousDashboardSessions();
|
|
906
|
+
// The current executable is authoritative. Retire every verified
|
|
907
|
+
// previous owner before allocating the new generation, never reuse it.
|
|
908
|
+
for (const previous of previousSessions) {
|
|
909
|
+
const status = await stopOwnedDashboard(previous);
|
|
910
|
+
await recordPortStatus(previous, status);
|
|
911
|
+
}
|
|
912
|
+
await assertCurrentLaunch();
|
|
919
913
|
const excludedPorts = new Set([
|
|
920
914
|
...previousSessions.map((session) => session.port),
|
|
921
915
|
launchPort,
|
|
@@ -925,7 +919,7 @@ async function main() {
|
|
|
925
919
|
launchPage.setProgress({
|
|
926
920
|
percent: 42,
|
|
927
921
|
phase: "Abrindo o banco local descartável",
|
|
928
|
-
detail: "
|
|
922
|
+
detail: "Abrindo o banco SQLite persistente deste workspace.",
|
|
929
923
|
});
|
|
930
924
|
const { child, port, dbPath, snapshotCache } = await startDashboardServer(
|
|
931
925
|
serverPath,
|
|
@@ -933,6 +927,9 @@ async function main() {
|
|
|
933
927
|
excludedPorts,
|
|
934
928
|
null,
|
|
935
929
|
(progress) => launchPage?.setProgress(progress),
|
|
930
|
+
(child) => {
|
|
931
|
+
ownedChild = child;
|
|
932
|
+
},
|
|
936
933
|
);
|
|
937
934
|
phaseTiming("server_ready", readyStartedAt, {
|
|
938
935
|
server_pid: child.pid,
|
|
@@ -949,7 +946,7 @@ async function main() {
|
|
|
949
946
|
: "Dashboard pronto",
|
|
950
947
|
detail:
|
|
951
948
|
snapshotCache === "stale_seed"
|
|
952
|
-
? "Os dados canônicos estão sendo atualizados em segundo plano
|
|
949
|
+
? "Os dados canônicos estão sendo atualizados em segundo plano nesta sessão."
|
|
953
950
|
: "Validando saúde e dados iniciais antes de abrir a tela principal.",
|
|
954
951
|
});
|
|
955
952
|
if (snapshotCache === "stale_seed") await delay(350);
|
|
@@ -957,9 +954,9 @@ async function main() {
|
|
|
957
954
|
launchPage,
|
|
958
955
|
dashboardUrl(port, token, snapshotCache === "stale_seed"),
|
|
959
956
|
);
|
|
957
|
+
await assertCurrentLaunch();
|
|
960
958
|
phaseTiming("handoff_complete", launchStartedAt, { server_pid: child.pid, port });
|
|
961
959
|
launchPage = null;
|
|
962
|
-
if (snapshotCache === "stale_seed") await refreshSnapshotSeedInBackground();
|
|
963
960
|
currentSession = {
|
|
964
961
|
kind: runtimeKind,
|
|
965
962
|
workspace: workspaceRoot,
|
|
@@ -971,19 +968,20 @@ async function main() {
|
|
|
971
968
|
dbPath,
|
|
972
969
|
buildSignature,
|
|
973
970
|
generationId: `${process.pid}-${child.pid}-${port}-${randomBytes(6).toString("hex")}`,
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
//
|
|
971
|
+
// The serving process owns the stale-seed refresh and promotes it
|
|
972
|
+
// into this session, so a later click can reuse the current
|
|
973
|
+
// generation after the health state flips.
|
|
977
974
|
snapshotState: snapshotCache === "stale_seed" ? "updating" : "current",
|
|
978
975
|
createdAt: new Date().toISOString(),
|
|
979
976
|
};
|
|
977
|
+
await assertCurrentLaunch();
|
|
980
978
|
await writeDashboardSession(currentSession);
|
|
979
|
+
await writeJsonAtomic(launchPath, { ...launchClaim, state: "ready", port });
|
|
981
980
|
await recordPortStatus(currentSession, "active");
|
|
982
981
|
const warnings = [];
|
|
983
982
|
for (const previous of previousSessions) {
|
|
984
983
|
const status = await stopOwnedDashboard(previous);
|
|
985
984
|
await recordPortStatus(previous, status);
|
|
986
|
-
if (status === "closed") await removeGeneratedReadModel(previous.dbPath);
|
|
987
985
|
if (teardownWarning(status))
|
|
988
986
|
warnings.push(`não foi possível fechar a porta anterior ${previous.port}`);
|
|
989
987
|
else if (status === "skipped_unowned")
|
|
@@ -1004,6 +1002,7 @@ async function main() {
|
|
|
1004
1002
|
}
|
|
1005
1003
|
},
|
|
1006
1004
|
{
|
|
1005
|
+
newestClickWins: true,
|
|
1007
1006
|
onWait: () => {
|
|
1008
1007
|
if (reportedLockWait) return;
|
|
1009
1008
|
reportedLockWait = true;
|
|
@@ -1016,6 +1015,13 @@ async function main() {
|
|
|
1016
1015
|
},
|
|
1017
1016
|
);
|
|
1018
1017
|
} catch (error) {
|
|
1018
|
+
if (
|
|
1019
|
+
error?.code === "ZAIP_DASHBOARD_SUPERSEDED" ||
|
|
1020
|
+
error?.code === "ZAIP_DASHBOARD_BROWSER_CLOSED"
|
|
1021
|
+
) {
|
|
1022
|
+
await writeLauncherStatus("cancelled").catch(() => {});
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1019
1025
|
launchPage = presentLaunchFailure(launchPage, error, formatLauncherError);
|
|
1020
1026
|
throw error;
|
|
1021
1027
|
}
|
|
@@ -1025,16 +1031,8 @@ async function main() {
|
|
|
1025
1031
|
currentSession,
|
|
1026
1032
|
clearOwnedDashboardSession,
|
|
1027
1033
|
recordPortStatus,
|
|
1028
|
-
removeGeneratedReadModel,
|
|
1029
|
-
});
|
|
1030
|
-
}
|
|
1031
|
-
function isPortFree(port) {
|
|
1032
|
-
return new Promise((resolvePort) => {
|
|
1033
|
-
const server = createServer();
|
|
1034
|
-
server.unref();
|
|
1035
|
-
server.once("error", () => resolvePort(false));
|
|
1036
|
-
server.listen({ host, port }, () => server.close(() => resolvePort(true)));
|
|
1037
1034
|
});
|
|
1035
|
+
await clearDashboardLaunch(launchClaim);
|
|
1038
1036
|
}
|
|
1039
1037
|
async function waitForDashboard(port, child) {
|
|
1040
1038
|
// A cold SQLite read model has no meaningful fixed duration: canonical
|
|
@@ -1075,12 +1073,6 @@ async function waitForDashboard(port, child) {
|
|
|
1075
1073
|
await delay(100);
|
|
1076
1074
|
}
|
|
1077
1075
|
}
|
|
1078
|
-
function exists(path) {
|
|
1079
|
-
return access(path).then(
|
|
1080
|
-
() => true,
|
|
1081
|
-
() => false,
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
1076
|
function delay(milliseconds) {
|
|
1085
1077
|
return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
|
|
1086
1078
|
}
|