refactor: migrate subagent registry store v2

Co-authored-by: adam91holt <adam91holt@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-01-17 04:24:44 +00:00
parent 4f37f66264
commit 780c811146
2 changed files with 35 additions and 9 deletions

View File

@@ -107,7 +107,7 @@ describe("subagent registry persistence", () => {
const registryPath = path.join(tempStateDir, "subagents", "runs.json"); const registryPath = path.join(tempStateDir, "subagents", "runs.json");
const persisted = { const persisted = {
version: 1, version: 2,
runs: { runs: {
"run-2": { "run-2": {
runId: "run-2", runId: "run-2",
@@ -177,6 +177,9 @@ describe("subagent registry persistence", () => {
expect(entry?.cleanupCompletedAt).toBe(9); expect(entry?.cleanupCompletedAt).toBe(9);
expect(entry?.requesterOrigin?.channel).toBe("whatsapp"); expect(entry?.requesterOrigin?.channel).toBe("whatsapp");
expect(entry?.requesterOrigin?.accountId).toBe("legacy-account"); expect(entry?.requesterOrigin?.accountId).toBe("legacy-account");
const after = JSON.parse(await fs.readFile(registryPath, "utf8")) as { version?: number };
expect(after.version).toBe(2);
}); });
it("retries cleanup announce after a failed announce", async () => { it("retries cleanup announce after a failed announce", async () => {
@@ -185,7 +188,7 @@ describe("subagent registry persistence", () => {
const registryPath = path.join(tempStateDir, "subagents", "runs.json"); const registryPath = path.join(tempStateDir, "subagents", "runs.json");
const persisted = { const persisted = {
version: 1, version: 2,
runs: { runs: {
"run-3": { "run-3": {
runId: "run-3", runId: "run-3",

View File

@@ -5,14 +5,21 @@ import { loadJsonFile, saveJsonFile } from "../infra/json-file.js";
import { normalizeDeliveryContext } from "../utils/delivery-context.js"; import { normalizeDeliveryContext } from "../utils/delivery-context.js";
import type { SubagentRunRecord } from "./subagent-registry.js"; import type { SubagentRunRecord } from "./subagent-registry.js";
export type PersistedSubagentRegistryVersion = 1; export type PersistedSubagentRegistryVersion = 1 | 2;
type PersistedSubagentRegistry = { type PersistedSubagentRegistryV1 = {
version: 1; version: 1;
runs: Record<string, LegacySubagentRunRecord>;
};
type PersistedSubagentRegistryV2 = {
version: 2;
runs: Record<string, PersistedSubagentRunRecord>; runs: Record<string, PersistedSubagentRunRecord>;
}; };
const REGISTRY_VERSION = 1 as const; type PersistedSubagentRegistry = PersistedSubagentRegistryV1 | PersistedSubagentRegistryV2;
const REGISTRY_VERSION = 2 as const;
type PersistedSubagentRunRecord = SubagentRunRecord; type PersistedSubagentRunRecord = SubagentRunRecord;
@@ -32,22 +39,30 @@ export function loadSubagentRegistryFromDisk(): Map<string, SubagentRunRecord> {
const raw = loadJsonFile(pathname); const raw = loadJsonFile(pathname);
if (!raw || typeof raw !== "object") return new Map(); if (!raw || typeof raw !== "object") return new Map();
const record = raw as Partial<PersistedSubagentRegistry>; const record = raw as Partial<PersistedSubagentRegistry>;
if (record.version !== REGISTRY_VERSION) return new Map(); if (record.version !== 1 && record.version !== 2) return new Map();
const runsRaw = record.runs; const runsRaw = record.runs;
if (!runsRaw || typeof runsRaw !== "object") return new Map(); if (!runsRaw || typeof runsRaw !== "object") return new Map();
const out = new Map<string, SubagentRunRecord>(); const out = new Map<string, SubagentRunRecord>();
const isLegacy = record.version === 1;
let migrated = false;
for (const [runId, entry] of Object.entries(runsRaw)) { for (const [runId, entry] of Object.entries(runsRaw)) {
if (!entry || typeof entry !== "object") continue; if (!entry || typeof entry !== "object") continue;
const typed = entry as LegacySubagentRunRecord; const typed = entry as LegacySubagentRunRecord;
if (!typed.runId || typeof typed.runId !== "string") continue; if (!typed.runId || typeof typed.runId !== "string") continue;
const legacyCompletedAt = const legacyCompletedAt =
typeof typed.announceCompletedAt === "number" ? typed.announceCompletedAt : undefined; isLegacy && typeof typed.announceCompletedAt === "number"
? typed.announceCompletedAt
: undefined;
const cleanupCompletedAt = const cleanupCompletedAt =
typeof typed.cleanupCompletedAt === "number" ? typed.cleanupCompletedAt : legacyCompletedAt; typeof typed.cleanupCompletedAt === "number"
? typed.cleanupCompletedAt
: legacyCompletedAt;
const cleanupHandled = const cleanupHandled =
typeof typed.cleanupHandled === "boolean" typeof typed.cleanupHandled === "boolean"
? typed.cleanupHandled ? typed.cleanupHandled
: Boolean(typed.announceHandled ?? cleanupCompletedAt); : isLegacy
? Boolean(typed.announceHandled ?? cleanupCompletedAt)
: undefined;
const requesterOrigin = normalizeDeliveryContext( const requesterOrigin = normalizeDeliveryContext(
typed.requesterOrigin ?? { typed.requesterOrigin ?? {
channel: typeof typed.requesterChannel === "string" ? typed.requesterChannel : undefined, channel: typeof typed.requesterChannel === "string" ? typed.requesterChannel : undefined,
@@ -68,6 +83,14 @@ export function loadSubagentRegistryFromDisk(): Map<string, SubagentRunRecord> {
cleanupCompletedAt, cleanupCompletedAt,
cleanupHandled, cleanupHandled,
}); });
if (isLegacy) migrated = true;
}
if (migrated) {
try {
saveSubagentRegistryToDisk(out);
} catch {
// ignore migration write failures
}
} }
return out; return out;
} }