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 persisted = {
version: 1,
version: 2,
runs: {
"run-2": {
runId: "run-2",
@@ -177,6 +177,9 @@ describe("subagent registry persistence", () => {
expect(entry?.cleanupCompletedAt).toBe(9);
expect(entry?.requesterOrigin?.channel).toBe("whatsapp");
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 () => {
@@ -185,7 +188,7 @@ describe("subagent registry persistence", () => {
const registryPath = path.join(tempStateDir, "subagents", "runs.json");
const persisted = {
version: 1,
version: 2,
runs: {
"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 type { SubagentRunRecord } from "./subagent-registry.js";
export type PersistedSubagentRegistryVersion = 1;
export type PersistedSubagentRegistryVersion = 1 | 2;
type PersistedSubagentRegistry = {
type PersistedSubagentRegistryV1 = {
version: 1;
runs: Record<string, LegacySubagentRunRecord>;
};
type PersistedSubagentRegistryV2 = {
version: 2;
runs: Record<string, PersistedSubagentRunRecord>;
};
const REGISTRY_VERSION = 1 as const;
type PersistedSubagentRegistry = PersistedSubagentRegistryV1 | PersistedSubagentRegistryV2;
const REGISTRY_VERSION = 2 as const;
type PersistedSubagentRunRecord = SubagentRunRecord;
@@ -32,22 +39,30 @@ export function loadSubagentRegistryFromDisk(): Map<string, SubagentRunRecord> {
const raw = loadJsonFile(pathname);
if (!raw || typeof raw !== "object") return new Map();
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;
if (!runsRaw || typeof runsRaw !== "object") return new Map();
const out = new Map<string, SubagentRunRecord>();
const isLegacy = record.version === 1;
let migrated = false;
for (const [runId, entry] of Object.entries(runsRaw)) {
if (!entry || typeof entry !== "object") continue;
const typed = entry as LegacySubagentRunRecord;
if (!typed.runId || typeof typed.runId !== "string") continue;
const legacyCompletedAt =
typeof typed.announceCompletedAt === "number" ? typed.announceCompletedAt : undefined;
isLegacy && typeof typed.announceCompletedAt === "number"
? typed.announceCompletedAt
: undefined;
const cleanupCompletedAt =
typeof typed.cleanupCompletedAt === "number" ? typed.cleanupCompletedAt : legacyCompletedAt;
typeof typed.cleanupCompletedAt === "number"
? typed.cleanupCompletedAt
: legacyCompletedAt;
const cleanupHandled =
typeof typed.cleanupHandled === "boolean"
? typed.cleanupHandled
: Boolean(typed.announceHandled ?? cleanupCompletedAt);
: isLegacy
? Boolean(typed.announceHandled ?? cleanupCompletedAt)
: undefined;
const requesterOrigin = normalizeDeliveryContext(
typed.requesterOrigin ?? {
channel: typeof typed.requesterChannel === "string" ? typed.requesterChannel : undefined,
@@ -68,6 +83,14 @@ export function loadSubagentRegistryFromDisk(): Map<string, SubagentRunRecord> {
cleanupCompletedAt,
cleanupHandled,
});
if (isLegacy) migrated = true;
}
if (migrated) {
try {
saveSubagentRegistryToDisk(out);
} catch {
// ignore migration write failures
}
}
return out;
}