Merge remote-tracking branch 'origin/main' into dev
# Conflicts: # nix/package.nix
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getpaseo/server",
|
||||
"version": "0.1.41",
|
||||
"version": "0.1.43-rc.1",
|
||||
"description": "Paseo backend server",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
@@ -64,8 +64,8 @@
|
||||
"@ai-sdk/openai": "2.0.52",
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
||||
"@deepgram/sdk": "^3.4.0",
|
||||
"@getpaseo/highlight": "0.1.41",
|
||||
"@getpaseo/relay": "0.1.41",
|
||||
"@getpaseo/highlight": "0.1.43-rc.1",
|
||||
"@getpaseo/relay": "0.1.43-rc.1",
|
||||
"@isaacs/ttlcache": "^2.1.4",
|
||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||
"@opencode-ai/sdk": "1.2.6",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import {
|
||||
findExecutable,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
resolveProviderCommandPrefix,
|
||||
applyProviderEnv,
|
||||
type ProviderRuntimeSettings,
|
||||
@@ -218,3 +220,75 @@ describe("findExecutable", () => {
|
||||
expect(findExecutableDependencies.existsSync).toHaveBeenCalledWith("/usr/local/bin/codex");
|
||||
});
|
||||
});
|
||||
|
||||
describe("quoteWindowsCommand", () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
function setPlatform(value: string) {
|
||||
Object.defineProperty(process, "platform", { value, writable: true });
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
setPlatform(originalPlatform);
|
||||
});
|
||||
|
||||
test("quotes a Windows path with spaces", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsCommand("C:\\Program Files\\Anthropic\\claude.exe")).toBe(
|
||||
'"C:\\Program Files\\Anthropic\\claude.exe"',
|
||||
);
|
||||
});
|
||||
|
||||
test("does not double-quote an already-quoted path", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsCommand('"C:\\Program Files\\Anthropic\\claude.exe"')).toBe(
|
||||
'"C:\\Program Files\\Anthropic\\claude.exe"',
|
||||
);
|
||||
});
|
||||
|
||||
test("returns the command unchanged when there are no spaces", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsCommand("C:\\nvm4w\\nodejs\\codex")).toBe("C:\\nvm4w\\nodejs\\codex");
|
||||
});
|
||||
|
||||
test("returns the command unchanged on non-Windows platforms", () => {
|
||||
setPlatform("darwin");
|
||||
expect(quoteWindowsCommand("/usr/local/bin/claude code")).toBe("/usr/local/bin/claude code");
|
||||
});
|
||||
});
|
||||
|
||||
describe("quoteWindowsArgument", () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
function setPlatform(value: string) {
|
||||
Object.defineProperty(process, "platform", { value, writable: true });
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
setPlatform(originalPlatform);
|
||||
});
|
||||
|
||||
test("quotes a Windows argument with spaces", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsArgument("C:\\Program Files\\Anthropic\\cli.js")).toBe(
|
||||
'"C:\\Program Files\\Anthropic\\cli.js"',
|
||||
);
|
||||
});
|
||||
|
||||
test("does not double-quote an already-quoted argument", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsArgument('"C:\\Program Files\\Anthropic\\cli.js"')).toBe(
|
||||
'"C:\\Program Files\\Anthropic\\cli.js"',
|
||||
);
|
||||
});
|
||||
|
||||
test("returns the argument unchanged when there are no spaces", () => {
|
||||
setPlatform("win32");
|
||||
expect(quoteWindowsArgument("--version")).toBe("--version");
|
||||
});
|
||||
|
||||
test("returns the argument unchanged on non-Windows platforms", () => {
|
||||
setPlatform("darwin");
|
||||
expect(quoteWindowsArgument("/usr/local/bin/claude code")).toBe("/usr/local/bin/claude code");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -281,6 +281,31 @@ export function findExecutable(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When spawning with `shell: true` on Windows, the command is passed to
|
||||
* `cmd.exe /d /s /c "command args"`. The `/s` strips outer quotes, so a
|
||||
* command path with spaces (e.g. `C:\Program Files\...`) is split at the
|
||||
* space. Wrapping it in quotes produces the correct `"C:\Program Files\..." args`.
|
||||
*/
|
||||
export function quoteWindowsCommand(command: string): string {
|
||||
if (process.platform !== "win32") return command;
|
||||
if (!command.includes(" ")) return command;
|
||||
if (command.startsWith('"') && command.endsWith('"')) return command;
|
||||
return `"${command}"`;
|
||||
}
|
||||
|
||||
/**
|
||||
* `spawn(..., { shell: true })` on Windows also passes argv through `cmd.exe`.
|
||||
* Any argument containing spaces must be quoted or it will be split before the
|
||||
* child process sees it.
|
||||
*/
|
||||
export function quoteWindowsArgument(argument: string): string {
|
||||
if (process.platform !== "win32") return argument;
|
||||
if (!argument.includes(" ")) return argument;
|
||||
if (argument.startsWith('"') && argument.endsWith('"')) return argument;
|
||||
return `"${argument}"`;
|
||||
}
|
||||
|
||||
export function isCommandAvailable(command: string): boolean {
|
||||
return findExecutable(command) !== null;
|
||||
}
|
||||
|
||||
@@ -213,6 +213,25 @@ describe("ClaudeAgentSession integration", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
test.runIf(canRunClaudeIntegration)(
|
||||
"keeps bypassPermissions available after a thinking-option restart",
|
||||
async () => {
|
||||
const handle = await createSession({
|
||||
cwdPrefix: "claude-agent-bypass-restart-",
|
||||
modeId: "bypassPermissions",
|
||||
});
|
||||
|
||||
try {
|
||||
await handle.session.setMode("acceptEdits");
|
||||
await handle.session.setThinkingOption("high");
|
||||
await expect(handle.session.setMode("bypassPermissions")).resolves.toBeUndefined();
|
||||
} finally {
|
||||
await cleanupSession(handle);
|
||||
}
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
test.runIf(canRunClaudeIntegration)(
|
||||
"supportedModels returns the current abstract Claude SDK model shape",
|
||||
async () => {
|
||||
|
||||
@@ -783,6 +783,58 @@ describe("ClaudeAgentSession redesign invariants", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("preserves bypass capability across query restarts triggered by thinking changes", async () => {
|
||||
const capturedOptions: Array<{
|
||||
permissionMode?: string;
|
||||
allowDangerouslySkipPermissions?: boolean;
|
||||
effort?: string;
|
||||
}> = [];
|
||||
|
||||
sdkQueryFactory.mockImplementation(
|
||||
({
|
||||
options,
|
||||
}: {
|
||||
options: {
|
||||
permissionMode?: string;
|
||||
allowDangerouslySkipPermissions?: boolean;
|
||||
effort?: string;
|
||||
};
|
||||
}) => {
|
||||
capturedOptions.push({
|
||||
permissionMode: options.permissionMode,
|
||||
allowDangerouslySkipPermissions: options.allowDangerouslySkipPermissions,
|
||||
effort: options.effort,
|
||||
});
|
||||
|
||||
return createBaseQueryMock(
|
||||
vi.fn(async () => ({ done: true, value: undefined })),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const session = await createSession();
|
||||
|
||||
try {
|
||||
await session.setMode("bypassPermissions");
|
||||
await session.setMode("acceptEdits");
|
||||
await session.setThinkingOption("high");
|
||||
await session.setMode("bypassPermissions");
|
||||
|
||||
expect(capturedOptions).toHaveLength(2);
|
||||
expect(capturedOptions[0]).toMatchObject({
|
||||
permissionMode: "default",
|
||||
allowDangerouslySkipPermissions: true,
|
||||
});
|
||||
expect(capturedOptions[1]).toMatchObject({
|
||||
permissionMode: "acceptEdits",
|
||||
allowDangerouslySkipPermissions: true,
|
||||
effort: "high",
|
||||
});
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("reuses one autonomous run for unbound stream_event bursts with no foreground run", async () => {
|
||||
const session = await createSession();
|
||||
const internal = session as unknown as {
|
||||
|
||||
@@ -70,6 +70,8 @@ import type {
|
||||
import {
|
||||
applyProviderEnv,
|
||||
findExecutable,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
type ProviderRuntimeSettings,
|
||||
} from "../provider-launch-config.js";
|
||||
import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js";
|
||||
@@ -213,7 +215,10 @@ function applyRuntimeSettingsToClaudeOptions(
|
||||
const isDefaultRuntime =
|
||||
resolved.command === "node" || resolved.command === "bun";
|
||||
const command = isDefaultRuntime ? process.execPath : resolved.command;
|
||||
const child = spawn(command, resolved.args, {
|
||||
const child = spawn(
|
||||
quoteWindowsCommand(command),
|
||||
resolved.args.map((argument) => quoteWindowsArgument(argument)),
|
||||
{
|
||||
cwd: spawnOptions.cwd,
|
||||
env: {
|
||||
...applyProviderEnv(spawnOptions.env, runtimeSettings),
|
||||
@@ -222,7 +227,8 @@ function applyRuntimeSettingsToClaudeOptions(
|
||||
shell: process.platform === "win32",
|
||||
signal: spawnOptions.signal,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
},
|
||||
);
|
||||
if (typeof options.stderr === "function") {
|
||||
child.stderr?.on("data", (chunk: Buffer | string) => {
|
||||
options.stderr?.(chunk.toString());
|
||||
@@ -1886,6 +1892,10 @@ class ClaudeAgentSession implements AgentSession {
|
||||
cwd: this.config.cwd,
|
||||
includePartialMessages: true,
|
||||
permissionMode: this.currentMode,
|
||||
// Dynamic mode switching can recreate the underlying Claude query. Keep the
|
||||
// bypass launch capability available so later setPermissionMode("bypassPermissions")
|
||||
// calls do not fail after a model/thinking/rewind-driven restart.
|
||||
allowDangerouslySkipPermissions: true,
|
||||
agents: this.defaults?.agents,
|
||||
canUseTool: this.handlePermissionRequest,
|
||||
...(claudeBinary ? { pathToClaudeCodeExecutable: claudeBinary } : {}),
|
||||
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
import {
|
||||
applyProviderEnv,
|
||||
findExecutable,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
resolveProviderCommandPrefix,
|
||||
type ProviderRuntimeSettings,
|
||||
} from "../provider-launch-config.js";
|
||||
@@ -3419,12 +3421,16 @@ export class CodexAppServerAgentClient implements AgentClient {
|
||||
},
|
||||
"Spawning Codex app server",
|
||||
);
|
||||
return spawn(launchPrefix.command, [...launchPrefix.args, "app-server"], {
|
||||
detached: process.platform !== "win32",
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
env: buildCodexAppServerEnv(this.runtimeSettings, launchEnv),
|
||||
});
|
||||
return spawn(
|
||||
quoteWindowsCommand(launchPrefix.command),
|
||||
[...launchPrefix.args, "app-server"].map((argument) => quoteWindowsArgument(argument)),
|
||||
{
|
||||
detached: process.platform !== "win32",
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
env: buildCodexAppServerEnv(this.runtimeSettings, launchEnv),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async createSession(
|
||||
|
||||
@@ -32,6 +32,8 @@ import type {
|
||||
import {
|
||||
applyProviderEnv,
|
||||
findExecutable,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
resolveProviderCommandPrefix,
|
||||
type ProviderRuntimeSettings,
|
||||
} from "../provider-launch-config.js";
|
||||
@@ -333,8 +335,10 @@ export class OpenCodeServerManager {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server = spawn(
|
||||
launchPrefix.command,
|
||||
[...launchPrefix.args, "serve", "--port", String(this.port)],
|
||||
quoteWindowsCommand(launchPrefix.command),
|
||||
[...launchPrefix.args, "serve", "--port", String(this.port)].map((argument) =>
|
||||
quoteWindowsArgument(argument),
|
||||
),
|
||||
{
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
|
||||
@@ -30,7 +30,12 @@ export {
|
||||
} from "./speech/providers/local/sherpa/sherpa-runtime-env.js";
|
||||
|
||||
// Provider binary resolution
|
||||
export { findExecutable, applyProviderEnv } from "./agent/provider-launch-config.js";
|
||||
export {
|
||||
findExecutable,
|
||||
applyProviderEnv,
|
||||
quoteWindowsArgument,
|
||||
quoteWindowsCommand,
|
||||
} from "./agent/provider-launch-config.js";
|
||||
|
||||
// Agent SDK types for CLI commands
|
||||
export type {
|
||||
|
||||
Reference in New Issue
Block a user