Merge PR #7678: fix(session): migrate hashed session dirs back to legacy names (@roboomp)
This commit is contained in:
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user