@zaip-ai/cli 1.4.3 → 2.0.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/dist/services/adapter-hook-sources.d.ts +1 -0
- package/dist/services/adapter-hook-sources.d.ts.map +1 -1
- package/dist/services/adapter-hook-sources.js +1 -0
- package/dist/services/adapter-hook-sources.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 +17 -4
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/agent-adapters.d.ts +97 -34
- package/dist/services/agent-adapters.d.ts.map +1 -1
- package/dist/services/agent-adapters.js +260 -49
- package/dist/services/agent-adapters.js.map +1 -1
- package/dist/services/codex-conversation-lifecycle-hook.d.ts +2 -0
- package/dist/services/codex-conversation-lifecycle-hook.d.ts.map +1 -0
- package/dist/services/codex-conversation-lifecycle-hook.js +29 -0
- package/dist/services/codex-conversation-lifecycle-hook.js.map +1 -0
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +173 -0
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts +1 -1
- package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
- package/dist/services/codex-subagent-hook-template.js +1 -1
- package/dist/services/doctor.js +2 -2
- package/dist/services/install-agents-template.d.ts.map +1 -1
- package/dist/services/install-agents-template.js +117 -5
- package/dist/services/install-agents-template.js.map +1 -1
- package/dist/services/logger.js +1 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +2 -2
- package/dist/services/stack-git.d.ts +3 -0
- package/dist/services/stack-git.d.ts.map +1 -1
- package/dist/services/stack-git.js +24 -3
- package/dist/services/stack-git.js.map +1 -1
- package/dist/services/template-manifest.d.ts.map +1 -1
- package/dist/services/template-manifest.js +3 -1
- package/dist/services/template-manifest.js.map +1 -1
- package/dist/services/transactional-files.d.ts +2 -1
- package/dist/services/transactional-files.d.ts.map +1 -1
- package/dist/services/transactional-files.js +8 -3
- package/dist/services/transactional-files.js.map +1 -1
- package/package.json +2 -2
- package/templates/.agents/README-ENG.md +718 -0
- package/templates/.agents/README.md +42 -42
- package/templates/.agents/architecture/hostinger-cloudflare-terraform.md +90 -0
- package/templates/.agents/config/autonomous-cloud-inputs.json +41 -0
- package/templates/.agents/dashboard/.zaip-managed.json +66 -46
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-oZVlf-l5.js → DashboardHelpDialog-CYfpYsTr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-BYjVOHI_.js → TraceabilityWorkspace-DHDrd5rE.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BYbpmqmM.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +2 -2
- package/templates/.agents/dashboard/apps/frontend/index.html +1 -1
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +28 -9
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +27 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +13 -29
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-identity.test.ts +43 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-identity.ts +32 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-project-overview.ts +28 -0
- package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/modules/traceability/src/contracts.ts +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/src/index.ts +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/dist/server.cjs +162 -162
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +109 -109
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +166 -166
- package/templates/.agents/dashboard/packages/local-runtime/src/server-identity.test.ts +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +41 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +1 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +2 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-sqlite-task-trace.ts +5 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test-fixtures.ts +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +1 -13
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +11 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts +55 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts +9 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.test.ts +22 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +10 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +68 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +39 -10
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +47 -42
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/design/.zaip-managed.json +41 -41
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- 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 +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/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +61 -23
- 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-aios/SKILL.md +4 -2
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +1 -1
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/infra/terraform/terraform.tfvars.example +2 -1
- package/templates/template-manifest.json +136 -104
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ZJFXmK7m.js +0 -2
- /package/templates/.agents/{data → records}/changes/.gitkeep +0 -0
- /package/templates/.agents/{data → records}/executions/.gitkeep +0 -0
- /package/templates/.agents/{data → records}/prds/.gitkeep +0 -0
- /package/templates/.agents/{data → records}/runs/.gitkeep +0 -0
- /package/templates/.agents/{data → records}/specs/.gitkeep +0 -0
- /package/templates/.agents/{data → records}/tasks/.gitkeep +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { WorkspaceFileSystem } from "@zaip-ai/core";
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
6
|
+
import { createDashboardApp, githubRepositoryFromRemote } from "./server.js";
|
|
7
|
+
|
|
8
|
+
describe("dashboard identity endpoint", () => {
|
|
9
|
+
it("normalizes GitHub HTTPS and SSH remotes for display", () => {
|
|
10
|
+
expect(githubRepositoryFromRemote("git@github.com:zaip-ai/zaip-aios.git")).toBe(
|
|
11
|
+
"zaip-ai/zaip-aios",
|
|
12
|
+
);
|
|
13
|
+
expect(githubRepositoryFromRemote("https://github.com/zaip-ai/zaip-aios.git")).toBe(
|
|
14
|
+
"zaip-ai/zaip-aios",
|
|
15
|
+
);
|
|
16
|
+
expect(githubRepositoryFromRemote("zaip-ai/zaip-aios")).toBe("zaip-ai/zaip-aios");
|
|
17
|
+
expect(githubRepositoryFromRemote("")).toBeNull();
|
|
18
|
+
expect(githubRepositoryFromRemote("https://gitlab.com/zaip-ai/zaip-aios.git")).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
it("serves authenticated dashboard identity without building or refreshing a projection", async () => {
|
|
21
|
+
const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-identity-"));
|
|
22
|
+
const refreshReadModel = vi.fn(async () => {});
|
|
23
|
+
try {
|
|
24
|
+
const app = createDashboardApp(root, "test-session", undefined, {
|
|
25
|
+
filesystem: new WorkspaceFileSystem(root),
|
|
26
|
+
refreshReadModel,
|
|
27
|
+
});
|
|
28
|
+
const response = await app.request("/api/v2/dashboard-identity", {
|
|
29
|
+
headers: { "x-zaip-session": "test-session" },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(response.status, await response.clone().text()).toBe(200);
|
|
33
|
+
await expect(response.json()).resolves.toEqual({
|
|
34
|
+
title: "Dashboard Zaip AIOS",
|
|
35
|
+
repository_name: "",
|
|
36
|
+
});
|
|
37
|
+
expect(refreshReadModel).not.toHaveBeenCalled();
|
|
38
|
+
} finally {
|
|
39
|
+
await rm(root, { recursive: true, force: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
const watchers: Array<{ path: string; listener: () => void }> = [];
|
|
5
|
+
|
|
6
|
+
vi.mock("node:fs", () => ({
|
|
7
|
+
watch: vi.fn((path: string, _options: unknown, listener: () => void) => {
|
|
8
|
+
watchers.push({ path, listener });
|
|
9
|
+
return { unref: vi.fn() };
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import { startSnapshotSynchronization } from "./server-snapshot-synchronization.js";
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
watchers.splice(0);
|
|
17
|
+
vi.useRealTimers();
|
|
18
|
+
vi.unstubAllEnvs();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("snapshot source synchronization", () => {
|
|
22
|
+
it("watches current data for both creation and alteration refreshes", async () => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
const refresh = vi.fn(async () => undefined);
|
|
25
|
+
const workspaceRoot = "C:/workspace";
|
|
26
|
+
|
|
27
|
+
startSnapshotSynchronization({ workspaceRoot, staleSeed: false, watchSources: true, refresh });
|
|
28
|
+
|
|
29
|
+
const currentData = watchers.find(
|
|
30
|
+
({ path }) => path === join(workspaceRoot, ".agents", "data"),
|
|
31
|
+
);
|
|
32
|
+
expect(currentData).toBeDefined();
|
|
33
|
+
currentData?.listener();
|
|
34
|
+
await vi.advanceTimersByTimeAsync(300);
|
|
35
|
+
expect(refresh).toHaveBeenCalledTimes(1);
|
|
36
|
+
|
|
37
|
+
currentData?.listener();
|
|
38
|
+
await vi.advanceTimersByTimeAsync(300);
|
|
39
|
+
expect(refresh).toHaveBeenCalledTimes(2);
|
|
40
|
+
});
|
|
41
|
+
});
|
package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts
CHANGED
|
@@ -19,6 +19,7 @@ export function startSnapshotSynchronization(input: {
|
|
|
19
19
|
pendingRefresh.unref();
|
|
20
20
|
};
|
|
21
21
|
for (const sourceRoot of [
|
|
22
|
+
join(input.workspaceRoot, ".agents", "data"),
|
|
22
23
|
join(input.workspaceRoot, ".agents", "records"),
|
|
23
24
|
join(input.workspaceRoot, ".agents", "config"),
|
|
24
25
|
]) {
|
|
@@ -45,7 +45,7 @@ describe("dashboard backend snapshot", () => {
|
|
|
45
45
|
[
|
|
46
46
|
"lock_version: 1",
|
|
47
47
|
"framework:",
|
|
48
|
-
" version:
|
|
48
|
+
" version: 2.0.0",
|
|
49
49
|
" distribution_sha256: null",
|
|
50
50
|
"layout:",
|
|
51
51
|
" version: 3",
|
|
@@ -510,7 +510,7 @@ describe("dashboard backend snapshot", () => {
|
|
|
510
510
|
});
|
|
511
511
|
await expect(response.json()).resolves.toMatchObject({
|
|
512
512
|
dashboard: {
|
|
513
|
-
title: "Dashboard Zaip AIOS
|
|
513
|
+
title: "Dashboard Zaip AIOS",
|
|
514
514
|
repository_name: "",
|
|
515
515
|
},
|
|
516
516
|
operations: { readonly: true, summary: { source_latest_at: "2026-07-14T00:00:05.000Z" } },
|
|
@@ -16,7 +16,10 @@ export function buildSqliteTaskTrace(
|
|
|
16
16
|
const initial = filesystem.canonicalTraceFiles([taskId]);
|
|
17
17
|
const taskFile = initial.find(
|
|
18
18
|
(row) =>
|
|
19
|
-
row.path.includes("/.agents/
|
|
19
|
+
row.path.includes("/.agents/data/tasks/") ||
|
|
20
|
+
row.path.includes(".agents/data/tasks/") ||
|
|
21
|
+
row.path.includes("/.agents/records/tasks/") ||
|
|
22
|
+
row.path.includes(".agents/records/tasks/"),
|
|
20
23
|
);
|
|
21
24
|
if (taskFile === undefined) throw new Error(`Canonical Task ${taskId} was not found`);
|
|
22
25
|
const scalar = (key: string): string | null => {
|
|
@@ -59,7 +62,7 @@ export function buildSqliteTaskTrace(
|
|
|
59
62
|
schema_version: 2,
|
|
60
63
|
layout_version: layout.version as 2 | 3,
|
|
61
64
|
readonly: true,
|
|
62
|
-
source:
|
|
65
|
+
source: filesystem.canonicalSource(),
|
|
63
66
|
task_id: taskId,
|
|
64
67
|
task_title: scalar("title") ?? taskFile.content.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? taskId,
|
|
65
68
|
task_risk: risk === "lite" || risk === "standard" || risk === "critical" ? risk : null,
|
|
@@ -11,7 +11,7 @@ export async function dashboardIdentity(
|
|
|
11
11
|
filesystem: WorkspaceFileSystem,
|
|
12
12
|
workspaceRoot: string,
|
|
13
13
|
): Promise<{ title: string; repository_name: string }> {
|
|
14
|
-
let title = process.env.ZAIP_DASHBOARD_TITLE?.trim() || "Dashboard Zaip AIOS
|
|
14
|
+
let title = process.env.ZAIP_DASHBOARD_TITLE?.trim() || "Dashboard Zaip AIOS";
|
|
15
15
|
let repositorySetting = "auto";
|
|
16
16
|
try {
|
|
17
17
|
if (await filesystem.exists(DASHBOARD_CONFIG_PATH)) {
|
|
@@ -20,7 +20,7 @@ export async function withSqliteReadModelFixture<T>(
|
|
|
20
20
|
);
|
|
21
21
|
await writeFile(
|
|
22
22
|
join(root, ".agents", "config", "framework.lock.yaml"),
|
|
23
|
-
"lock_version: 1\nframework:\n version:
|
|
23
|
+
"lock_version: 1\nframework:\n version: 2.0.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
|
|
24
24
|
);
|
|
25
25
|
await rebuildSqliteReadModel(root);
|
|
26
26
|
filesystem = new SqliteReadModelFileSystem(root);
|
|
@@ -6,7 +6,6 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
6
6
|
import {
|
|
7
7
|
createDashboardApp,
|
|
8
8
|
dashboardWorkspaceKey,
|
|
9
|
-
githubRepositoryFromRemote,
|
|
10
9
|
manifestRun,
|
|
11
10
|
mergeManifestRuns,
|
|
12
11
|
openWorkIds,
|
|
@@ -107,17 +106,6 @@ describe("dashboard backend", () => {
|
|
|
107
106
|
),
|
|
108
107
|
).toEqual(["RUN-IN-PERIOD"]);
|
|
109
108
|
});
|
|
110
|
-
it("normalizes GitHub HTTPS and SSH remotes for display", () => {
|
|
111
|
-
expect(githubRepositoryFromRemote("git@github.com:zaip-ai/zaip-aios.git")).toBe(
|
|
112
|
-
"zaip-ai/zaip-aios",
|
|
113
|
-
);
|
|
114
|
-
expect(githubRepositoryFromRemote("https://github.com/zaip-ai/zaip-aios.git")).toBe(
|
|
115
|
-
"zaip-ai/zaip-aios",
|
|
116
|
-
);
|
|
117
|
-
expect(githubRepositoryFromRemote("zaip-ai/zaip-aios")).toBe("zaip-ai/zaip-aios");
|
|
118
|
-
expect(githubRepositoryFromRemote("")).toBeNull();
|
|
119
|
-
expect(githubRepositoryFromRemote("https://gitlab.com/zaip-ai/zaip-aios.git")).toBeNull();
|
|
120
|
-
});
|
|
121
109
|
it("exposes a modern read-only health endpoint", async () => {
|
|
122
110
|
const app = createDashboardApp(workspaceRoot, "test-session");
|
|
123
111
|
const response = await app.request("/api/v2/health");
|
|
@@ -294,7 +282,7 @@ describe("dashboard backend", () => {
|
|
|
294
282
|
);
|
|
295
283
|
await writeFile(
|
|
296
284
|
join(fixtureRoot, ".agents", "config", "framework.lock.yaml"),
|
|
297
|
-
"lock_version: 1\nframework:\n version:
|
|
285
|
+
"lock_version: 1\nframework:\n version: 2.0.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
|
|
298
286
|
);
|
|
299
287
|
const task = (status: "executing" | "completed") => ({
|
|
300
288
|
schema_version: 1,
|
|
@@ -389,6 +389,7 @@ function buildSqliteOperationsProjection(
|
|
|
389
389
|
const hasUnknownUsage = Object.values(dailyPeriods).some(
|
|
390
390
|
(period) => period.usage.total_tokens.unknown > 0 || period.usage.cost.unknown > 0,
|
|
391
391
|
);
|
|
392
|
+
const canonicalSource = filesystem.canonicalSource();
|
|
392
393
|
const taskProjection = (row: Record<string, unknown>) => ({
|
|
393
394
|
schema_version: 2 as const,
|
|
394
395
|
id: String(row.id),
|
|
@@ -411,7 +412,7 @@ function buildSqliteOperationsProjection(
|
|
|
411
412
|
started_at: null,
|
|
412
413
|
finished_at: null,
|
|
413
414
|
result: null,
|
|
414
|
-
artifact_path:
|
|
415
|
+
artifact_path: canonicalSource,
|
|
415
416
|
progress: 0,
|
|
416
417
|
spec_id: typeof row.spec_id === "string" ? row.spec_id : null,
|
|
417
418
|
origin: typeof row.origin === "string" ? row.origin : null,
|
|
@@ -435,7 +436,7 @@ function buildSqliteOperationsProjection(
|
|
|
435
436
|
updated_at: String(row.occurred_at),
|
|
436
437
|
kind,
|
|
437
438
|
parent_id: null,
|
|
438
|
-
artifact_path:
|
|
439
|
+
artifact_path: canonicalSource,
|
|
439
440
|
lifecycle_status: String(row.status ?? "draft"),
|
|
440
441
|
work_status: normalizeMacroStatus(row.status),
|
|
441
442
|
execution_ids: [],
|
|
@@ -472,7 +473,7 @@ function buildSqliteOperationsProjection(
|
|
|
472
473
|
schema_version: 2,
|
|
473
474
|
layout_version: layout.version as 2 | 3,
|
|
474
475
|
readonly: true,
|
|
475
|
-
source:
|
|
476
|
+
source: canonicalSource,
|
|
476
477
|
filters: filter,
|
|
477
478
|
summary: {
|
|
478
479
|
prd_count: allTime?.aggregate.prd_count ?? prds.length,
|
|
@@ -939,6 +940,13 @@ export function createDashboardApp(
|
|
|
939
940
|
}
|
|
940
941
|
});
|
|
941
942
|
|
|
943
|
+
// Header identity is intentionally separate from Operations projections.
|
|
944
|
+
// The quick first fold omits it, and resolving this small configuration/git
|
|
945
|
+
// value must not read, refresh, or otherwise materialize a snapshot.
|
|
946
|
+
app.on(["GET", "HEAD"], `${API_PREFIX}/dashboard-identity`, async (context) =>
|
|
947
|
+
headOrJson(context, await dashboardIdentity(filesystem, workspaceRoot)),
|
|
948
|
+
);
|
|
949
|
+
|
|
942
950
|
app.on(["GET", "HEAD"], `${API_PREFIX}/operations`, async (context) => {
|
|
943
951
|
try {
|
|
944
952
|
const requestStartedAt = performance.now();
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-incremental-refresh.test.ts
CHANGED
|
@@ -68,6 +68,61 @@ afterEach(async () => {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
describe("incremental refresh preserves historical facts", () => {
|
|
71
|
+
it("keeps one current Run when a legacy duplicate is added and falls back after current deletion", async () => {
|
|
72
|
+
const root = await fixture();
|
|
73
|
+
await prepareMultiProviderFixture(root);
|
|
74
|
+
const legacy = join(root, ".agents/records/runs/2026/08/RUN-MULTI.jsonl");
|
|
75
|
+
const current = join(root, ".agents/data/runs/2026/08/RUN-MULTI.jsonl");
|
|
76
|
+
await mkdir(join(root, ".agents/data/runs/2026/08"), { recursive: true });
|
|
77
|
+
const content = await readFile(legacy, "utf8");
|
|
78
|
+
await writeFile(current, content);
|
|
79
|
+
|
|
80
|
+
const first = await rebuildSqliteReadModel(root);
|
|
81
|
+
const expected = inspect(
|
|
82
|
+
first.path,
|
|
83
|
+
"SELECT COUNT(*) AS count, SUM(total_tokens) AS tokens, SUM(cost_brl) AS cost FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
|
|
84
|
+
);
|
|
85
|
+
expect(
|
|
86
|
+
inspect(
|
|
87
|
+
first.path,
|
|
88
|
+
"SELECT COUNT(*) AS count FROM zaip_vw_canonical_run_index WHERE run_id='RUN-MULTI'",
|
|
89
|
+
),
|
|
90
|
+
).toEqual([{ count: 1 }]);
|
|
91
|
+
// Establish a cached current-only generation before an upgrade leaves a
|
|
92
|
+
// duplicate legacy file behind.
|
|
93
|
+
await rm(legacy);
|
|
94
|
+
await rebuildSqliteReadModel(root);
|
|
95
|
+
await writeFile(legacy, content);
|
|
96
|
+
await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
|
|
97
|
+
expect(
|
|
98
|
+
inspect(
|
|
99
|
+
first.path,
|
|
100
|
+
"SELECT COUNT(*) AS count FROM zaip_vw_canonical_run_index WHERE run_id='RUN-MULTI'",
|
|
101
|
+
),
|
|
102
|
+
).toEqual([{ count: 1 }]);
|
|
103
|
+
expect(
|
|
104
|
+
inspect(
|
|
105
|
+
first.path,
|
|
106
|
+
"SELECT COUNT(*) AS count, SUM(total_tokens) AS tokens, SUM(cost_brl) AS cost FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
|
|
107
|
+
),
|
|
108
|
+
).toEqual(expected);
|
|
109
|
+
expect(
|
|
110
|
+
inspect(
|
|
111
|
+
first.path,
|
|
112
|
+
"SELECT source_path FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
|
|
113
|
+
),
|
|
114
|
+
).toEqual([{ source_path: ".agents/data/runs/2026/08/RUN-MULTI.jsonl" }]);
|
|
115
|
+
|
|
116
|
+
await rm(current);
|
|
117
|
+
await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ cache: "rebuilt" });
|
|
118
|
+
expect(
|
|
119
|
+
inspect(
|
|
120
|
+
first.path,
|
|
121
|
+
"SELECT COUNT(*) AS count, source_path FROM zaip_aios_activities WHERE payload_type IS NULL AND run_id='RUN-MULTI'",
|
|
122
|
+
),
|
|
123
|
+
).toEqual([{ count: 1, source_path: ".agents/records/runs/2026/08/RUN-MULTI.jsonl" }]);
|
|
124
|
+
}, 120_000);
|
|
125
|
+
|
|
71
126
|
it("refreshes developer delivery and shared SPEC allocation from cached Run facts", async () => {
|
|
72
127
|
const root = await fixture();
|
|
73
128
|
await prepareMultiProviderFixture(root);
|
package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-canonical.ts
CHANGED
|
@@ -117,7 +117,7 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
|
|
|
117
117
|
const id = stringValue(metadata.id);
|
|
118
118
|
const createdAt = stringValue(metadata.created_at);
|
|
119
119
|
if (id === null || createdAt === null || Number.isNaN(Date.parse(createdAt))) continue;
|
|
120
|
-
if (/^\.agents\/records\/executions\/.*\/execution\.md$/.test(row.path)) {
|
|
120
|
+
if (/^\.agents\/(?:data|records)\/executions\/.*\/execution\.md$/.test(row.path)) {
|
|
121
121
|
const origin = recordValue(metadata.origin);
|
|
122
122
|
const originType = stringValue(origin?.type);
|
|
123
123
|
const originId =
|
|
@@ -142,7 +142,7 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
|
|
|
142
142
|
});
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
|
-
if (/^\.agents\/records\/changes\/.*\/change\.md$/.test(row.path)) {
|
|
145
|
+
if (/^\.agents\/(?:data|records)\/changes\/.*\/change\.md$/.test(row.path)) {
|
|
146
146
|
const source = recordValue(metadata.source);
|
|
147
147
|
const sourceType = stringValue(source?.type);
|
|
148
148
|
const sourceId =
|
|
@@ -175,7 +175,9 @@ export function readCanonicalLifecycleRecords(rows: Array<{ path: string; conten
|
|
|
175
175
|
});
|
|
176
176
|
continue;
|
|
177
177
|
}
|
|
178
|
-
if (
|
|
178
|
+
if (
|
|
179
|
+
/^\.agents\/(?:data|records)\/(changes|executions)\/.*\/decisions\/.*\.md$/.test(row.path)
|
|
180
|
+
) {
|
|
179
181
|
const origin = metadata.origin;
|
|
180
182
|
const scopeType = metadata.scope_type;
|
|
181
183
|
const scopeId = stringValue(metadata.scope_id);
|
|
@@ -205,9 +207,10 @@ export function readCanonicalEntities(
|
|
|
205
207
|
): Map<string, CanonicalEntity> {
|
|
206
208
|
const entities = new Map<string, CanonicalEntity>();
|
|
207
209
|
for (const row of rows) {
|
|
208
|
-
const match =
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
const match =
|
|
211
|
+
/^\.agents\/(?:data|records)\/(tasks|specs|prds|changes|executions|conversations)\//.exec(
|
|
212
|
+
row.path,
|
|
213
|
+
);
|
|
211
214
|
if (match === null) continue;
|
|
212
215
|
const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(row.content)?.[1];
|
|
213
216
|
if (frontMatter === undefined) continue;
|
|
@@ -25,6 +25,28 @@ afterEach(async () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
describe("persistent source fingerprint checkpoints", () => {
|
|
28
|
+
it("includes current data records, retains legacy records, and excludes generated projections", async () => {
|
|
29
|
+
const { root } = await fixture();
|
|
30
|
+
await mkdir(join(root, ".agents/data/runs/2026/09"), { recursive: true });
|
|
31
|
+
await mkdir(join(root, ".agents/data/executions/EXEC-CURRENT/decisions"), { recursive: true });
|
|
32
|
+
await mkdir(join(root, ".agents/data/lifecycle/indexes"), { recursive: true });
|
|
33
|
+
await writeFile(join(root, ".agents/data/runs/2026/09/RUN-CURRENT.jsonl"), "{}\n");
|
|
34
|
+
await writeFile(
|
|
35
|
+
join(root, ".agents/data/executions/EXEC-CURRENT/decisions/DEC-CURRENT.md"),
|
|
36
|
+
"---\nid: DEC-CURRENT\ncreated_at: 2026-09-01T00:00:00Z\n---\n",
|
|
37
|
+
);
|
|
38
|
+
await writeFile(join(root, ".agents/data/lifecycle/indexes/run.json"), "{}\n");
|
|
39
|
+
await writeFile(join(root, ".agents/data/lifecycle/active-work.json"), "{}\n");
|
|
40
|
+
|
|
41
|
+
const snapshot = await sourceFingerprint(root);
|
|
42
|
+
|
|
43
|
+
expect(snapshot.candidates.map((candidate) => candidate.source)).toEqual([
|
|
44
|
+
".agents/data/executions/EXEC-CURRENT/decisions/DEC-CURRENT.md",
|
|
45
|
+
".agents/data/runs/2026/09/RUN-CURRENT.jsonl",
|
|
46
|
+
".agents/records/tasks/task.md",
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
28
50
|
it("reuses historical content hashes across serialized checkpoints and detects additions and removals", async () => {
|
|
29
51
|
const { root, path } = await fixture();
|
|
30
52
|
const initial = await sourceFingerprint(root);
|
|
@@ -5,6 +5,7 @@ import { join, relative, resolve } from "node:path";
|
|
|
5
5
|
const textExtensions = new Set([".json", ".jsonl", ".md", ".yaml", ".yml", ".txt"]);
|
|
6
6
|
|
|
7
7
|
function isDashboardCanonicalSource(source: string): boolean {
|
|
8
|
+
const canonicalRoot = "\\.agents\\/(?:data|records)";
|
|
8
9
|
if (
|
|
9
10
|
[
|
|
10
11
|
".agents/config/project.yaml",
|
|
@@ -15,17 +16,18 @@ function isDashboardCanonicalSource(source: string): boolean {
|
|
|
15
16
|
)
|
|
16
17
|
return true;
|
|
17
18
|
if (source.startsWith(".agents/config/pricing/")) return true;
|
|
18
|
-
if (
|
|
19
|
-
if (
|
|
20
|
-
if (
|
|
19
|
+
if (new RegExp(`^${canonicalRoot}/runs/`).test(source) && source.endsWith(".jsonl")) return true;
|
|
20
|
+
if (new RegExp(`^${canonicalRoot}/(tasks|specs|prds)/.*\\.md$`).test(source)) return true;
|
|
21
|
+
if (new RegExp(`^${canonicalRoot}/executions/.*/execution\\.md$`).test(source)) return true;
|
|
22
|
+
if (new RegExp(`^${canonicalRoot}/executions/.*/decisions/.*\\.md$`).test(source)) return true;
|
|
21
23
|
if (
|
|
22
|
-
|
|
24
|
+
new RegExp(`^${canonicalRoot}/lifecycle/historical-developer-attribution/`).test(source) &&
|
|
23
25
|
source.endsWith(".json")
|
|
24
26
|
)
|
|
25
27
|
return true;
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
);
|
|
28
|
+
return new RegExp(
|
|
29
|
+
`^${canonicalRoot}/changes/.*/(change\\.md|decisions/.*\\.md|artifacts/(evidence|tests|validations)/.*\\.md)$`,
|
|
30
|
+
).test(source);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export type SqliteReadModelSourceCandidate = {
|
|
@@ -66,7 +68,7 @@ export async function sourceFingerprint(
|
|
|
66
68
|
"executions",
|
|
67
69
|
"changes",
|
|
68
70
|
"lifecycle/historical-developer-attribution",
|
|
69
|
-
].
|
|
71
|
+
].flatMap((kind) => [`.agents/data/${kind}`, `.agents/records/${kind}`]),
|
|
70
72
|
].map((root) => listFiles(resolve(workspaceRoot, root))),
|
|
71
73
|
)
|
|
72
74
|
)
|
|
@@ -157,6 +157,72 @@ describe("SQLite dashboard read model", () => {
|
|
|
157
157
|
await expect(rebuildSqliteReadModel(root)).resolves.toMatchObject({ files: 2 });
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
it("uses current data entities once when the same ID remains in the legacy layout", async () => {
|
|
161
|
+
const root = await createSqliteTestRoot();
|
|
162
|
+
roots.push(root);
|
|
163
|
+
await mkdir(join(root, ".agents", "config"), { recursive: true });
|
|
164
|
+
await mkdir(join(root, ".agents", "records", "tasks"), { recursive: true });
|
|
165
|
+
await mkdir(join(root, ".agents", "data", "tasks"), { recursive: true });
|
|
166
|
+
await writeFile(join(root, ".agents", "config", "project.yaml"), "zaip:\n enabled: true\n");
|
|
167
|
+
const legacy = `---\nid: TASK-DUPLICATE\ntitle: Legacy title\nstatus: completed\ncreated_at: 2026-09-01T10:00:00.000Z\n---\n`;
|
|
168
|
+
const current = `---\nid: TASK-DUPLICATE\ntitle: Current title\nstatus: executing\ncreated_at: 2026-09-02T10:00:00.000Z\n---\n`;
|
|
169
|
+
await writeFile(join(root, ".agents", "records", "tasks", "TASK-DUPLICATE.md"), legacy);
|
|
170
|
+
await writeFile(join(root, ".agents", "data", "tasks", "TASK-DUPLICATE.md"), current);
|
|
171
|
+
|
|
172
|
+
await rebuildSqliteReadModel(root);
|
|
173
|
+
const metadata = new DatabaseSync(
|
|
174
|
+
join(root, ".local", "sqlite", "zaip-aios-dashboard.sqlite"),
|
|
175
|
+
{
|
|
176
|
+
readOnly: true,
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
try {
|
|
180
|
+
expect(
|
|
181
|
+
metadata
|
|
182
|
+
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
|
|
183
|
+
.get(),
|
|
184
|
+
).toEqual({ value: "74" });
|
|
185
|
+
} finally {
|
|
186
|
+
metadata.close();
|
|
187
|
+
}
|
|
188
|
+
const filesystem = new SqliteReadModelFileSystem(root);
|
|
189
|
+
try {
|
|
190
|
+
expect(filesystem.canonicalWorkPage("task", { from: null, to: null }, 0, 10)).toMatchObject({
|
|
191
|
+
total_count: 1,
|
|
192
|
+
rows: [expect.objectContaining({ id: "TASK-DUPLICATE", title: "Current title" })],
|
|
193
|
+
});
|
|
194
|
+
} finally {
|
|
195
|
+
filesystem.close();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await writeFile(
|
|
199
|
+
join(root, ".agents", "records", "tasks", "TASK-DUPLICATE.md"),
|
|
200
|
+
legacy.replace("Legacy title", "Edited legacy title"),
|
|
201
|
+
);
|
|
202
|
+
await rebuildSqliteReadModel(root);
|
|
203
|
+
const currentCached = new SqliteReadModelFileSystem(root);
|
|
204
|
+
try {
|
|
205
|
+
expect(
|
|
206
|
+
currentCached.canonicalWorkPage("task", { from: null, to: null }, 0, 10),
|
|
207
|
+
).toMatchObject({
|
|
208
|
+
rows: [expect.objectContaining({ title: "Current title" })],
|
|
209
|
+
});
|
|
210
|
+
} finally {
|
|
211
|
+
currentCached.close();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await rm(join(root, ".agents", "data", "tasks", "TASK-DUPLICATE.md"));
|
|
215
|
+
await rebuildSqliteReadModel(root);
|
|
216
|
+
const fallback = new SqliteReadModelFileSystem(root);
|
|
217
|
+
try {
|
|
218
|
+
expect(fallback.canonicalWorkPage("task", { from: null, to: null }, 0, 10)).toMatchObject({
|
|
219
|
+
rows: [expect.objectContaining({ title: "Edited legacy title" })],
|
|
220
|
+
});
|
|
221
|
+
} finally {
|
|
222
|
+
fallback.close();
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
160
226
|
it("accepts only the canonical C3D4 historical assignment among lifecycle files", async () => {
|
|
161
227
|
const root = await createSqliteTestRoot();
|
|
162
228
|
roots.push(root);
|
|
@@ -1252,7 +1318,7 @@ created_at: 2026-08-10T11:01:00.000Z
|
|
|
1252
1318
|
snapshot
|
|
1253
1319
|
.prepare("SELECT value FROM zaip_vw_read_model_metadata WHERE key='schema_version'")
|
|
1254
1320
|
.get(),
|
|
1255
|
-
).toEqual({ value: "
|
|
1321
|
+
).toEqual({ value: "74" });
|
|
1256
1322
|
} finally {
|
|
1257
1323
|
snapshot.close();
|
|
1258
1324
|
}
|
|
@@ -2464,7 +2530,7 @@ created_at: 2026-08-10T11:01:00.000Z
|
|
|
2464
2530
|
);
|
|
2465
2531
|
await writeFile(
|
|
2466
2532
|
join(root, ".agents", "config", "framework.lock.yaml"),
|
|
2467
|
-
"lock_version: 1\nframework:\n version:
|
|
2533
|
+
"lock_version: 1\nframework:\n version: 2.0.0\n distribution_sha256: null\nlayout:\n version: 3\nresolved_profile:\n id: null\n sha256: null\nmanaged_files: {}\n",
|
|
2468
2534
|
"utf8",
|
|
2469
2535
|
);
|
|
2470
2536
|
const run = (
|
|
@@ -77,9 +77,10 @@ import type { SqliteReadModelSourceCheckpoint } from "./sqlite-read-model-source
|
|
|
77
77
|
import { sourceFingerprint } from "./sqlite-read-model-source-fingerprint.js";
|
|
78
78
|
|
|
79
79
|
const dbRelativePath = ".local/sqlite/zaip-aios-dashboard.sqlite";
|
|
80
|
-
// Schema
|
|
80
|
+
// Schema 74 invalidates parser checkpoints produced before .agents/data became
|
|
81
|
+
// canonical input. A schema rebuild is required even when source hashes match.
|
|
81
82
|
// Older snapshots initialize these inputs once in the same database transaction.
|
|
82
|
-
const readModelSchemaVersion = "
|
|
83
|
+
const readModelSchemaVersion = "74";
|
|
83
84
|
const requiredPersistedTables = [
|
|
84
85
|
"zaip_aios_activities",
|
|
85
86
|
"zaip_dim_llm_model",
|
|
@@ -598,12 +599,12 @@ export async function rebuildSqliteReadModel(
|
|
|
598
599
|
source.includes("/historical-developer-attribution/"),
|
|
599
600
|
);
|
|
600
601
|
const entityChanged = [...changedSources].some((source) =>
|
|
601
|
-
/^\.agents\/records\/(tasks|specs|prds)\//.test(source),
|
|
602
|
+
/^\.agents\/(?:data|records)\/(tasks|specs|prds)\//.test(source),
|
|
602
603
|
);
|
|
603
604
|
const changedCreationRunIds = new Set<string>();
|
|
604
605
|
if (hasPersistentSchema)
|
|
605
606
|
for (const source of changedSources) {
|
|
606
|
-
if (
|
|
607
|
+
if (!/^\.agents\/(?:data|records)\/specs\//.test(source)) continue;
|
|
607
608
|
const cached = database
|
|
608
609
|
.prepare(
|
|
609
610
|
"SELECT payload_json FROM zaip_aios_activities WHERE payload_type='parsed_source' AND activity_key=?",
|
|
@@ -620,6 +621,14 @@ export async function rebuildSqliteReadModel(
|
|
|
620
621
|
.filter((row) => configurationChanged || changedSources.has(row.source_path))
|
|
621
622
|
.map((row) => String(row.run_id)),
|
|
622
623
|
);
|
|
624
|
+
// A newly-added alternate layout has no prior activity row, but can name
|
|
625
|
+
// the same Run already materialized from the current layout. Mark that
|
|
626
|
+
// Run dirty before retaining canonical facts so the later preferred fold
|
|
627
|
+
// replaces it instead of colliding with a preserved run-index row.
|
|
628
|
+
for (const source of changedSources) {
|
|
629
|
+
const match = /^\.agents\/(?:data|records)\/runs\/.*\/([^/]+)\.jsonl$/.exec(source);
|
|
630
|
+
if (match?.[1]) dirtyRunIds.add(match[1]);
|
|
631
|
+
}
|
|
623
632
|
// A parent identity change may affect descendants even when their own bytes are unchanged.
|
|
624
633
|
let expanded = true;
|
|
625
634
|
while (expanded) {
|
|
@@ -837,7 +846,7 @@ export async function rebuildSqliteReadModel(
|
|
|
837
846
|
reportLaunchProgress("projection_build", 74, "Organizando fatos e projeções do banco local.");
|
|
838
847
|
const canonicalRows = database
|
|
839
848
|
.prepare(
|
|
840
|
-
"SELECT path, content FROM files WHERE path LIKE '.agents/records/%' AND path NOT LIKE '.agents/records/runs/%'",
|
|
849
|
+
"SELECT path, content FROM files WHERE (path LIKE '.agents/data/%' OR path LIKE '.agents/records/%') AND path NOT LIKE '.agents/data/runs/%' AND path NOT LIKE '.agents/records/runs/%' ORDER BY CASE WHEN path LIKE '.agents/data/%' THEN 1 ELSE 0 END, path",
|
|
841
850
|
)
|
|
842
851
|
.all() as Array<{ path: string; content: string }>;
|
|
843
852
|
const canonicalEntities = new Map<string, CanonicalEntity>(
|
|
@@ -851,7 +860,7 @@ export async function rebuildSqliteReadModel(
|
|
|
851
860
|
),
|
|
852
861
|
);
|
|
853
862
|
for (const row of canonicalRows)
|
|
854
|
-
if (changedSources.has(row.path) && row.path
|
|
863
|
+
if (changedSources.has(row.path) && /^\.agents\/(?:data|records)\/specs\//.test(row.path))
|
|
855
864
|
for (const [, entity] of parsedSources.read<Array<[string, CanonicalEntity]>>(
|
|
856
865
|
"entities",
|
|
857
866
|
row.path,
|
|
@@ -897,6 +906,17 @@ export async function rebuildSqliteReadModel(
|
|
|
897
906
|
canonicalLifecycle.changes.push(...parsed.changes);
|
|
898
907
|
canonicalLifecycle.decisions.push(...parsed.decisions);
|
|
899
908
|
}
|
|
909
|
+
// Both supported layouts can coexist during an upgrade. SQL ordering puts
|
|
910
|
+
// .agents/data last, so the installed current record wins by stable ID.
|
|
911
|
+
canonicalLifecycle.executions = [
|
|
912
|
+
...new Map(canonicalLifecycle.executions.map((item) => [item.id, item])).values(),
|
|
913
|
+
];
|
|
914
|
+
canonicalLifecycle.changes = [
|
|
915
|
+
...new Map(canonicalLifecycle.changes.map((item) => [item.id, item])).values(),
|
|
916
|
+
];
|
|
917
|
+
canonicalLifecycle.decisions = [
|
|
918
|
+
...new Map(canonicalLifecycle.decisions.map((item) => [item.id, item])).values(),
|
|
919
|
+
];
|
|
900
920
|
const insertCanonicalExecution = database.prepare(
|
|
901
921
|
"INSERT INTO canonical_executions(id, status, kind, origin_type, origin_id, created_at, occurred_day_sp, finished_at, source_path) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
902
922
|
);
|
|
@@ -982,7 +1002,7 @@ export async function rebuildSqliteReadModel(
|
|
|
982
1002
|
);
|
|
983
1003
|
const runRows = database
|
|
984
1004
|
.prepare(
|
|
985
|
-
"SELECT path, content, content_sha256 FROM files WHERE path LIKE '.agents/records/runs/%.jsonl' ORDER BY path",
|
|
1005
|
+
"SELECT path, content, content_sha256 FROM files WHERE (path LIKE '.agents/data/runs/%.jsonl' OR path LIKE '.agents/records/runs/%.jsonl') ORDER BY CASE WHEN path LIKE '.agents/data/%' THEN 1 ELSE 0 END, path",
|
|
986
1006
|
)
|
|
987
1007
|
.all() as Array<{ path: string; content: string; content_sha256: string }>;
|
|
988
1008
|
|
|
@@ -992,7 +1012,7 @@ export async function rebuildSqliteReadModel(
|
|
|
992
1012
|
const historicalDeveloperManifests = (
|
|
993
1013
|
database
|
|
994
1014
|
.prepare(
|
|
995
|
-
"SELECT content FROM files WHERE path LIKE '.agents/records/lifecycle/historical-developer-attribution/%.json'",
|
|
1015
|
+
"SELECT content FROM files WHERE path LIKE '.agents/data/lifecycle/historical-developer-attribution/%.json' OR path LIKE '.agents/records/lifecycle/historical-developer-attribution/%.json'",
|
|
996
1016
|
)
|
|
997
1017
|
.all() as Array<{ content: string }>
|
|
998
1018
|
).flatMap((row) => {
|
|
@@ -1047,7 +1067,9 @@ export async function rebuildSqliteReadModel(
|
|
|
1047
1067
|
);
|
|
1048
1068
|
for (const previous of previousActivities) {
|
|
1049
1069
|
const row = runRows.find((row) => row.path === previous.source_path);
|
|
1050
|
-
|
|
1070
|
+
const selected = runRowsById.get(previous.run_id);
|
|
1071
|
+
if (row && (selected === undefined || !selected.path.startsWith(".agents/data/")))
|
|
1072
|
+
runRowsById.set(previous.run_id, row);
|
|
1051
1073
|
}
|
|
1052
1074
|
const folds = new Map<string, { row: (typeof runRows)[number]; fold: RunV2Fold }>();
|
|
1053
1075
|
const readFold = (row: (typeof runRows)[number]) =>
|
|
@@ -3031,6 +3053,13 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3031
3053
|
// exact filter results inside the immutable generation so a tab switch does
|
|
3032
3054
|
// not repeat its multi-second SQL scan.
|
|
3033
3055
|
private readonly costMonitorSummaryCache = new Map<string, CostSummarySource>();
|
|
3056
|
+
|
|
3057
|
+
canonicalSource(): ".agents/data" | ".agents/records" {
|
|
3058
|
+
const current = this.database
|
|
3059
|
+
.prepare("SELECT 1 FROM files WHERE path LIKE '.agents/data/%' LIMIT 1")
|
|
3060
|
+
.get();
|
|
3061
|
+
return current === undefined ? ".agents/records" : ".agents/data";
|
|
3062
|
+
}
|
|
3034
3063
|
private dailyAggregateRows(): Array<Record<string, number | string | null>> {
|
|
3035
3064
|
if (this.dailyAggregateRowsCache === null) {
|
|
3036
3065
|
this.dailyAggregateRowsCache = this.database
|
|
@@ -3527,7 +3556,7 @@ export class SqliteReadModelFileSystem extends WorkspaceFileSystem {
|
|
|
3527
3556
|
const clauses = selected.map(() => "content LIKE ?").join(" OR ");
|
|
3528
3557
|
return this.database
|
|
3529
3558
|
.prepare(`SELECT path, content FROM files
|
|
3530
|
-
WHERE path LIKE '.agents/records/%' AND (${clauses}) ORDER BY path`)
|
|
3559
|
+
WHERE (path LIKE '.agents/data/%' OR path LIKE '.agents/records/%') AND (${clauses}) ORDER BY path`)
|
|
3531
3560
|
.all(...selected.map((marker) => `%${marker}%`)) as Array<{
|
|
3532
3561
|
path: string;
|
|
3533
3562
|
content: string;
|