diff --git a/src/main/wsl/wsl-orcad-chat-provider-fixture.ts b/src/main/wsl/wsl-orcad-chat-provider-fixture.ts index 24f0f04f47f..faf40f206a0 100644 --- a/src/main/wsl/wsl-orcad-chat-provider-fixture.ts +++ b/src/main/wsl/wsl-orcad-chat-provider-fixture.ts @@ -2,7 +2,7 @@ import { z } from 'zod' import { folderWorkspaceKey } from '../../shared/workspace-scope' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' -import { shellEscape } from '../ssh/ssh-connection-utils' +import { quotePosixShell } from '../../shared/shell-argument-quoting' export type WslChatHost = { rpc(method: string, params?: unknown): Promise @@ -16,7 +16,7 @@ export async function writeChatFixtureFile( content: string ): Promise { await host.guest( - `printf %s ${shellEscape(Buffer.from(content).toString('base64'))} | base64 -d > ${shellEscape(path)}` + `printf %s ${quotePosixShell(Buffer.from(content).toString('base64'))} | base64 -d > ${quotePosixShell(path)}` ) } @@ -24,7 +24,7 @@ export async function writeChatFixtureFile( export async function installWslChatFixture(lane: WslChatHost) { const home = (await lane.guest('printf %s "$HOME"')).trim() const root = `${home}/wsl chat fixture` - await lane.guest(`mkdir -p ${shellEscape(root)}`) + await lane.guest(`mkdir -p ${quotePosixShell(root)}`) const runtime = (await lane.guest(`readlink -f /proc/${lane.runtimePid}/exe`)).trim() const script = `${root}/scripted.mjs` const cli = `${root}/claude` @@ -36,13 +36,13 @@ export async function installWslChatFixture(lane: WslChatHost) { await writeChatFixtureFile( lane, cli, - `#!/bin/sh\nexec ${shellEscape(runtime)} ${shellEscape(script)} "$@"\n` + `#!/bin/sh\nexec ${quotePosixShell(runtime)} ${quotePosixShell(script)} "$@"\n` ) - await lane.guest(`chmod 700 ${shellEscape(cli)}`) + await lane.guest(`chmod 700 ${quotePosixShell(cli)}`) await lane.rpc('settings.mutateAgentLaunch', { type: 'command', agent: 'claude', - value: shellEscape(cli) + value: quotePosixShell(cli) }) await lane.rpc('settings.update', { nativeChatPermissionMode: 'ask', @@ -58,7 +58,7 @@ export async function installWslChatFixture(lane: WslChatHost) { name: 'WSL chat lane' }) const folderPath = `${root}/folder project` - await lane.guest(`mkdir -p ${shellEscape(folderPath)}`) + await lane.guest(`mkdir -p ${quotePosixShell(folderPath)}`) const folder = await lane.rpc<{ folderWorkspace: { id: string } }>('folderWorkspace.create', { projectGroupId: group.group.id, folderPath, diff --git a/src/main/wsl/wsl-orcad-structured-chat-contract.ts b/src/main/wsl/wsl-orcad-structured-chat-contract.ts index 89d4066406f..4f38c7a36e4 100644 --- a/src/main/wsl/wsl-orcad-structured-chat-contract.ts +++ b/src/main/wsl/wsl-orcad-structured-chat-contract.ts @@ -15,7 +15,7 @@ import { } from '../../shared/remote-runtime-client' import { claudeSessionIdForOrcaSession } from '../claude/claude-structured-session-id' import { packagedClaudeChatScenario } from '../orcad/orcad-structured-chat-scenario' -import { shellEscape } from '../ssh/ssh-connection-utils' +import { quotePosixShell } from '../../shared/shell-argument-quoting' import { wslClaudeChatScenario } from './wsl-orcad-chat-scenario' import { installWslChatFixture, @@ -55,7 +55,7 @@ async function exerciseChat(lane: ChatLane, root: string, worktree: string, cwd: const ackPath = `${root}/partial-observed` const scenarioPath = `${root}/scenario.json` const reportPath = `${root}/report.json` - await lane.guest(`rm -f ${shellEscape(ackPath)}`) + await lane.guest(`rm -f ${quotePosixShell(ackPath)}`) await writeChatFixtureFile( lane, scenarioPath, @@ -125,7 +125,7 @@ async function exerciseChat(lane: ChatLane, root: string, worktree: string, cwd: upload ) expect(attachment.path).toMatch(/^\//u) - expect(await lane.guest(`base64 -w0 ${shellEscape(attachment.path)}`)).toBe(png) + expect(await lane.guest(`base64 -w0 ${quotePosixShell(attachment.path)}`)).toBe(png) const body = { kind: 'message', role: 'user', @@ -150,7 +150,7 @@ async function exerciseChat(lane: ChatLane, root: string, worktree: string, cwd: dropping = true // Close the actual WSL bridge, not just the chat subscription; the guest owns the turn. await lane.reconnect(async () => { - await lane.guest(`touch ${shellEscape(ackPath)}`) + await lane.guest(`touch ${quotePosixShell(ackPath)}`) }) subscription.close() rows.clear() @@ -186,7 +186,7 @@ async function exerciseChat(lane: ChatLane, root: string, worktree: string, cwd: }) ).toMatchObject({ ok: true, value: { cancelled: true } }) const report = WslChatProviderReport.parse( - JSON.parse(await lane.guest(`cat ${shellEscape(reportPath)}`)) + JSON.parse(await lane.guest(`cat ${quotePosixShell(reportPath)}`)) ) expect(report.cwd).toBe(cwd) expect(report.execPath).toMatch(/^\//u) @@ -260,7 +260,7 @@ async function exerciseChat(lane: ChatLane, root: string, worktree: string, cwd: { timeout: 30_000 } ) const resumed = WslChatProviderReport.parse( - JSON.parse(await lane.guest(`cat ${shellEscape(reportPath)}`)) + JSON.parse(await lane.guest(`cat ${quotePosixShell(reportPath)}`)) ) expect(resumed.cwd).toBe(cwd) expect(resumed.argv.join(' ')).toMatch(new RegExp(`--resume(?:=| )${providerId}`))