fix: avoid synthetic dates on restored agent files

Co-Authored-By: Paperclip <[email protected]>
This commit is contained in:
TonioandPaperclip committed 2026-11-06 10:22:35 -08:00
1 parent 254e872fcf
commit f31405a730
2 files changed
+32 -5

No files matched your search

@@ -111,6 +111,31 @@ describe("durable agent file authority", () => {
} })).toEqual({ entryFile: "missing.md", contentHash: null });
});
it("keeps recorded file dates through restore without treating projection times as edits", async () => {
await materializeInstructionBytes(root, "AGENTS.md", Buffer.from("Historical instructions"));
await db.update(agents).set({ adapterConfig: { instructionsBundleMode: "managed", instructionsRootPath: root,
instructionsEntryFile: "AGENTS.md" } }).where(eq(agents.id, agentId));
const [agent] = await db.select().from(agents).where(eq(agents.id, agentId));
await store().read(companyId, agentId, "AGENTS.md", actor());
await store().write({ companyId, agentId, path: "NOTES.md", bytes: Buffer.from("Recorded creation"), baseHash: null }, actor());
const instructions = agentInstructionsService(db);
const created = await instructions.readFile(agent, "NOTES.md");
expect(Number.isFinite(Date.parse(created.createdAt!))).toBe(true);
await replaceFilesystem();
vi.stubEnv("PAPERCLIP_DURABLE_AGENT_FILES", "false");
const restored = await instructions.readFile(agent, "AGENTS.md");
expect(restored.content).toBe("Historical instructions");
expect(restored.createdAt).toBeUndefined();
expect(restored.updatedAt).toBeUndefined();
const summary = (await instructions.getBundle(agent)).files.find(file => file.path === "AGENTS.md");
expect(summary).toBeDefined();
expect(summary?.createdAt).toBeUndefined();
expect(summary?.updatedAt).toBeUndefined();
const recorded = await instructions.readFile(agent, "NOTES.md");
expect(recorded.createdAt).toBe(created.createdAt);
expect(recorded.updatedAt).toBeUndefined();
});
it("imports legacy bytes, modes and empty entries, then restores without any old filesystem", async () => {
const binary = Buffer.alloc(2 * 1024 * 1024, 0x81);
await materializeInstructionBytes(root, "AGENTS.md", Buffer.from("Original"));
+7 -5
View File
@@ -221,12 +221,12 @@ async function listFilesRecursive(
return output.sort((left, right) => left.localeCompare(right));
}
async function readFileSummary(rootPath: string, relativePath: string, entryFile: string): Promise<AgentInstructionsFileSummary> {
async function readFileSummary(rootPath: string, relativePath: string, entryFile: string, includeFilesystemDate: boolean): Promise<AgentInstructionsFileSummary> {
const absolutePath = resolvePathWithinRoot(rootPath, relativePath);
const stat = await fs.stat(absolutePath);
// External bundles may contain large assets; listing must not read them.
const file = stat.size > MAX_AGENT_FILE_BYTES ? null : await inspectAgentFile(rootPath, relativePath);
return { ...summarizeFile(relativePath, entryFile, file?.size ?? stat.size, file?.bytes ?? null, file?.hash), updatedAt: stat.mtime.toISOString() };
return { ...summarizeFile(relativePath, entryFile, file?.size ?? stat.size, file?.bytes ?? null, file?.hash), ...(includeFilesystemDate ? { updatedAt: stat.mtime.toISOString() } : {}) };
}
function summarizeFile(relativePath: string, entryFile: string, size: number, bytes: Buffer | null, hash?: string): AgentInstructionsFileSummary {
@@ -535,7 +535,7 @@ export function agentInstructionsService(db?: Db) {
.orderBy(desc(activityLog.createdAt)).limit(1);
if (saved) return saved.details?.created === true ? { createdAt: saved.date.toISOString() } : { updatedAt: saved.date.toISOString() };
}
// Filesystem birth times reset on atomic replacement/restoration. Only expose mtime.
if (db && state.mode !== "external") return {};
const stat = await statIfExists(filename);
return stat?.isFile() ? { updatedAt: stat.mtime.toISOString() } : {};
}
@@ -577,7 +577,7 @@ export function agentInstructionsService(db?: Db) {
const summaries: AgentInstructionsFileSummary[] = [];
// Bound open descriptors and text buffers even for a large personal folder.
for (let index = 0; index < files.length; index += 8) {
summaries.push(...await Promise.all(files.slice(index, index + 8).map(relativePath => readFileSummary(state.rootPath!, relativePath, state.entryFile))));
summaries.push(...await Promise.all(files.slice(index, index + 8).map(relativePath => readFileSummary(state.rootPath!, relativePath, state.entryFile, !db || state.mode === "external"))));
}
return toBundle(agent, state, summaries);
}
@@ -746,12 +746,14 @@ export function agentInstructionsService(db?: Db) {
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
const existed = Boolean(await statIfExists(absolutePath));
await fs.writeFile(absolutePath, content, "utf8");
const writtenAt = new Date().toISOString();
const nextAgent = { ...agent, adapterConfig: prepared.adapterConfig };
const [bundle, file] = await Promise.all([
getBundle(nextAgent),
readFile(nextAgent, relativePath),
]);
if (!existed && file.updatedAt) { file.createdAt = file.updatedAt; delete file.updatedAt; }
if (existed) file.updatedAt = writtenAt;
else { file.createdAt = writtenAt; delete file.updatedAt; }
return { bundle, file, adapterConfig: prepared.adapterConfig };
}