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