mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 04:02:18 +01:00
fix(server): preserve live instruction locks
Only recover owner locks when their process is gone; use age solely when owner metadata cannot identify a process. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
e3b05223fa
commit
b8f08ea794
2 files changed
+47
-7
No files matched your search
@@ -489,6 +489,44 @@ describe("agent instructions service", () => {
|
||||
await expect(fs.stat(lockPath)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("does not evict an old materialization lock owned by a live process", async () => {
|
||||
const paperclipHome = await makeTempDir("paperclip-agent-instructions-live-lock-");
|
||||
cleanupDirs.add(paperclipHome);
|
||||
process.env.PAPERCLIP_HOME = paperclipHome;
|
||||
process.env.PAPERCLIP_INSTANCE_ID = "test-instance";
|
||||
const svc = agentInstructionsService();
|
||||
const agent = makeAgent({});
|
||||
const managedRoot = path.join(
|
||||
paperclipHome,
|
||||
"instances",
|
||||
"test-instance",
|
||||
"companies",
|
||||
"company-1",
|
||||
"agents",
|
||||
"agent-1",
|
||||
"instructions",
|
||||
);
|
||||
const lockPath = `${managedRoot}.paperclip-materialize.lock`;
|
||||
await fs.mkdir(lockPath, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(lockPath, "owner.json"),
|
||||
`${JSON.stringify({ pid: process.pid, token: "live", createdAt: "1970-01-01T00:00:00.000Z" })}\n`,
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const materialization = svc.materializeManagedBundle(agent, { "AGENTS.md": "# Stock\n" });
|
||||
const firstOutcome = await Promise.race([
|
||||
materialization.then(() => "materialized"),
|
||||
new Promise<"waiting">((resolve) => setTimeout(() => resolve("waiting"), 75)),
|
||||
]);
|
||||
|
||||
expect(firstOutcome).toBe("waiting");
|
||||
await fs.rm(lockPath, { recursive: true, force: true });
|
||||
await expect(materialization).resolves.toMatchObject({
|
||||
materialization: { action: "added" },
|
||||
});
|
||||
});
|
||||
|
||||
it("canonicalizes line endings and ignores transient files when classifying drift", async () => {
|
||||
const paperclipHome = await makeTempDir("paperclip-agent-instructions-canonical-");
|
||||
cleanupDirs.add(paperclipHome);
|
||||
|
||||
@@ -192,14 +192,16 @@ async function removeStaleManagedBundleLock(lockPath: string): Promise<boolean>
|
||||
try {
|
||||
const owner = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf8")) as {
|
||||
pid?: unknown;
|
||||
createdAt?: unknown;
|
||||
};
|
||||
const pid = typeof owner.pid === "number" ? owner.pid : 0;
|
||||
const createdAt = typeof owner.createdAt === "string" ? Date.parse(owner.createdAt) : Number.NaN;
|
||||
const ageMs = Number.isFinite(createdAt)
|
||||
? Date.now() - createdAt
|
||||
: MANAGED_BUNDLE_LOCK_STALE_MS + 1;
|
||||
shouldRemove = !isProcessAlive(pid) || ageMs > MANAGED_BUNDLE_LOCK_STALE_MS;
|
||||
const pid = typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0
|
||||
? owner.pid
|
||||
: null;
|
||||
if (pid === null) {
|
||||
const stat = await statIfExists(lockPath);
|
||||
shouldRemove = !stat || Date.now() - stat.mtimeMs > MANAGED_BUNDLE_LOCK_STALE_MS;
|
||||
} else {
|
||||
shouldRemove = !isProcessAlive(pid);
|
||||
}
|
||||
} catch {
|
||||
const stat = await statIfExists(lockPath);
|
||||
shouldRemove = !stat || Date.now() - stat.mtimeMs > MANAGED_BUNDLE_LOCK_STALE_MS;
|
||||
|
||||
Reference in new issue
Block a user