@zaip-ai/cli 4.5.0 → 4.6.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/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +4 -4
- package/dist/services/adapters.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/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.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +71 -0
- package/dist/services/logger.test.js.map +1 -1
- 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 +23 -31
- 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 +211 -75
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-BRtiPijr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-BR4ZeYpE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-Dr4QDFmn.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-BG0N5dw_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-BvoM-YQ0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-fTQhSL85.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-Bc6vux5t.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BMfzKaAR.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-DNJQFov3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-B7tjIB7W.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/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/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 +232 -217
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +201 -188
- 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 +232 -217
- 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-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-view-page.ts +9 -58
- 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 +10 -8
- 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-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 +366 -196
- 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 +36 -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 +303 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +334 -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 +44 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +91 -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 +330 -142
- 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
package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { createDashboardApp } from "./server.js";
|
|
5
|
+
import { withSqliteReadModelFixture } from "./server.test-fixtures.js";
|
|
6
|
+
|
|
7
|
+
describe("recent telemetry command readiness", () => {
|
|
8
|
+
it("requires a POST capability and stays updating until capture and the SQLite commit finish", async () => {
|
|
9
|
+
await withSqliteReadModelFixture("telemetry-open", async ({ root, filesystem }) => {
|
|
10
|
+
await mkdir(join(root, ".agents/records/lifecycle"), { recursive: true });
|
|
11
|
+
await writeFile(
|
|
12
|
+
join(root, ".agents/records/lifecycle/manifest.json"),
|
|
13
|
+
JSON.stringify({
|
|
14
|
+
schema_version: 1,
|
|
15
|
+
updated_at: new Date().toISOString(),
|
|
16
|
+
offsets: {},
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
await writeFile(
|
|
20
|
+
join(root, ".agents/records/lifecycle/summary.json"),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
schema_version: 1,
|
|
23
|
+
updated_at: new Date().toISOString(),
|
|
24
|
+
counts: { total: 0, by_kind: {}, by_status: {} },
|
|
25
|
+
freshness: {},
|
|
26
|
+
usage: {},
|
|
27
|
+
periods: {},
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
let releaseCapture!: () => void;
|
|
31
|
+
let releaseCommit!: () => void;
|
|
32
|
+
const capture = vi.fn(async () => {
|
|
33
|
+
await new Promise<void>((resolve) => {
|
|
34
|
+
releaseCapture = resolve;
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
captured_runs: 1,
|
|
38
|
+
captured_segments: null,
|
|
39
|
+
manifest_state: "initialized" as const,
|
|
40
|
+
skipped_reason: null,
|
|
41
|
+
last_capture_at: new Date().toISOString(),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const commit = vi.fn(async () => {
|
|
45
|
+
await new Promise<void>((resolve) => {
|
|
46
|
+
releaseCommit = resolve;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
const reconcile = vi.fn();
|
|
50
|
+
const app = createDashboardApp(root, "session", undefined, {
|
|
51
|
+
filesystem,
|
|
52
|
+
captureTelemetry: capture,
|
|
53
|
+
refreshReadModel: commit,
|
|
54
|
+
reconcileOpenWork: reconcile,
|
|
55
|
+
});
|
|
56
|
+
const headers = { "x-zaip-session": "session" };
|
|
57
|
+
const ready = async () => (await app.request("/api/v2/ready", { headers })).json();
|
|
58
|
+
expect((await app.request("/api/v2/capture-telemetry", { method: "POST" })).status).toBe(401);
|
|
59
|
+
expect(capture).not.toHaveBeenCalled();
|
|
60
|
+
const response = await app.request("/api/v2/capture-telemetry", { method: "POST", headers });
|
|
61
|
+
expect(response.status).toBe(202);
|
|
62
|
+
expect(await response.json()).toEqual({ snapshot_state: "updating" });
|
|
63
|
+
await expect(ready()).resolves.toMatchObject({ snapshot_state: "updating" });
|
|
64
|
+
// Passive probes cannot start another capture. A second opening shares it.
|
|
65
|
+
await ready();
|
|
66
|
+
await app.request("/api/v2/capture-telemetry", { method: "POST", headers });
|
|
67
|
+
expect(capture).toHaveBeenCalledOnce();
|
|
68
|
+
expect(commit).not.toHaveBeenCalled();
|
|
69
|
+
releaseCapture();
|
|
70
|
+
await vi.waitFor(() => expect(commit).toHaveBeenCalledOnce());
|
|
71
|
+
await expect(ready()).resolves.toMatchObject({ snapshot_state: "updating" });
|
|
72
|
+
releaseCommit();
|
|
73
|
+
await vi.waitFor(async () =>
|
|
74
|
+
expect(await ready()).toMatchObject({
|
|
75
|
+
status: "ready",
|
|
76
|
+
snapshot_state: "current",
|
|
77
|
+
active_capture: { captured_segments: null },
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
expect(reconcile).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("cannot advertise current after a failed capture even when canonical projection succeeds", async () => {
|
|
85
|
+
await withSqliteReadModelFixture("telemetry-failed", async ({ root, filesystem }) => {
|
|
86
|
+
const commit = vi.fn(async () => undefined);
|
|
87
|
+
const app = createDashboardApp(root, "session", undefined, {
|
|
88
|
+
filesystem,
|
|
89
|
+
refreshReadModel: commit,
|
|
90
|
+
captureTelemetry: async () => {
|
|
91
|
+
throw new Error("transcript timed out");
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const headers = { "x-zaip-session": "session" };
|
|
95
|
+
await app.request("/api/v2/capture-telemetry", { method: "POST", headers });
|
|
96
|
+
await vi.waitFor(() => expect(commit).toHaveBeenCalledOnce());
|
|
97
|
+
const ready = await app.request("/api/v2/ready", { headers });
|
|
98
|
+
expect(await ready.json()).toMatchObject({
|
|
99
|
+
status: "degraded",
|
|
100
|
+
snapshot_state: "updating",
|
|
101
|
+
active_capture_failure: { code: "active_telemetry_capture_failed" },
|
|
102
|
+
});
|
|
103
|
+
const manual = await app.request("/api/v2/refresh-projection", { method: "POST", headers });
|
|
104
|
+
expect(manual.status).toBe(503);
|
|
105
|
+
expect(await manual.json()).toMatchObject({
|
|
106
|
+
error: "Telemetry capture is unavailable",
|
|
107
|
+
snapshot_state: "updating",
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createTelemetryProjectionRefresh } from "./server-telemetry-refresh.js";
|
|
3
|
+
|
|
4
|
+
describe("telemetry before projection", () => {
|
|
5
|
+
it("shares startup/browser/refresh requests and cannot finish before capture or rebuild", async () => {
|
|
6
|
+
let finishCapture!: () => void;
|
|
7
|
+
let finishProjection!: () => void;
|
|
8
|
+
const order: string[] = [];
|
|
9
|
+
const capture = vi.fn(async () => {
|
|
10
|
+
order.push("capture");
|
|
11
|
+
await new Promise<void>((resolve) => {
|
|
12
|
+
finishCapture = resolve;
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
captured_runs: 1,
|
|
16
|
+
captured_segments: null,
|
|
17
|
+
manifest_state: "initialized" as const,
|
|
18
|
+
skipped_reason: null,
|
|
19
|
+
last_capture_at: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
const refresh = vi.fn(async () => {
|
|
23
|
+
order.push("projection");
|
|
24
|
+
await new Promise<void>((resolve) => {
|
|
25
|
+
finishProjection = resolve;
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const sync = createTelemetryProjectionRefresh({ capture, refresh });
|
|
29
|
+
const startup = sync.refresh();
|
|
30
|
+
const browser = sync.refresh();
|
|
31
|
+
expect(browser).toBe(startup);
|
|
32
|
+
expect(sync.pending).toBe(true);
|
|
33
|
+
expect(order).toEqual(["capture"]);
|
|
34
|
+
finishCapture();
|
|
35
|
+
await vi.waitFor(() => expect(refresh).toHaveBeenCalledOnce());
|
|
36
|
+
expect(sync.pending).toBe(true);
|
|
37
|
+
finishProjection();
|
|
38
|
+
await startup;
|
|
39
|
+
expect(sync.pending).toBe(false);
|
|
40
|
+
expect(order).toEqual(["capture", "projection"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("retains a failed capture while projecting canonical data and allows retry", async () => {
|
|
44
|
+
const failure = new Error("unavailable telemetry");
|
|
45
|
+
const onCaptureFailure = vi.fn();
|
|
46
|
+
const refresh = vi.fn(async () => undefined);
|
|
47
|
+
const sync = createTelemetryProjectionRefresh({
|
|
48
|
+
capture: vi.fn().mockRejectedValue(failure),
|
|
49
|
+
refresh,
|
|
50
|
+
onCaptureFailure,
|
|
51
|
+
});
|
|
52
|
+
await sync.refresh();
|
|
53
|
+
expect(onCaptureFailure).toHaveBeenCalledWith(failure);
|
|
54
|
+
expect(refresh).toHaveBeenCalledOnce();
|
|
55
|
+
expect(sync.pending).toBe(false);
|
|
56
|
+
await sync.refresh();
|
|
57
|
+
expect(refresh).toHaveBeenCalledTimes(2);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ActiveCodexTelemetryCapture } from "./active-codex-telemetry.js";
|
|
2
|
+
|
|
3
|
+
/** Startup, explicit browser opening, and manual refresh share one writer. */
|
|
4
|
+
export function createTelemetryProjectionRefresh(input: {
|
|
5
|
+
capture?: () => Promise<ActiveCodexTelemetryCapture>;
|
|
6
|
+
refresh: () => Promise<void>;
|
|
7
|
+
onCaptureSuccess?: (result: ActiveCodexTelemetryCapture) => void;
|
|
8
|
+
onCaptureFailure?: (error: unknown) => void;
|
|
9
|
+
}) {
|
|
10
|
+
let inFlight: Promise<void> | null = null;
|
|
11
|
+
let projecting = false;
|
|
12
|
+
const refresh = (catchUp = false): Promise<void> => {
|
|
13
|
+
if (inFlight !== null) {
|
|
14
|
+
// An explicit refresh may observe a source change after the SQLite
|
|
15
|
+
// worker started. Preserve the projection's bounded catch-up request.
|
|
16
|
+
if (catchUp && projecting) void input.refresh().catch(() => undefined);
|
|
17
|
+
return inFlight;
|
|
18
|
+
}
|
|
19
|
+
inFlight = (async () => {
|
|
20
|
+
try {
|
|
21
|
+
if (input.capture !== undefined) {
|
|
22
|
+
const result = await input.capture();
|
|
23
|
+
input.onCaptureSuccess?.(result);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
input.onCaptureFailure?.(error);
|
|
27
|
+
}
|
|
28
|
+
// Keep canonical records readable even when telemetry is unavailable.
|
|
29
|
+
// The caller retains the capture failure separately from SQLite health.
|
|
30
|
+
projecting = true;
|
|
31
|
+
await input.refresh();
|
|
32
|
+
})().finally(() => {
|
|
33
|
+
projecting = false;
|
|
34
|
+
inFlight = null;
|
|
35
|
+
});
|
|
36
|
+
return inFlight;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
refresh,
|
|
40
|
+
get pending() {
|
|
41
|
+
return inFlight !== null;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts
CHANGED
|
@@ -13,6 +13,26 @@ const changed: ReconciliationResult = {
|
|
|
13
13
|
afterEach(() => vi.useRealTimers());
|
|
14
14
|
|
|
15
15
|
describe("canonical host recovery", () => {
|
|
16
|
+
it("associates deduplicated queued requests with the original job instead of the preceding job", async () => {
|
|
17
|
+
let release!: (value: ReconciliationResult) => void;
|
|
18
|
+
const first = new Promise<ReconciliationResult>((resolve) => {
|
|
19
|
+
release = resolve;
|
|
20
|
+
});
|
|
21
|
+
const reconcile = vi.fn().mockReturnValueOnce(first).mockResolvedValue(clean);
|
|
22
|
+
const run = createWorkReconciliation({ reconcile, refresh: async () => undefined });
|
|
23
|
+
const previous = run("host-terminal", "JOB-HOST");
|
|
24
|
+
const queued = run("manual", "JOB-MANUAL");
|
|
25
|
+
const deduplicated = run("manual", "JOB-SECOND-CLICK");
|
|
26
|
+
expect(run.jobIdFor(previous)).toBe("JOB-HOST");
|
|
27
|
+
expect(run.jobIdFor(queued)).toBe("JOB-MANUAL");
|
|
28
|
+
expect(run.jobIdFor(deduplicated)).toBe("JOB-MANUAL");
|
|
29
|
+
release(clean);
|
|
30
|
+
await queued;
|
|
31
|
+
expect(reconcile.mock.calls).toEqual([
|
|
32
|
+
["host-terminal", "JOB-HOST"],
|
|
33
|
+
["manual", "JOB-MANUAL"],
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
16
36
|
it("queues manual recovery after host-only work and deduplicates concurrent clicks", async () => {
|
|
17
37
|
let release!: (value: ReconciliationResult) => void;
|
|
18
38
|
const first = new Promise<ReconciliationResult>((resolve) => {
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts
CHANGED
|
@@ -34,13 +34,14 @@ export async function waitForWorkReconciliation(
|
|
|
34
34
|
|
|
35
35
|
/** Serialize canonical mutations from the button and the one-shot host recovery. */
|
|
36
36
|
export function createWorkReconciliation(input: {
|
|
37
|
-
reconcile: (mode: ReconciliationMode) => Promise<ReconciliationResult>;
|
|
38
|
-
refresh: () => Promise<void>;
|
|
37
|
+
reconcile: (mode: ReconciliationMode, jobId?: string) => Promise<ReconciliationResult>;
|
|
38
|
+
refresh: (jobId?: string) => Promise<void>;
|
|
39
39
|
}) {
|
|
40
40
|
let tail = Promise.resolve();
|
|
41
41
|
const pending = new Map<ReconciliationMode, Promise<ReconciliationResult>>();
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const jobIds = new WeakMap<Promise<ReconciliationResult>, string>();
|
|
43
|
+
const refresh = (jobId?: string) => void input.refresh(jobId).catch(() => undefined);
|
|
44
|
+
const run = (mode: ReconciliationMode, jobId?: string): Promise<ReconciliationResult> => {
|
|
44
45
|
// A manual command is an explicit operator action. It may wait for an
|
|
45
46
|
// already-writing command, but it must never be represented by the host
|
|
46
47
|
// recovery promise: that made the dialog appear to run forever while the
|
|
@@ -48,24 +49,28 @@ export function createWorkReconciliation(input: {
|
|
|
48
49
|
const current = pending.get(mode);
|
|
49
50
|
if (current !== undefined) return current;
|
|
50
51
|
const job = tail
|
|
51
|
-
.then(() => input.reconcile(mode))
|
|
52
|
+
.then(() => (jobId === undefined ? input.reconcile(mode) : input.reconcile(mode, jobId)))
|
|
52
53
|
.then(
|
|
53
54
|
(result) => {
|
|
54
|
-
if (result.reconciled_ids.length > 0 || result.blocked.length > 0) refresh();
|
|
55
|
+
if (result.reconciled_ids.length > 0 || result.blocked.length > 0) refresh(jobId);
|
|
55
56
|
return result;
|
|
56
57
|
},
|
|
57
58
|
(error: unknown) => {
|
|
58
59
|
// A failed command may already have persisted part of the graph.
|
|
59
|
-
refresh();
|
|
60
|
+
refresh(jobId);
|
|
60
61
|
throw error;
|
|
61
62
|
},
|
|
62
63
|
)
|
|
63
64
|
.finally(() => pending.delete(mode));
|
|
64
65
|
pending.set(mode, job);
|
|
66
|
+
if (jobId !== undefined) jobIds.set(job, jobId);
|
|
65
67
|
tail = job.then(
|
|
66
68
|
() => undefined,
|
|
67
69
|
() => undefined,
|
|
68
70
|
);
|
|
69
71
|
return job;
|
|
70
72
|
};
|
|
73
|
+
return Object.assign(run, {
|
|
74
|
+
jobIdFor: (job: Promise<ReconciliationResult>) => jobIds.get(job) ?? null,
|
|
75
|
+
});
|
|
71
76
|
}
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
5
|
import type { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
6
|
+
import { createDashboardDiagnostics } from "../dashboard-diagnostics.mjs";
|
|
6
7
|
|
|
7
8
|
const DASHBOARD_CONFIG_PATH = ".agents/dashboard/dashboard.config.json";
|
|
8
9
|
const execFileAsync = promisify(execFile);
|
|
@@ -66,6 +67,15 @@ export interface ReconciliationResult {
|
|
|
66
67
|
reconciled_ids: string[];
|
|
67
68
|
blocked: ReconciliationItem[];
|
|
68
69
|
not_reconciled: ReconciliationItem[];
|
|
70
|
+
outcomes?: Array<ReconciliationItem & { status: string }>;
|
|
71
|
+
diagnostics?: {
|
|
72
|
+
job_id: string;
|
|
73
|
+
started_at: string;
|
|
74
|
+
completed_at: string;
|
|
75
|
+
log_directory: string;
|
|
76
|
+
projection_updated_at: string | null;
|
|
77
|
+
latest_token_collected_at: string | null;
|
|
78
|
+
};
|
|
69
79
|
}
|
|
70
80
|
|
|
71
81
|
export const reconciliationCommandTimeoutMs = 170_000;
|
|
@@ -82,15 +92,19 @@ export class ReconciliationCommandTimeoutError extends Error {
|
|
|
82
92
|
export async function reconcileOpenWork(
|
|
83
93
|
filesystem: WorkspaceFileSystem,
|
|
84
94
|
workspaceRoot: string,
|
|
85
|
-
options: { hostTerminalOnly?: boolean } = {},
|
|
95
|
+
options: { hostTerminalOnly?: boolean; jobId?: string } = {},
|
|
86
96
|
): Promise<ReconciliationResult> {
|
|
97
|
+
const diagnostics = createDashboardDiagnostics(workspaceRoot, "reconciliation");
|
|
98
|
+
const job_id = options.jobId ?? diagnostics.correlationId;
|
|
99
|
+
diagnostics.log("cli_start", { job_id, host_terminal_only: options.hostTerminalOnly === true });
|
|
87
100
|
if (!(await filesystem.exists(".agents/runtime/bin/zaip.mjs"))) {
|
|
101
|
+
diagnostics.log("cli_unavailable", { job_id });
|
|
88
102
|
throw new Error("ZAIP runtime is unavailable; open work could not be reconciled.");
|
|
89
103
|
}
|
|
90
104
|
const cliPath = resolve(workspaceRoot, ".agents", "runtime", "bin", "zaip.mjs");
|
|
91
105
|
let stdout: string;
|
|
92
106
|
try {
|
|
93
|
-
|
|
107
|
+
const output = await execFileAsync(
|
|
94
108
|
process.execPath,
|
|
95
109
|
[
|
|
96
110
|
cliPath,
|
|
@@ -107,20 +121,35 @@ export async function reconcileOpenWork(
|
|
|
107
121
|
timeout: reconciliationCommandTimeoutMs,
|
|
108
122
|
killSignal: "SIGTERM",
|
|
109
123
|
},
|
|
110
|
-
)
|
|
124
|
+
);
|
|
125
|
+
stdout = output.stdout;
|
|
126
|
+
diagnostics.log("cli_output", { job_id, stdout, stderr: output.stderr });
|
|
111
127
|
} catch (error) {
|
|
128
|
+
diagnostics.log("cli_error", { job_id, error });
|
|
112
129
|
if (typeof error === "object" && error !== null && "killed" in error && error.killed === true) {
|
|
113
130
|
throw new ReconciliationCommandTimeoutError();
|
|
114
131
|
}
|
|
115
132
|
throw error;
|
|
116
133
|
}
|
|
117
|
-
|
|
134
|
+
try {
|
|
135
|
+
const result = reconciliationResult(JSON.parse(stdout) as unknown);
|
|
136
|
+
diagnostics.log("cli_completed", { job_id, result });
|
|
137
|
+
return result;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
diagnostics.log("cli_invalid_result", { job_id, error, stdout });
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
118
142
|
}
|
|
119
143
|
|
|
120
144
|
export function reconciliationResult(value: unknown): ReconciliationResult {
|
|
121
145
|
const reconciled = new Set<string>();
|
|
122
146
|
const blocked = new Map<string, string | null>();
|
|
123
147
|
const notReconciled = new Map<string, string | null>();
|
|
148
|
+
const outcomes = new Map<string, ReconciliationItem & { status: string }>();
|
|
149
|
+
const recordOutcome = (id: unknown, status: unknown, reason: unknown) => {
|
|
150
|
+
if (typeof id === "string" && typeof status === "string")
|
|
151
|
+
outcomes.set(id, { id, status, reason: typeof reason === "string" ? reason : null });
|
|
152
|
+
};
|
|
124
153
|
const remember = (target: Map<string, string | null>, id: unknown, reason: unknown): void => {
|
|
125
154
|
if (typeof id !== "string") return;
|
|
126
155
|
target.set(id, typeof reason === "string" && reason.trim() ? reason : null);
|
|
@@ -145,6 +174,7 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
|
|
|
145
174
|
typeof record.id === "string"
|
|
146
175
|
) {
|
|
147
176
|
reconciled.add(record.id);
|
|
177
|
+
recordOutcome(record.id, record.status, record.status_reason ?? reason);
|
|
148
178
|
}
|
|
149
179
|
if (record.status === "blocked") {
|
|
150
180
|
remember(blocked, record.id, record.status_reason ?? reason);
|
|
@@ -159,8 +189,18 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
|
|
|
159
189
|
const closedRuns = Array.isArray(records.runs) ? records.runs : [];
|
|
160
190
|
closedRuns.forEach((candidate) => {
|
|
161
191
|
if (typeof candidate !== "object" || candidate === null) return;
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
// CLI returns RunV2Fold: the started header is under run, terminal
|
|
193
|
+
// status/summary belong to the fold and its finished record.
|
|
194
|
+
const fold = candidate as {
|
|
195
|
+
run?: { run_id?: unknown };
|
|
196
|
+
status?: unknown;
|
|
197
|
+
finished?: { status?: unknown; summary?: unknown };
|
|
198
|
+
};
|
|
199
|
+
const run = fold.run;
|
|
200
|
+
if (typeof run?.run_id === "string") {
|
|
201
|
+
reconciled.add(run.run_id);
|
|
202
|
+
recordOutcome(run.run_id, fold.status ?? fold.finished?.status, fold.finished?.summary);
|
|
203
|
+
}
|
|
164
204
|
});
|
|
165
205
|
const orphanedExecutions = Array.isArray(records.orphaned_executions)
|
|
166
206
|
? records.orphaned_executions
|
|
@@ -183,6 +223,7 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
|
|
|
183
223
|
const entity = candidate as Record<string, unknown>;
|
|
184
224
|
if (typeof entity.id !== "string" || !terminalStatuses.has(entity.status as string)) continue;
|
|
185
225
|
reconciled.add(entity.id);
|
|
226
|
+
recordOutcome(entity.id, entity.status, entity.status_reason);
|
|
186
227
|
if (entity.status === "blocked") remember(blocked, entity.id, entity.status_reason);
|
|
187
228
|
}
|
|
188
229
|
}
|
|
@@ -206,5 +247,6 @@ export function reconciliationResult(value: unknown): ReconciliationResult {
|
|
|
206
247
|
reconciled_ids: [...reconciled].sort(),
|
|
207
248
|
blocked: entries(blocked),
|
|
208
249
|
not_reconciled: entries(notReconciled),
|
|
250
|
+
outcomes: [...outcomes.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
|
209
251
|
};
|
|
210
252
|
}
|
|
@@ -283,6 +283,7 @@ describe("dashboard backend", () => {
|
|
|
283
283
|
expect(reconcile).toHaveBeenCalledOnce();
|
|
284
284
|
expect(reconcile).toHaveBeenCalledWith(canonicalFilesystem, workspaceRoot, {
|
|
285
285
|
hostTerminalOnly: false,
|
|
286
|
+
jobId: expect.any(String),
|
|
286
287
|
});
|
|
287
288
|
expect(refresh).toHaveBeenCalledOnce();
|
|
288
289
|
const rejected = await app.request("/api/v2/reconcile-open-work", { method: "POST" });
|
|
@@ -359,7 +360,6 @@ describe("dashboard backend", () => {
|
|
|
359
360
|
summary: { overview_metrics: { task_status_counts: Record<string, number> } };
|
|
360
361
|
};
|
|
361
362
|
expect(beforePayload.summary.overview_metrics.task_status_counts.executing).toBe(1);
|
|
362
|
-
|
|
363
363
|
await writeFile(taskIndexPath, `${JSON.stringify(task("completed"))}\n`);
|
|
364
364
|
await writeFile(taskRecordPath, taskRecord("completed"));
|
|
365
365
|
const previousSeedPreference = process.env.ZAIP_SQLITE_PREFER_LAST_VALID_SEED;
|
|
@@ -439,7 +439,7 @@ describe("dashboard backend", () => {
|
|
|
439
439
|
});
|
|
440
440
|
await expect(
|
|
441
441
|
canonicalFilesystem.readText(".agents/dashboard/work-priority.json"),
|
|
442
|
-
).resolves.toContain(priority.item_ids[0]
|
|
442
|
+
).resolves.toContain(priority.item_ids[0]);
|
|
443
443
|
const readsAfterPut = priorityReads.mock.calls.filter(
|
|
444
444
|
([path]) => path === ".agents/dashboard/work-priority.json",
|
|
445
445
|
).length;
|