diff --git a/packages/pi-agent-core/src/agent-loop.ts b/packages/pi-agent-core/src/agent-loop.ts index fa05a0eff..8379d5853 100644 --- a/packages/pi-agent-core/src/agent-loop.ts +++ b/packages/pi-agent-core/src/agent-loop.ts @@ -118,7 +118,10 @@ export function agentLoopContinue( (async () => { const newMessages: AgentMessage[] = []; - const currentContext: AgentContext = { ...context }; + const currentContext: AgentContext = { + ...context, + messages: [...context.messages], + }; stream.push({ type: "agent_start" }); stream.push({ type: "turn_start" }); diff --git a/packages/pi-coding-agent/src/core/blob-store.ts b/packages/pi-coding-agent/src/core/blob-store.ts index 16262c892..9ad9e4f49 100644 --- a/packages/pi-coding-agent/src/core/blob-store.ts +++ b/packages/pi-coding-agent/src/core/blob-store.ts @@ -6,7 +6,7 @@ * provides automatic deduplication across sessions. */ import { createHash } from "node:crypto"; -import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync, accessSync, unlinkSync, statSync } from "node:fs"; +import { mkdirSync, readdirSync, readFileSync, writeFileSync, accessSync, unlinkSync, statSync } from "node:fs"; import { join } from "node:path"; const BLOB_PREFIX = "blob:sha256:"; @@ -37,8 +37,11 @@ export class BlobStore { }, }; - if (!existsSync(blobPath)) { - writeFileSync(blobPath, data); + try { + writeFileSync(blobPath, data, { flag: "wx" }); // Atomic: fails if file exists + } catch (err: any) { + if (err.code !== "EEXIST") throw err; + // File already exists — expected for content-addressed storage } return result; } diff --git a/packages/pi-coding-agent/src/core/discovery-cache.ts b/packages/pi-coding-agent/src/core/discovery-cache.ts index a75633c2f..d9d9bded8 100644 --- a/packages/pi-coding-agent/src/core/discovery-cache.ts +++ b/packages/pi-coding-agent/src/core/discovery-cache.ts @@ -3,7 +3,7 @@ * Stores results at {agentDir}/discovery-cache.json with per-provider TTLs. */ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs"; import { dirname, join } from "path"; import { getAgentDir } from "../config.js"; import { type DiscoveredModel, getDefaultTTL } from "./model-discovery.js"; @@ -35,6 +35,8 @@ export class ModelDiscoveryCache { } set(provider: string, models: DiscoveredModel[], ttlMs?: number): void { + // Re-read from disk to get the latest state before modifying + this.load(); this.data.entries[provider] = { models, fetchedAt: Date.now(), @@ -50,6 +52,8 @@ export class ModelDiscoveryCache { } clear(provider?: string): void { + // Re-read from disk to get the latest state before modifying + this.load(); if (provider) { delete this.data.entries[provider]; } else { @@ -89,7 +93,10 @@ export class ModelDiscoveryCache { if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } - writeFileSync(this.cachePath, JSON.stringify(this.data, null, 2), "utf-8"); + // Atomic write: write to temp file then rename to avoid partial reads + const tmpPath = this.cachePath + ".tmp"; + writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), "utf-8"); + renameSync(tmpPath, this.cachePath); } catch { // Silently ignore write failures (read-only FS, permissions, etc.) }