@zaip-ai/cli 4.5.0 → 4.7.0
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 +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.d.ts.map +1 -1
- package/dist/services/adapters.js +6 -6
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-config-overlay.d.ts.map +1 -1
- package/dist/services/codex-config-overlay.js +27 -10
- package/dist/services/codex-config-overlay.js.map +1 -1
- package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
- package/dist/services/codex-native-tool-authority-template.js +188 -9
- package/dist/services/codex-native-tool-authority-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +50 -128
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +1 -4
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
- package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
- package/dist/services/codex-stale-git-lock-template.js +64 -0
- package/dist/services/codex-stale-git-lock-template.js.map +1 -0
- package/dist/services/global-codex-config-migration.test.js +1 -4
- package/dist/services/global-codex-config-migration.test.js.map +1 -1
- package/dist/services/global-codex-config.d.ts.map +1 -1
- package/dist/services/global-codex-config.js +2 -1
- package/dist/services/global-codex-config.js.map +1 -1
- package/dist/services/global-codex-config.test.js +3 -1
- package/dist/services/global-codex-config.test.js.map +1 -1
- package/dist/services/hook-readiness.d.ts.map +1 -1
- package/dist/services/hook-readiness.js.map +1 -1
- package/dist/services/install-agents-template.js +2 -2
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/install-template-policy.d.ts.map +1 -1
- package/dist/services/install-template-policy.js +2 -0
- package/dist/services/install-template-policy.js.map +1 -1
- package/dist/services/logger.d.ts +1 -1
- package/dist/services/logger.d.ts.map +1 -1
- package/dist/services/logger.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +77 -0
- package/dist/services/logger.test.js.map +1 -1
- package/dist/services/runtime-template-integrity.test.d.ts +2 -0
- package/dist/services/runtime-template-integrity.test.d.ts.map +1 -0
- package/dist/services/runtime-template-integrity.test.js +26 -0
- package/dist/services/runtime-template-integrity.test.js.map +1 -0
- package/dist/services/runtime-template-test-support.d.ts +3 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -0
- package/dist/services/runtime-template-test-support.js +32 -0
- package/dist/services/runtime-template-test-support.js.map +1 -0
- package/dist/services/runtime-template.test.js +37 -48
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README.md +8 -0
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/config/deploy-policy.json +5 -0
- package/templates/.agents/dashboard/.zaip-managed.json +235 -83
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-OUjdwEcM.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-Cei1ChjE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-_woNCrTT.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-B2_7MVOP.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-C_jCsSZe.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C8zQDBT7.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-iMPXot7Y.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DiCwz0sr.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-721Re3Ss.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-BjdTkWS0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.macro-metrics.test.ts +7 -2
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +6 -6
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/overview-duration-hint.ts +14 -0
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +285 -205
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +318 -236
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-summary-elapsed.ts +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-summary.ts +41 -54
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +7 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-counts-every-root-execution-day-returns.test.ts +5 -5
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-multi-provider.test.ts +4 -4
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +13 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-base-views.ts +9 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-schema-dashboard-views.ts +10 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed-view.ts +61 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-spec-root-elapsed.test.ts +210 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
- package/templates/.agents/design/.zaip-managed.json +17 -5
- package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
- package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- 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 +1 -1
- 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 +21 -6
- package/templates/.agents/requirements/public-distribution.md +4 -4
- 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/core.md +3 -1
- package/templates/.agents/rules/workflow.md +3 -2
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +368 -199
- package/templates/.agents/runtime/checksums.sha256 +5 -5
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/provenance.json +4 -4
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
- package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +38 -48
- package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +307 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +337 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +46 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +100 -44
- package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
- package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
- package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
- package/templates/template-manifest.json +354 -150
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
4
|
+
import type { Hono } from "hono";
|
|
5
|
+
import {
|
|
6
|
+
type AgentAdapterSyncResult,
|
|
7
|
+
AgentCatalogUpdateError,
|
|
8
|
+
persistAgentCatalogUpdate,
|
|
9
|
+
readAgentCatalogSettings,
|
|
10
|
+
} from "./agent-catalog-settings.js";
|
|
11
|
+
import { agentPortraitFiles } from "./server-agent-assets.js";
|
|
12
|
+
import { API_PREFIX, headOrJson } from "./server-http-contracts.js";
|
|
13
|
+
|
|
14
|
+
export function registerAgentCatalogRoutes(
|
|
15
|
+
app: Hono,
|
|
16
|
+
options: {
|
|
17
|
+
filesystem: WorkspaceFileSystem;
|
|
18
|
+
workspaceRoot: string;
|
|
19
|
+
syncAgentAdapters: () => Promise<AgentAdapterSyncResult>;
|
|
20
|
+
clearSnapshotCache: () => void;
|
|
21
|
+
},
|
|
22
|
+
): void {
|
|
23
|
+
const { filesystem, workspaceRoot, syncAgentAdapters, clearSnapshotCache } = options;
|
|
24
|
+
let catalogWriteTail = Promise.resolve();
|
|
25
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/agent-catalog`, async (context) => {
|
|
26
|
+
try {
|
|
27
|
+
return headOrJson(context, await readAgentCatalogSettings(filesystem));
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return headOrJson(
|
|
30
|
+
context,
|
|
31
|
+
{ error: error instanceof Error ? error.message : String(error) },
|
|
32
|
+
400,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
app.get(`${API_PREFIX}/agent-catalog-assets/:agentId`, async (context) => {
|
|
37
|
+
const portrait = agentPortraitFiles[context.req.param("agentId")];
|
|
38
|
+
if (portrait === undefined) return context.json({ error: "Agent portrait not found" }, 404);
|
|
39
|
+
try {
|
|
40
|
+
return context.body(
|
|
41
|
+
await readFile(resolve(workspaceRoot, ".agents/config/src", portrait)),
|
|
42
|
+
200,
|
|
43
|
+
{ "cache-control": "no-store", "content-type": "image/png" },
|
|
44
|
+
);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
47
|
+
return context.json({ error: "Agent portrait not found" }, 404);
|
|
48
|
+
}
|
|
49
|
+
return context.json({ error: "Agent portrait could not be read" }, 500);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
app.put(`${API_PREFIX}/agent-catalog`, async (context) => {
|
|
53
|
+
const update = async () => {
|
|
54
|
+
try {
|
|
55
|
+
const persisted = await persistAgentCatalogUpdate(
|
|
56
|
+
filesystem,
|
|
57
|
+
await context.req.json(),
|
|
58
|
+
syncAgentAdapters,
|
|
59
|
+
);
|
|
60
|
+
clearSnapshotCache();
|
|
61
|
+
return context.json(persisted);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof AgentCatalogUpdateError) {
|
|
64
|
+
return context.json({ error: error.message }, error.status);
|
|
65
|
+
}
|
|
66
|
+
return context.json({ error: "Agent catalog update failed" }, 500);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const result = catalogWriteTail.then(update, update);
|
|
70
|
+
catalogWriteTail = result.then(
|
|
71
|
+
() => undefined,
|
|
72
|
+
() => undefined,
|
|
73
|
+
);
|
|
74
|
+
return result;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -207,6 +207,11 @@ describe("dashboard backend", () => {
|
|
|
207
207
|
{ id: "RUN-FAILED", reason: "Could not read Run." },
|
|
208
208
|
{ id: "TASK-DEFERRED", reason: "Canonical gates are incomplete." },
|
|
209
209
|
],
|
|
210
|
+
outcomes: [
|
|
211
|
+
{ id: "CHG-COMPLETED", status: "completed", reason: null },
|
|
212
|
+
{ id: "TASK-BLOCKED", status: "blocked", reason: "Missing finalizer." },
|
|
213
|
+
{ id: "TASK-COMPLETED", status: "completed", reason: null },
|
|
214
|
+
],
|
|
210
215
|
});
|
|
211
216
|
});
|
|
212
217
|
it("requires the local session for projections", async () => {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import type { WorkspaceLayout } from "@zaip-ai/core";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { createDashboardApp } from "./server.js";
|
|
6
|
+
import { ReconciliationCommandTimeoutError } from "./server-workspace.js";
|
|
7
|
+
|
|
8
|
+
const roots: string[] = [];
|
|
9
|
+
async function fixture() {
|
|
10
|
+
const base = resolve(import.meta.dirname, "../../../../../../.tmp/dashboard-server-diagnostics");
|
|
11
|
+
await mkdir(base, { recursive: true });
|
|
12
|
+
const root = await mkdtemp(join(base, "fixture-"));
|
|
13
|
+
roots.push(root);
|
|
14
|
+
return root;
|
|
15
|
+
}
|
|
16
|
+
async function events(root: string) {
|
|
17
|
+
const directory = join(root, ".local/dashboard-logs");
|
|
18
|
+
const content = (
|
|
19
|
+
await Promise.all(
|
|
20
|
+
(await readdir(directory)).map((name) => readFile(join(directory, name), "utf8")),
|
|
21
|
+
)
|
|
22
|
+
).join("");
|
|
23
|
+
return content
|
|
24
|
+
.trim()
|
|
25
|
+
.split("\n")
|
|
26
|
+
.map((line) => JSON.parse(line));
|
|
27
|
+
}
|
|
28
|
+
const layout = { version: 3 } as WorkspaceLayout;
|
|
29
|
+
const headers = { "x-zaip-session": "test-session" };
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
32
|
+
});
|
|
33
|
+
describe("manual reconciliation observable boundaries", () => {
|
|
34
|
+
it("keeps later unrelated refreshes separate from the completed reconciliation job", async () => {
|
|
35
|
+
const root = await fixture();
|
|
36
|
+
const app = createDashboardApp(root, "test-session", undefined, {
|
|
37
|
+
layout,
|
|
38
|
+
snapshotReadOnly: true,
|
|
39
|
+
reconcileOpenWork: async () => ({
|
|
40
|
+
reconciled_ids: ["RUN-X"],
|
|
41
|
+
blocked: [],
|
|
42
|
+
not_reconciled: [],
|
|
43
|
+
}),
|
|
44
|
+
refreshReadModel: async () => undefined,
|
|
45
|
+
});
|
|
46
|
+
await app.request("/api/v2/reconcile-open-work", { method: "POST", headers });
|
|
47
|
+
await vi.waitFor(async () =>
|
|
48
|
+
expect(
|
|
49
|
+
(await events(root)).some((item) => item.event === "projection_refresh_completed"),
|
|
50
|
+
).toBe(true),
|
|
51
|
+
);
|
|
52
|
+
await app.request("/api/v2/refresh-projection", { method: "POST", headers });
|
|
53
|
+
const completed = (await events(root)).filter(
|
|
54
|
+
(item) => item.event === "projection_refresh_completed",
|
|
55
|
+
);
|
|
56
|
+
expect(completed[0].job_id).toEqual(expect.any(String));
|
|
57
|
+
expect(completed.at(-1).job_id).toBe(null);
|
|
58
|
+
expect(completed.at(-1).job_ids).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
it("logs request, job completion and asynchronous refresh failure without false promotion", async () => {
|
|
61
|
+
const root = await fixture();
|
|
62
|
+
const app = createDashboardApp(root, "test-session", undefined, {
|
|
63
|
+
layout,
|
|
64
|
+
snapshotReadOnly: true,
|
|
65
|
+
reconcileOpenWork: async () => ({
|
|
66
|
+
reconciled_ids: ["RUN-X"],
|
|
67
|
+
blocked: [],
|
|
68
|
+
not_reconciled: [],
|
|
69
|
+
}),
|
|
70
|
+
refreshReadModel: async () => {
|
|
71
|
+
throw new Error("simulated refresh failure");
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const response = await app.request("/api/v2/reconcile-open-work", { method: "POST", headers });
|
|
75
|
+
expect(response.status).toBe(200);
|
|
76
|
+
const body = (await response.json()) as { reconciliation: { diagnostics: { job_id: string } } };
|
|
77
|
+
expect(body.reconciliation.diagnostics).toMatchObject({
|
|
78
|
+
job_id: expect.any(String),
|
|
79
|
+
completed_at: expect.any(String),
|
|
80
|
+
latest_token_collected_at: null,
|
|
81
|
+
projection_updated_at: null,
|
|
82
|
+
});
|
|
83
|
+
await vi.waitFor(async () => {
|
|
84
|
+
const records = await events(root);
|
|
85
|
+
expect(records.map((item) => item.event)).toContain("projection_refresh_failed");
|
|
86
|
+
});
|
|
87
|
+
const records = await events(root);
|
|
88
|
+
const job = body.reconciliation.diagnostics.job_id;
|
|
89
|
+
expect(records.find((item) => item.event === "projection_refresh_failed").job_id).toBe(job);
|
|
90
|
+
expect(records.some((item) => item.event === "projection_refresh_completed")).toBe(false);
|
|
91
|
+
expect(records.some((item) => item.event === "reconciliation_button_received")).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it("logs timeout/partial failure and attempts snapshot refresh with a correlated finite response", async () => {
|
|
94
|
+
const root = await fixture();
|
|
95
|
+
const refresh = vi.fn(async () => undefined);
|
|
96
|
+
const app = createDashboardApp(root, "test-session", undefined, {
|
|
97
|
+
layout,
|
|
98
|
+
snapshotReadOnly: true,
|
|
99
|
+
reconcileOpenWork: async () => {
|
|
100
|
+
throw new ReconciliationCommandTimeoutError();
|
|
101
|
+
},
|
|
102
|
+
refreshReadModel: refresh,
|
|
103
|
+
});
|
|
104
|
+
const response = await app.request("/api/v2/reconcile-open-work", { method: "POST", headers });
|
|
105
|
+
expect(response.status).toBe(408);
|
|
106
|
+
expect(await response.json()).toMatchObject({
|
|
107
|
+
reconciliation_may_be_partial: true,
|
|
108
|
+
projection_refresh_started: true,
|
|
109
|
+
request_id: expect.any(String),
|
|
110
|
+
job_id: expect.any(String),
|
|
111
|
+
log_directory: ".local/dashboard-logs",
|
|
112
|
+
});
|
|
113
|
+
await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
|
|
114
|
+
expect((await events(root)).map((item) => item.event)).toEqual(
|
|
115
|
+
expect.arrayContaining(["reconciliation_failed", "reconciliation_response_error"]),
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
1
3
|
import type { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
2
4
|
import { describe, expect, it, vi } from "vitest";
|
|
3
5
|
import { reconcileOpenWork, reconciliationResult } from "./server-workspace.js";
|
|
@@ -14,7 +16,7 @@ describe("manual reconciliation report", () => {
|
|
|
14
16
|
},
|
|
15
17
|
],
|
|
16
18
|
}),
|
|
17
|
-
).
|
|
19
|
+
).toMatchObject({ reconciled_ids: ["PRD-DONE", "SPEC-DONE"], blocked: [], not_reconciled: [] });
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
it("reports the canonical owners of an orphan with their persisted blocking reasons", () => {
|
|
@@ -35,7 +37,7 @@ describe("manual reconciliation report", () => {
|
|
|
35
37
|
],
|
|
36
38
|
unresolved_running_run_ids: ["RUN-LIVE"],
|
|
37
39
|
}),
|
|
38
|
-
).
|
|
40
|
+
).toMatchObject({
|
|
39
41
|
reconciled_ids: ["EXEC-ORPHAN", "PRD-ORPHAN", "SPEC-ORPHAN", "TASK-ORPHAN"],
|
|
40
42
|
blocked: [
|
|
41
43
|
{ id: "EXEC-ORPHAN", reason: "Host terminated without delivery evidence." },
|
|
@@ -56,13 +58,40 @@ describe("manual reconciliation report", () => {
|
|
|
56
58
|
{ id: "TASK-LIVE", status: "executing" },
|
|
57
59
|
],
|
|
58
60
|
}),
|
|
59
|
-
).
|
|
61
|
+
).toMatchObject({ reconciled_ids: [], blocked: [], not_reconciled: [], outcomes: [] });
|
|
62
|
+
});
|
|
63
|
+
it("preserves failed Run and owner blocking outcomes from manual orphan closure", () => {
|
|
64
|
+
const result = reconciliationResult({
|
|
65
|
+
runs: [
|
|
66
|
+
{
|
|
67
|
+
run: { run_id: "RUN-ORPHAN" },
|
|
68
|
+
status: "failed",
|
|
69
|
+
finished: { status: "failed", summary: "orphaned" },
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
reconciliations: [
|
|
73
|
+
{
|
|
74
|
+
status: "closed",
|
|
75
|
+
tasks: [{ id: "TASK-X", status: "blocked", status_reason: "repair required" }],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
expect(result.outcomes).toEqual([
|
|
80
|
+
{ id: "RUN-ORPHAN", status: "failed", reason: "orphaned" },
|
|
81
|
+
{ id: "TASK-X", status: "blocked", reason: "repair required" },
|
|
82
|
+
]);
|
|
83
|
+
expect(result.reconciled_ids).toEqual(["RUN-ORPHAN", "TASK-X"]);
|
|
60
84
|
});
|
|
61
85
|
|
|
62
86
|
it("reports unavailable reconciliation instead of returning a successful empty result", async () => {
|
|
63
87
|
const filesystem = { exists: vi.fn(async () => false) } as unknown as WorkspaceFileSystem;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
88
|
+
const base = resolve(import.meta.dirname, "../../../../../../.tmp/reconciliation-unavailable");
|
|
89
|
+
await mkdir(base, { recursive: true });
|
|
90
|
+
const root = await mkdtemp(join(base, "fixture-"));
|
|
91
|
+
try {
|
|
92
|
+
await expect(reconcileOpenWork(filesystem, root)).rejects.toThrow("could not be reconciled");
|
|
93
|
+
} finally {
|
|
94
|
+
await rm(root, { recursive: true, force: true });
|
|
95
|
+
}
|
|
67
96
|
});
|
|
68
97
|
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { type createDashboardDiagnostics, sanitizeDiagnostic } from "../dashboard-diagnostics.mjs";
|
|
4
|
+
import { API_PREFIX, V2WorkspaceRequiredError } from "./server-http-contracts.js";
|
|
5
|
+
import {
|
|
6
|
+
createWorkReconciliation,
|
|
7
|
+
type ReconciliationMode,
|
|
8
|
+
waitForWorkReconciliation,
|
|
9
|
+
} from "./server-work-reconciliation.js";
|
|
10
|
+
import {
|
|
11
|
+
ReconciliationCommandTimeoutError,
|
|
12
|
+
type ReconciliationResult,
|
|
13
|
+
} from "./server-workspace.js";
|
|
14
|
+
|
|
15
|
+
type Diagnostics = ReturnType<typeof createDashboardDiagnostics>;
|
|
16
|
+
export function createLoggedWorkReconciliation(input: {
|
|
17
|
+
diagnostics: Diagnostics;
|
|
18
|
+
reconcile: (mode: ReconciliationMode, jobId: string) => Promise<ReconciliationResult>;
|
|
19
|
+
snapshotTimes: () => {
|
|
20
|
+
projection_updated_at: string | null;
|
|
21
|
+
latest_token_collected_at: string | null;
|
|
22
|
+
};
|
|
23
|
+
refresh: (jobId: string | null) => Promise<void>;
|
|
24
|
+
}) {
|
|
25
|
+
return createWorkReconciliation({
|
|
26
|
+
reconcile: async (mode, requestedJobId) => {
|
|
27
|
+
const job_id = requestedJobId ?? randomUUID();
|
|
28
|
+
const started_at = new Date().toISOString();
|
|
29
|
+
input.diagnostics.log("reconciliation_started", { job_id, mode });
|
|
30
|
+
try {
|
|
31
|
+
const result = await input.reconcile(mode, job_id);
|
|
32
|
+
const receipt = {
|
|
33
|
+
...result,
|
|
34
|
+
diagnostics: {
|
|
35
|
+
job_id,
|
|
36
|
+
started_at,
|
|
37
|
+
completed_at: new Date().toISOString(),
|
|
38
|
+
log_directory: ".local/dashboard-logs",
|
|
39
|
+
...input.snapshotTimes(),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
input.diagnostics.log("reconciliation_completed", { job_id, result: receipt });
|
|
43
|
+
return receipt;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
input.diagnostics.log("reconciliation_failed", { job_id, error });
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
refresh: (jobId) => input.refresh(jobId ?? null),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function registerReconciliationRoute(
|
|
54
|
+
app: Hono,
|
|
55
|
+
input: {
|
|
56
|
+
diagnostics: Diagnostics;
|
|
57
|
+
readLayout: () => Promise<unknown>;
|
|
58
|
+
reconcileWork: ReturnType<typeof createLoggedWorkReconciliation>;
|
|
59
|
+
refresh: (jobId: string | null) => Promise<void>;
|
|
60
|
+
snapshotState: () => "current" | "updating";
|
|
61
|
+
refreshCompleted: () => boolean;
|
|
62
|
+
},
|
|
63
|
+
) {
|
|
64
|
+
app.post(`${API_PREFIX}/reconcile-open-work`, async (context) => {
|
|
65
|
+
const request_id = randomUUID();
|
|
66
|
+
let job_id: string | null = null;
|
|
67
|
+
context.header("X-ZAIP-Request-Id", request_id);
|
|
68
|
+
input.diagnostics.log("reconciliation_button_received", { request_id });
|
|
69
|
+
try {
|
|
70
|
+
await input.readLayout();
|
|
71
|
+
const pending = input.reconcileWork("manual", request_id);
|
|
72
|
+
job_id = input.reconcileWork.jobIdFor(pending);
|
|
73
|
+
input.diagnostics.log("reconciliation_queued", { request_id, job_id });
|
|
74
|
+
const result = await waitForWorkReconciliation(pending, () => {
|
|
75
|
+
input.diagnostics.log("reconciliation_http_deadline", { request_id, job_id });
|
|
76
|
+
void input.refresh(job_id).catch(() => undefined);
|
|
77
|
+
});
|
|
78
|
+
input.diagnostics.log("reconciliation_response", {
|
|
79
|
+
request_id,
|
|
80
|
+
job_id: result.diagnostics?.job_id,
|
|
81
|
+
snapshot_state: input.snapshotState(),
|
|
82
|
+
});
|
|
83
|
+
return context.json({
|
|
84
|
+
reconciliation: result,
|
|
85
|
+
snapshot_state: input.snapshotState(),
|
|
86
|
+
projection_refresh_completed: input.refreshCompleted(),
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
input.diagnostics.log("reconciliation_response_error", { request_id, job_id, error });
|
|
90
|
+
if (error instanceof V2WorkspaceRequiredError) throw error;
|
|
91
|
+
const failure = {
|
|
92
|
+
error: sanitizeDiagnostic(error instanceof Error ? error.message : String(error)),
|
|
93
|
+
request_id,
|
|
94
|
+
job_id,
|
|
95
|
+
log_directory: ".local/dashboard-logs",
|
|
96
|
+
};
|
|
97
|
+
if (error instanceof ReconciliationCommandTimeoutError)
|
|
98
|
+
return context.json(
|
|
99
|
+
{ ...failure, reconciliation_may_be_partial: true, projection_refresh_started: true },
|
|
100
|
+
408,
|
|
101
|
+
);
|
|
102
|
+
return context.json(failure, 400);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -12,6 +12,7 @@ vi.mock("node:fs", () => ({
|
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
|
|
15
|
+
import { createTelemetryProjectionRefresh } from "./server-telemetry-refresh.js";
|
|
15
16
|
|
|
16
17
|
afterEach(() => {
|
|
17
18
|
watchers.splice(0);
|
|
@@ -20,24 +21,73 @@ afterEach(() => {
|
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
describe("snapshot source synchronization", () => {
|
|
24
|
+
it("orders stale-seed startup capture before the initial projection commit", async () => {
|
|
25
|
+
vi.useFakeTimers();
|
|
26
|
+
let release!: () => void;
|
|
27
|
+
const refresh = vi.fn(async () => undefined);
|
|
28
|
+
const capture = vi.fn(async () => {
|
|
29
|
+
await new Promise<void>((resolve) => {
|
|
30
|
+
release = resolve;
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
captured_runs: 0,
|
|
34
|
+
captured_segments: 0,
|
|
35
|
+
manifest_state: "initialized" as const,
|
|
36
|
+
skipped_reason: "no_recent_runs" as const,
|
|
37
|
+
last_capture_at: new Date().toISOString(),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
startSnapshotSynchronization({
|
|
41
|
+
workspaceRoot: "C:/workspace",
|
|
42
|
+
staleSeed: true,
|
|
43
|
+
watchSources: true,
|
|
44
|
+
refresh,
|
|
45
|
+
synchronizeTelemetry: createTelemetryProjectionRefresh({ capture, refresh }).refresh,
|
|
46
|
+
});
|
|
47
|
+
expect(capture).toHaveBeenCalledOnce();
|
|
48
|
+
await vi.advanceTimersByTimeAsync(300);
|
|
49
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
50
|
+
release();
|
|
51
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
52
|
+
expect(refresh).toHaveBeenCalledOnce();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("keeps controlled snapshots stable when source watching is explicitly disabled", async () => {
|
|
56
|
+
vi.useFakeTimers();
|
|
57
|
+
vi.stubEnv("ZAIP_DASHBOARD_WATCH_SOURCES", "0");
|
|
58
|
+
const refresh = vi.fn(async () => undefined);
|
|
59
|
+
const synchronizeTelemetry = vi.fn(async () => undefined);
|
|
60
|
+
startSnapshotSynchronization({
|
|
61
|
+
workspaceRoot: "C:/workspace",
|
|
62
|
+
staleSeed: false,
|
|
63
|
+
watchSources: true,
|
|
64
|
+
refresh,
|
|
65
|
+
synchronizeTelemetry,
|
|
66
|
+
});
|
|
67
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
68
|
+
expect(refresh).not.toHaveBeenCalled();
|
|
69
|
+
expect(synchronizeTelemetry).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
23
71
|
it("observes newly registered checkout records and stops observing removed checkouts", async () => {
|
|
24
72
|
vi.useFakeTimers();
|
|
25
|
-
|
|
73
|
+
const primaryRoot = "C:/main";
|
|
74
|
+
const siblingRoot = "C:/codex/worktree";
|
|
75
|
+
let roots = [primaryRoot, siblingRoot];
|
|
26
76
|
const refresh = vi.fn(async () => undefined);
|
|
27
77
|
startSnapshotSynchronization({
|
|
28
|
-
workspaceRoot:
|
|
78
|
+
workspaceRoot: primaryRoot,
|
|
29
79
|
discoverSourceRoots: async () => roots,
|
|
30
80
|
staleSeed: false,
|
|
31
81
|
watchSources: true,
|
|
32
82
|
refresh,
|
|
33
83
|
});
|
|
34
84
|
await vi.advanceTimersByTimeAsync(1);
|
|
35
|
-
const sibling = watchers.find(({ path }) => path === join(
|
|
85
|
+
const sibling = watchers.find(({ path }) => path === join(siblingRoot, ".agents", "records"));
|
|
36
86
|
expect(sibling).toBeDefined();
|
|
37
87
|
sibling?.listener();
|
|
38
88
|
await vi.advanceTimersByTimeAsync(300);
|
|
39
89
|
expect(refresh).toHaveBeenCalled();
|
|
40
|
-
roots = [
|
|
90
|
+
roots = [primaryRoot];
|
|
41
91
|
await vi.advanceTimersByTimeAsync(60_000);
|
|
42
92
|
expect(sibling?.close).toHaveBeenCalledOnce();
|
|
43
93
|
});
|
|
@@ -51,7 +101,7 @@ describe("snapshot source synchronization", () => {
|
|
|
51
101
|
captured_runs: 0,
|
|
52
102
|
captured_segments: 0,
|
|
53
103
|
manifest_state: "initialized",
|
|
54
|
-
skipped_reason: "
|
|
104
|
+
skipped_reason: "no_recent_runs",
|
|
55
105
|
last_capture_at: "2026-09-16T00:00:00.000Z",
|
|
56
106
|
});
|
|
57
107
|
const refresh = vi.fn(async () => undefined);
|
|
@@ -60,9 +110,12 @@ describe("snapshot source synchronization", () => {
|
|
|
60
110
|
staleSeed: false,
|
|
61
111
|
watchSources: true,
|
|
62
112
|
refresh,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
113
|
+
synchronizeTelemetry: createTelemetryProjectionRefresh({
|
|
114
|
+
capture,
|
|
115
|
+
refresh,
|
|
116
|
+
onCaptureFailure,
|
|
117
|
+
onCaptureSuccess,
|
|
118
|
+
}).refresh,
|
|
66
119
|
onRefreshFailure,
|
|
67
120
|
});
|
|
68
121
|
await vi.advanceTimersByTimeAsync(301);
|
|
@@ -71,7 +124,7 @@ describe("snapshot source synchronization", () => {
|
|
|
71
124
|
expect(refresh).toHaveBeenCalledTimes(1);
|
|
72
125
|
await vi.advanceTimersByTimeAsync(60_000);
|
|
73
126
|
expect(onCaptureSuccess).toHaveBeenCalledWith(
|
|
74
|
-
expect.objectContaining({ skipped_reason: "
|
|
127
|
+
expect.objectContaining({ skipped_reason: "no_recent_runs" }),
|
|
75
128
|
);
|
|
76
129
|
expect(refresh).toHaveBeenCalledTimes(2);
|
|
77
130
|
});
|
|
@@ -81,6 +134,8 @@ describe("snapshot source synchronization", () => {
|
|
|
81
134
|
const workspaceRoot = "C:/workspace";
|
|
82
135
|
|
|
83
136
|
startSnapshotSynchronization({ workspaceRoot, staleSeed: false, watchSources: true, refresh });
|
|
137
|
+
await vi.advanceTimersByTimeAsync(0);
|
|
138
|
+
refresh.mockClear();
|
|
84
139
|
|
|
85
140
|
const currentData = watchers.find(
|
|
86
141
|
({ path }) => path === join(workspaceRoot, ".agents", "data"),
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { watch } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type { ActiveCodexTelemetryCapture } from "./active-codex-telemetry.js";
|
|
4
3
|
|
|
5
4
|
export function startSnapshotSynchronization(input: {
|
|
6
5
|
workspaceRoot: string;
|
|
@@ -9,17 +8,26 @@ export function startSnapshotSynchronization(input: {
|
|
|
9
8
|
staleSeed: boolean;
|
|
10
9
|
watchSources: boolean;
|
|
11
10
|
refresh: () => Promise<void>;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
onCaptureFailure?: (error: unknown) => void;
|
|
15
|
-
onCaptureSuccess?: (result: ActiveCodexTelemetryCapture) => void;
|
|
11
|
+
/** Capture exact recent usage before rebuilding, without lifecycle repair. */
|
|
12
|
+
synchronizeTelemetry?: () => Promise<void>;
|
|
16
13
|
/** Report failed asynchronous refreshes; a stale snapshot must never look healthy. */
|
|
17
14
|
onRefreshFailure?: (input: { trigger: "stale_seed" | "source_change"; error: unknown }) => void;
|
|
18
15
|
}): void {
|
|
19
16
|
const refresh = (trigger: "stale_seed" | "source_change") =>
|
|
20
17
|
void input.refresh().catch((error: unknown) => input.onRefreshFailure?.({ trigger, error }));
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const watching = input.watchSources && process.env.ZAIP_DASHBOARD_WATCH_SOURCES !== "0";
|
|
19
|
+
const captureAndRefresh = () =>
|
|
20
|
+
void (input.synchronizeTelemetry?.() ?? input.refresh()).catch((error: unknown) =>
|
|
21
|
+
input.onRefreshFailure?.({
|
|
22
|
+
trigger: input.staleSeed ? "stale_seed" : "source_change",
|
|
23
|
+
error,
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
// Startup is one ordered pass. Never publish a stale-seed rebuild before
|
|
27
|
+
// the initial telemetry command has completed.
|
|
28
|
+
if (watching) captureAndRefresh();
|
|
29
|
+
else if (input.staleSeed) refresh("stale_seed");
|
|
30
|
+
if (!watching) return;
|
|
23
31
|
let pendingRefresh: NodeJS.Timeout | undefined;
|
|
24
32
|
const scheduleRefresh = () => {
|
|
25
33
|
if (pendingRefresh !== undefined) clearTimeout(pendingRefresh);
|
|
@@ -64,27 +72,11 @@ export function startSnapshotSynchronization(input: {
|
|
|
64
72
|
}
|
|
65
73
|
};
|
|
66
74
|
void discover();
|
|
67
|
-
let captureInFlight = false;
|
|
68
|
-
const captureAndRefresh = () => {
|
|
69
|
-
if (captureInFlight) return;
|
|
70
|
-
captureInFlight = true;
|
|
71
|
-
void (async () => {
|
|
72
|
-
try {
|
|
73
|
-
await discover();
|
|
74
|
-
if (input.captureActiveTelemetry !== undefined) {
|
|
75
|
-
input.onCaptureSuccess?.(await input.captureActiveTelemetry());
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
input.onCaptureFailure?.(error);
|
|
79
|
-
} finally {
|
|
80
|
-
scheduleRefresh();
|
|
81
|
-
captureInFlight = false;
|
|
82
|
-
}
|
|
83
|
-
})();
|
|
84
|
-
};
|
|
85
75
|
// Run at most once a minute. File watchers only project canonical changes;
|
|
86
76
|
// they never launch telemetry or lifecycle work.
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
const freshnessTimer = setInterval(() => {
|
|
78
|
+
void discover();
|
|
79
|
+
captureAndRefresh();
|
|
80
|
+
}, 60_000);
|
|
89
81
|
freshnessTimer.unref();
|
|
90
82
|
}
|