Merge PR #7678: fix(session): migrate hashed session dirs back to legacy names (@roboomp)

This commit is contained in:
can1357
2026-08-05 22:16:28 +02:00
3 changed files with 87 additions and 9 deletions
+3
View File
@@ -39,6 +39,9 @@
### Fixed
- Fixed approved plans hiding live execution progress: after the fullscreen Plan Review closed, the conversation view stayed blank/stale while the plan executed. The propose write's `tool_execution_end` handler ran inside the event-controller's serialized dispatch chain and awaited `handlePlanApproval`, which awaits `session.prompt` for the entire execution turn — so every later `agent_start`/`message_start`/tool/`message_update` event queued behind it until the run finished. The approval dispatch is now detached from the dispatch chain, so the turn's events render live ([#7684](https://github.com/can1357/oh-my-pi/issues/7684)).
### Fixed
- Fixed `omp -r` current-folder scope missing sessions written under the short-lived hashed project-directory scheme (17.2.5-17.2.8): the 17.2.9 revert restored the legacy path-based names but dropped all migration, stranding those sessions. `computeDefaultSessionDir` now performs a one-way migration of the hashed dir back into its legacy name ([#7677](https://github.com/can1357/oh-my-pi/issues/7677)).
## [17.2.9] - 2026-08-05
@@ -42,7 +42,28 @@ function encodeRelativeSessionDirName(prefix: string, relative: string): string
return encoded ? (prefix.endsWith("-") ? `${prefix}${encoded}` : `${prefix}-${encoded}`) : prefix;
}
function getDefaultSessionDirName(cwd: string): { encodedDirName: string; resolvedCwd: string } {
/**
* Reconstruct the short-lived hashed session dir name used by 17.2.5-17.2.8
* (reverted PR #7397): `<scope>-<readable>-<sha256hex>` keyed by the canonical
* cwd. Kept only so {@link migrateHashedSessionDir} can recover sessions
* stranded when 17.2.9 restored the legacy names without a reverse migration.
*/
function encodeHashedSessionDirName(canonicalCwd: string, scope: "home" | "tmp" | "abs"): string {
const normalized = canonicalCwd.replaceAll("\\", "/");
const readable = path
.basename(canonicalCwd)
.replace(/[^a-zA-Z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(-80);
const digest = Bun.SHA256.hash(normalized, "hex");
return `${scope}-${readable || "project"}-${digest}`;
}
function getDefaultSessionDirName(cwd: string): {
encodedDirName: string;
hashedDirName: string;
resolvedCwd: string;
} {
const resolvedCwd = path.resolve(cwd);
const canonicalCwd = resolveEquivalentPath(resolvedCwd);
const home = os.homedir();
@@ -51,13 +72,19 @@ function getDefaultSessionDirName(cwd: string): { encodedDirName: string; resolv
const canonicalTempRoot = resolveEquivalentPath(tempRoot);
const homeRelative = path.relative(canonicalHome, canonicalCwd);
const tempRelative = path.relative(canonicalTempRoot, canonicalCwd);
const encodedDirName =
homeRelative === "" || (!homeRelative.startsWith("..") && !path.isAbsolute(homeRelative))
? encodeRelativeSessionDirName("-", homeRelative)
: tempRelative === "" || (!tempRelative.startsWith("..") && !path.isAbsolute(tempRelative))
? encodeRelativeSessionDirName("-tmp", tempRelative)
: encodeLegacyAbsoluteSessionDirName(canonicalCwd);
return { encodedDirName, resolvedCwd };
let encodedDirName: string;
let scope: "home" | "tmp" | "abs";
if (homeRelative === "" || (!homeRelative.startsWith("..") && !path.isAbsolute(homeRelative))) {
encodedDirName = encodeRelativeSessionDirName("-", homeRelative);
scope = "home";
} else if (tempRelative === "" || (!tempRelative.startsWith("..") && !path.isAbsolute(tempRelative))) {
encodedDirName = encodeRelativeSessionDirName("-tmp", tempRelative);
scope = "tmp";
} else {
encodedDirName = encodeLegacyAbsoluteSessionDirName(canonicalCwd);
scope = "abs";
}
return { encodedDirName, hashedDirName: encodeHashedSessionDirName(canonicalCwd, scope), resolvedCwd };
}
/**
@@ -121,6 +148,26 @@ function migrateLegacyAbsoluteSessionDir(cwd: string, sessionDir: string, sessio
}
}
/**
* Migrate a 17.2.5-17.2.8 hashed session dir back into its legacy path-based
* directory. The 17.2.9 revert restored the legacy names but dropped migration,
* stranding sessions written under the hashed scheme (issue #7677). Best-effort.
*/
function migrateHashedSessionDir(hashedDirName: string, sessionDir: string, sessionsRoot: string): void {
const hashedDir = path.join(sessionsRoot, hashedDirName);
if (hashedDir === sessionDir || !fs.existsSync(hashedDir)) return;
try {
migrateSessionDirPath(hashedDir, sessionDir);
} catch (error) {
logger.warn("Failed to migrate hashed session directory", {
oldPath: hashedDir,
newPath: sessionDir,
error: String(error),
});
}
}
export function resolveManagedSessionRoot(sessionDir: string, cwd: string): string | undefined {
const currentDirName = path.basename(sessionDir);
const { encodedDirName } = getDefaultSessionDirName(cwd);
@@ -140,10 +187,11 @@ export function computeDefaultSessionDir(
storage: SessionStorage,
sessionsRoot: string = getSessionsDir(),
): string {
const { encodedDirName, resolvedCwd } = getDefaultSessionDirName(cwd);
const { encodedDirName, hashedDirName, resolvedCwd } = getDefaultSessionDirName(cwd);
migrateHomeSessionDirs(sessionsRoot);
const sessionDir = path.join(sessionsRoot, encodedDirName);
migrateLegacyAbsoluteSessionDir(resolvedCwd, sessionDir, sessionsRoot);
migrateHashedSessionDir(hashedDirName, sessionDir, sessionsRoot);
storage.ensureDirSync(sessionDir);
return sessionDir;
}
@@ -217,6 +217,33 @@ describe("SessionManager temp cwd session dirs", () => {
expect(path.dirname(sessionFile)).toBe(expectedDir);
expect(fs.existsSync(path.join(expectedDir, "carried.jsonl"))).toBe(true);
});
it("migrates hashed-scheme session dirs back into legacy names", () => {
const tempCwd = path.join(testAgentDir, `hashed-cwd-${Snowflake.next()}`);
fs.mkdirSync(tempCwd, { recursive: true });
// Reconstruct the 17.2.5-17.2.8 hashed dir name (reverted PR #7397).
const canonicalCwd = path.resolve(tempCwd);
const normalized = canonicalCwd.replaceAll("\\", "/");
const readable = path
.basename(canonicalCwd)
.replace(/[^a-zA-Z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(-80);
const digest = Bun.SHA256.hash(normalized, "hex");
const hashedDir = path.join(getSessionsDir(), `tmp-${readable || "project"}-${digest}`);
fs.mkdirSync(hashedDir, { recursive: true });
fs.writeFileSync(path.join(hashedDir, "stranded.jsonl"), "stranded\n");
const session = SessionManager.create(tempCwd);
const sessionFile = session.getSessionFile();
if (!sessionFile) throw new Error("Expected session file path");
const expectedDir = path.join(getSessionsDir(), expectedTempSessionDirName(tempCwd));
expect(fs.existsSync(hashedDir)).toBe(false);
expect(path.dirname(sessionFile)).toBe(expectedDir);
expect(fs.existsSync(path.join(expectedDir, "stranded.jsonl"))).toBe(true);
});
});
describe("SessionManager legacy session migration persistence", () => {