diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0abac08d8..94a9cc9b7 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -99,6 +99,10 @@ - Fixed a per-turn `before_agent_start` system prompt override being silently dropped when a base-prompt rebuild fired between the hook and the provider request. The override lived only on the agent state, so `refreshBaseSystemPrompt`/`applyActiveToolsByName` re-pushing the rebuilt base (context-overflow compaction/promotion, memory promotion, MCP/RPC tool refresh, or the fire-and-forget hindsight MM-TTL refresh) clobbered it. The tools controller now tracks the active override and re-applies it on every base rebuild during the turn, clearing it when the turn ends ([#7755](https://github.com/can1357/oh-my-pi/issues/7755)). +### Fixed + +- Fixed ACP `session/load` and `session/resume` failing with `ACP session not found` for sessions created under the legacy/hashed project-directory scheme (17.2.5+, reverted in #7656): the lookup only scanned the directory re-derived from `cwd`, so sessions stored under a differently-named directory were unreachable. It now falls back to a global by-id scan (the same one the fork path already uses) when the cwd-scoped lookup misses ([#7779](https://github.com/can1357/oh-my-pi/issues/7779)). + ## [17.2.9] - 2026-08-05 ### Breaking Changes diff --git a/packages/coding-agent/src/modes/acp/acp-agent.ts b/packages/coding-agent/src/modes/acp/acp-agent.ts index b3852dc11..c4d177ca8 100644 --- a/packages/coding-agent/src/modes/acp/acp-agent.ts +++ b/packages/coding-agent/src/modes/acp/acp-agent.ts @@ -2019,7 +2019,16 @@ export class AcpAgent implements Agent { async #findStoredSession(sessionId: string, cwd: string): Promise { const sessions = await this.#listStoredSessions(cwd); - return sessions.find(session => session.id === sessionId); + const scoped = sessions.find(session => session.id === sessionId); + if (scoped) { + return scoped; + } + // The cwd-derived directory only covers sessions stored under the current + // naming scheme. Sessions written under a legacy/hashed project directory + // (the 17.2.5+ scheme reverted in #7656) live elsewhere, so fall back to a + // global by-id scan: the session id is globally unique, and + // #openStoredSession reopens the file with the request cwd. See #7779. + return this.#findStoredSessionById(sessionId); } async #findStoredSessionById(sessionId: string): Promise { diff --git a/packages/coding-agent/test/acp-agent.test.ts b/packages/coding-agent/test/acp-agent.test.ts index 1ea177a9f..f3d4b4ea2 100644 --- a/packages/coding-agent/test/acp-agent.test.ts +++ b/packages/coding-agent/test/acp-agent.test.ts @@ -1098,6 +1098,48 @@ describe("ACP agent", () => { await Bun.sleep(0); }); + it("loads a session stored under a legacy/hashed project directory (#7779)", async () => { + const harness = await createHarness(); + const stored = new FakeAgentSession(harness.cwdA); + harness.sessions.push(stored); + stored.sessionManager.appendMessage({ role: "user", content: "legacy hello", timestamp: Date.now() }); + stored.sessionManager.appendMessage(makeAssistantMessage("legacy reply")); + await stored.sessionManager.ensureOnDisk(); + await stored.sessionManager.flush(); + + const sessionFile = stored.sessionManager.getSessionFile(); + if (!sessionFile) throw new Error("session file not persisted"); + const sessionId = stored.sessionId; + // Release the writer so the directory can be renamed out from under it. + await stored.dispose(); + + // Simulate the hashed-directory era (#7397, reverted in #7656): the + // session file lives under a project directory whose name the current + // cwd->dir scheme would never produce, so the cwd-scoped scan misses it. + const cwdDerivedDir = path.dirname(sessionFile); + const sessionsRoot = path.dirname(cwdDerivedDir); + const hashedDir = path.join(sessionsRoot, `home-cwd-a-${"a".repeat(64)}`); + await fs.promises.rename(cwdDerivedDir, hashedDir); + + const loaded = await harness.agent.loadSession({ + sessionId, + cwd: harness.cwdA, + mcpServers: [], + }); + expectAcpStructure(zLoadSessionResponse, loaded); + + const replayChunks = harness.updates.filter( + update => + update.sessionId === sessionId && + (update.update.sessionUpdate === "user_message_chunk" || + update.update.sessionUpdate === "agent_message_chunk"), + ); + expect(replayChunks.length).toBeGreaterThan(0); + + harness.abortController.abort(); + await Bun.sleep(0); + }); + it("delivers the final visible answer when agent_end overtakes the assistant message_end (#4902)", async () => { const harness = await createHarness(); const created = await harness.agent.newSession({ cwd: harness.cwdA, mcpServers: [] });