fix(coding-agent): handle missing session cwd
This commit is contained in:
@@ -5,6 +5,7 @@ import type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from "./agen
|
||||
import type { SessionStartEvent } from "./extensions/index.js";
|
||||
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
||||
import type { CreateAgentSessionResult } from "./sdk.js";
|
||||
import { assertSessionCwdExists } from "./session-cwd.js";
|
||||
import { SessionManager } from "./session-manager.js";
|
||||
|
||||
/**
|
||||
@@ -124,14 +125,15 @@ export class AgentSessionRuntime {
|
||||
this._modelFallbackMessage = result.modelFallbackMessage;
|
||||
}
|
||||
|
||||
async switchSession(sessionPath: string): Promise<{ cancelled: boolean }> {
|
||||
async switchSession(sessionPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> {
|
||||
const beforeResult = await this.emitBeforeSwitch("resume", sessionPath);
|
||||
if (beforeResult.cancelled) {
|
||||
return beforeResult;
|
||||
}
|
||||
|
||||
const previousSessionFile = this.session.sessionFile;
|
||||
const sessionManager = SessionManager.open(sessionPath);
|
||||
const sessionManager = SessionManager.open(sessionPath, undefined, cwdOverride);
|
||||
assertSessionCwdExists(sessionManager, this.cwd);
|
||||
await this.teardownCurrent();
|
||||
this.apply(
|
||||
await this.createRuntime({
|
||||
@@ -246,7 +248,7 @@ export class AgentSessionRuntime {
|
||||
return { cancelled: false, selectedText };
|
||||
}
|
||||
|
||||
async importFromJsonl(inputPath: string): Promise<{ cancelled: boolean }> {
|
||||
async importFromJsonl(inputPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> {
|
||||
const resolvedPath = resolve(inputPath);
|
||||
if (!existsSync(resolvedPath)) {
|
||||
throw new Error(`File not found: ${resolvedPath}`);
|
||||
@@ -268,7 +270,8 @@ export class AgentSessionRuntime {
|
||||
copyFileSync(resolvedPath, destinationPath);
|
||||
}
|
||||
|
||||
const sessionManager = SessionManager.open(destinationPath, sessionDir);
|
||||
const sessionManager = SessionManager.open(destinationPath, sessionDir, cwdOverride);
|
||||
assertSessionCwdExists(sessionManager, this.cwd);
|
||||
await this.teardownCurrent();
|
||||
this.apply(
|
||||
await this.createRuntime({
|
||||
@@ -302,6 +305,7 @@ export async function createAgentSessionRuntime(
|
||||
sessionStartEvent?: SessionStartEvent;
|
||||
},
|
||||
): Promise<AgentSessionRuntime> {
|
||||
assertSessionCwdExists(options.sessionManager, options.cwd);
|
||||
const result = await createRuntime(options);
|
||||
if (process.cwd() !== result.services.cwd) {
|
||||
process.chdir(result.services.cwd);
|
||||
|
||||
59
packages/coding-agent/src/core/session-cwd.ts
Normal file
59
packages/coding-agent/src/core/session-cwd.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
export interface SessionCwdIssue {
|
||||
sessionFile?: string;
|
||||
sessionCwd: string;
|
||||
fallbackCwd: string;
|
||||
}
|
||||
|
||||
interface SessionCwdSource {
|
||||
getCwd(): string;
|
||||
getSessionFile(): string | undefined;
|
||||
}
|
||||
|
||||
export function getMissingSessionCwdIssue(
|
||||
sessionManager: SessionCwdSource,
|
||||
fallbackCwd: string,
|
||||
): SessionCwdIssue | undefined {
|
||||
const sessionFile = sessionManager.getSessionFile();
|
||||
if (!sessionFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sessionCwd = sessionManager.getCwd();
|
||||
if (!sessionCwd || existsSync(sessionCwd)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
sessionFile,
|
||||
sessionCwd,
|
||||
fallbackCwd,
|
||||
};
|
||||
}
|
||||
|
||||
export function formatMissingSessionCwdError(issue: SessionCwdIssue): string {
|
||||
const sessionFile = issue.sessionFile ? `\nSession file: ${issue.sessionFile}` : "";
|
||||
return `Stored session working directory does not exist: ${issue.sessionCwd}${sessionFile}\nCurrent working directory: ${issue.fallbackCwd}`;
|
||||
}
|
||||
|
||||
export function formatMissingSessionCwdPrompt(issue: SessionCwdIssue): string {
|
||||
return `cwd from session file does not exist\n${issue.sessionCwd}\n\ncontinue in current cwd\n${issue.fallbackCwd}`;
|
||||
}
|
||||
|
||||
export class MissingSessionCwdError extends Error {
|
||||
readonly issue: SessionCwdIssue;
|
||||
|
||||
constructor(issue: SessionCwdIssue) {
|
||||
super(formatMissingSessionCwdError(issue));
|
||||
this.name = "MissingSessionCwdError";
|
||||
this.issue = issue;
|
||||
}
|
||||
}
|
||||
|
||||
export function assertSessionCwdExists(sessionManager: SessionCwdSource, fallbackCwd: string): void {
|
||||
const issue = getMissingSessionCwdIssue(sessionManager, fallbackCwd);
|
||||
if (issue) {
|
||||
throw new MissingSessionCwdError(issue);
|
||||
}
|
||||
}
|
||||
@@ -1270,12 +1270,13 @@ export class SessionManager {
|
||||
* Open a specific session file.
|
||||
* @param path Path to session file
|
||||
* @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent.
|
||||
* @param cwdOverride Optional cwd override instead of the session header cwd.
|
||||
*/
|
||||
static open(path: string, sessionDir?: string): SessionManager {
|
||||
static open(path: string, sessionDir?: string, cwdOverride?: string): SessionManager {
|
||||
// Extract cwd from session header if possible, otherwise use process.cwd()
|
||||
const entries = loadEntriesFromFile(path);
|
||||
const header = entries.find((e) => e.type === "session") as SessionHeader | undefined;
|
||||
const cwd = header?.cwd ?? process.cwd();
|
||||
const cwd = cwdOverride ?? header?.cwd ?? process.cwd();
|
||||
// If no sessionDir provided, derive from file's parent directory
|
||||
const dir = sessionDir ?? resolve(path, "..");
|
||||
return new SessionManager(cwd, dir, path, true);
|
||||
|
||||
Reference in New Issue
Block a user