From 4cbf6993608a6486d856bddb4ce5ff4c247616d0 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:41:32 -0700 Subject: [PATCH] fix: quote queued OMP resumes for Windows shells (#7628) --- .../src/lib/ai-vault-resume-command.test.ts | 29 +++++++++++++++++-- .../src/lib/ai-vault-resume-command.ts | 3 +- src/shared/ai-vault-resume-command.test.ts | 15 ++++++++++ src/shared/ai-vault-types.ts | 10 +++++-- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/lib/ai-vault-resume-command.test.ts b/src/renderer/src/lib/ai-vault-resume-command.test.ts index d6be594878e..4d5db5d7237 100644 --- a/src/renderer/src/lib/ai-vault-resume-command.test.ts +++ b/src/renderer/src/lib/ai-vault-resume-command.test.ts @@ -123,9 +123,9 @@ describe('ai vault resume command runtime', () => { ).toBe("cd 'C:\\Users\\alice\\repo' && claude '--resume' 'session one'") }) - it('resumes a local OMP session by its absolute transcript path, not its id', () => { + it('queues a PowerShell-valid local OMP resume by absolute transcript path', () => { // Regression: local rebuilds must forward session.filePath so OMP resumes by - // path (not the internal id) even when the session lives in a non-default store. + // path, and queued Windows commands must match the live tab shell. const state = makeState({ worktreePath: 'C:\\Users\\alice\\repo' }) const command = buildAiVaultResumeCommandForWorktree({ @@ -141,11 +141,34 @@ describe('ai vault resume command runtime', () => { }) expect(command).toBe( - 'cmd /d /s /c "cd /d ""C:\\Users\\alice\\repo"" && omp --resume ""C:\\Users\\alice\\.omp\\agent\\sessions\\repo\\sess.jsonl"""' + "Set-Location -LiteralPath 'C:\\Users\\alice\\repo'; omp --resume 'C:\\Users\\alice\\.omp\\agent\\sessions\\repo\\sess.jsonl'" ) expect(command).not.toContain('019f27cd-4268-7000-96e7-62f42a55c144') }) + it('keeps cmd quoting for local OMP resume when cmd.exe is configured', () => { + const state = makeState({ + worktreePath: 'C:\\Users\\alice\\repo', + terminalWindowsShell: 'cmd.exe' + }) + + expect( + buildAiVaultResumeCommandForWorktree({ + state, + worktreeId: 'repo-1::worktree-1', + session: { + agent: 'omp', + sessionId: '019f27cd-4268-7000-96e7-62f42a55c144', + filePath: 'C:\\Users\\alice\\.omp\\agent\\sessions\\repo\\sess.jsonl', + cwd: 'C:\\Users\\alice\\repo', + codexHome: null + } + }) + ).toBe( + 'cmd /d /s /c "cd /d ""C:\\Users\\alice\\repo"" && omp --resume ""C:\\Users\\alice\\.omp\\agent\\sessions\\repo\\sess.jsonl"""' + ) + }) + it('keeps the cmd wrapper for the copy-to-clipboard command on Windows', () => { // Regression guard: the copy path is self-contained for pasting into cmd.exe // and must stay cmd-wrapped even though the queued path now follows the shell. diff --git a/src/renderer/src/lib/ai-vault-resume-command.ts b/src/renderer/src/lib/ai-vault-resume-command.ts index ce23324c02a..d71f3653fbb 100644 --- a/src/renderer/src/lib/ai-vault-resume-command.ts +++ b/src/renderer/src/lib/ai-vault-resume-command.ts @@ -136,7 +136,8 @@ export function buildAiVaultResumeStartupForWorktree(args: { cwd: args.session.cwd, platform, commandOverride: args.commandOverride, - codexHome + codexHome, + shell: queuedShell }) } } diff --git a/src/shared/ai-vault-resume-command.test.ts b/src/shared/ai-vault-resume-command.test.ts index 101237ce1c3..65fe12d4c99 100644 --- a/src/shared/ai-vault-resume-command.test.ts +++ b/src/shared/ai-vault-resume-command.test.ts @@ -55,6 +55,21 @@ describe('buildAiVaultResumeCommand', () => { ) }) + it('quotes queued OMP resume paths for the provided Windows shell', () => { + expect( + buildAiVaultResumeCommand({ + agent: 'omp', + sessionId: '019f27cd-4268-7000-96e7-62f42a55c144', + resumeFilePath: 'C:\\Users\\Ada Lovelace\\.omp\\agent\\sessions\\repo\\sess.jsonl', + cwd: 'C:\\Users\\Ada Lovelace\\repo', + platform: 'win32', + shell: 'powershell' + }) + ).toBe( + "Set-Location -LiteralPath 'C:\\Users\\Ada Lovelace\\repo'; omp --resume 'C:\\Users\\Ada Lovelace\\.omp\\agent\\sessions\\repo\\sess.jsonl'" + ) + }) + it('falls back to the session id when no OMP transcript path is known', () => { expect( buildAiVaultResumeCommand({ diff --git a/src/shared/ai-vault-types.ts b/src/shared/ai-vault-types.ts index 4ba2c8820cb..df6abfb8349 100644 --- a/src/shared/ai-vault-types.ts +++ b/src/shared/ai-vault-types.ts @@ -105,18 +105,22 @@ export function buildAiVaultResumeCommand(args: { commandOverride?: string | null codexHome?: string | null resumeFilePath?: string | null + shell?: AgentStartupShell }): string { - const { agent, sessionId, cwd, platform, commandOverride, codexHome, resumeFilePath } = args + const { agent, sessionId, cwd, platform, commandOverride, codexHome, resumeFilePath, shell } = + args const baseCommand = commandOverride?.trim() || defaultAiVaultResumeCommandBase(agent) // Why: OMP's `--resume` accepts an absolute transcript path, which resolves // regardless of which session-dir root (custom OMP_CODING_AGENT_DIR / WSL // home) the file was discovered under, where an id-prefix lookup scoped to // the default store would miss it. Falls back to the id if no path is known. const resumeTarget = agent === 'omp' && resumeFilePath?.trim() ? resumeFilePath.trim() : sessionId - const sessionArg = quoteShellArg(resumeTarget, platform) + const sessionArg = shell + ? quoteStartupArg(resumeTarget, shell) + : quoteShellArg(resumeTarget, platform) const resumeCommand = buildAgentResumeInvocation(agent, baseCommand, sessionArg) - return buildAiVaultResumeShellCommand({ resumeCommand, cwd, platform, codexHome }) + return buildAiVaultResumeShellCommand({ resumeCommand, cwd, platform, codexHome, shell }) } export function buildAiVaultResumeShellCommand(args: {