Merge remote-tracking branch 'origin/farm/48d8eb10/windows-mcp-command'
This commit is contained in:
@@ -46,6 +46,7 @@
|
||||
- Fixed pasting into the ask tool's "Other (type your own)" text box (and hook input/editor dialogs) on terminals with OSC 5522 enhanced paste (kitty protocol): the enhanced-paste focus routing only targets components exposing a `pasteText` hook, and the dialog wrappers had none, so the payload was stuffed into the main prompt editor hidden behind the dialog. `HookEditorComponent` and `HookInputComponent` now forward `pasteText` to their inner editor/input (pasting also resets the input dialog's timeout countdown like any keystroke).
|
||||
- Fixed auto-retry giving up after one attempt ("Provider requested Xms wait, exceeds retry.maxDelayMs") on a usage-limit 429 when every sibling account was only momentarily blocked: the retry delay now waits for the earliest sibling unblock when that comes sooner than the provider's multi-hour retry-after, so the next attempt picks up the recovered account instead of failing fast.
|
||||
- Fixed Hindsight `per-project-tagged` mental-model seeding so each project gets its own conventions/decisions models and session context only injects active-project or untagged models ([#2218](https://github.com/can1357/oh-my-pi/issues/2218)).
|
||||
- Fixed Windows stdio MCP `.cmd` commands regressing from direct argv launches to a `cmd.exe /c` wrapper in v15.10.10, which made Codegraph MCP exit immediately with `Transport closed` ([#2220](https://github.com/can1357/oh-my-pi/issues/2220)).
|
||||
|
||||
- Fixed the bundled `explore` agent's `thinking-level: med` frontmatter — not a valid effort (`minimal`/`low`/`medium`/`high`/`xhigh`), so it silently parsed to undefined and the agent ran without its intended thinking level
|
||||
- Discovery context-file reads (`~/.claude`, `~/.cursor`, project trees, `@`-imports) now stat-gate to regular files before reading: a FIFO/socket/char device dropped where a context file is expected previously blocked startup forever on a read that can never see EOF.
|
||||
|
||||
@@ -35,7 +35,6 @@ export interface ResolveStdioSpawnOptions {
|
||||
}
|
||||
|
||||
const DEFAULT_WINDOWS_PATHEXT = [".COM", ".EXE", ".BAT", ".CMD"];
|
||||
const WINDOWS_BATCH_EXTENSIONS = new Set([".bat", ".cmd"]);
|
||||
|
||||
function getCaseInsensitiveEnv(env: Record<string, string | undefined>, name: string): string | undefined {
|
||||
const direct = env[name];
|
||||
@@ -107,32 +106,6 @@ async function resolveWindowsCommandPath(
|
||||
return null;
|
||||
}
|
||||
|
||||
function quoteCmdArg(value: string): string {
|
||||
if (value.length === 0) return '""';
|
||||
let result = '"';
|
||||
for (const char of value) {
|
||||
if (char === '"') {
|
||||
result += '^"';
|
||||
} else if (char === "^") {
|
||||
result += "^^";
|
||||
} else if (char === "%") {
|
||||
result += "^%";
|
||||
} else {
|
||||
result += char;
|
||||
}
|
||||
}
|
||||
return `${result}"`;
|
||||
}
|
||||
|
||||
function isWindowsBatchCommand(command: string): boolean {
|
||||
return WINDOWS_BATCH_EXTENSIONS.has(path.extname(command).toLowerCase());
|
||||
}
|
||||
|
||||
function resolveComSpec(env: Record<string, string | undefined>): string {
|
||||
const comspec = getCaseInsensitiveEnv(env, "COMSPEC");
|
||||
return comspec && comspec.length > 0 ? comspec : "cmd.exe";
|
||||
}
|
||||
|
||||
/** Resolve the subprocess argv used to launch an MCP stdio server. */
|
||||
export async function resolveStdioSpawnCommand(
|
||||
config: MCPStdioServerConfig,
|
||||
@@ -143,11 +116,7 @@ export async function resolveStdioSpawnCommand(
|
||||
|
||||
const resolvedCommand =
|
||||
(await resolveWindowsCommandPath(config.command, options.cwd, options.env)) ?? config.command;
|
||||
if (!isWindowsBatchCommand(resolvedCommand)) return { cmd: [resolvedCommand, ...args] };
|
||||
|
||||
return {
|
||||
cmd: [resolveComSpec(options.env), "/d", "/s", "/c", [resolvedCommand, ...args].map(quoteCmdArg).join(" ")],
|
||||
};
|
||||
return { cmd: [resolvedCommand, ...args] };
|
||||
}
|
||||
|
||||
/** Minimal write surface of `Subprocess.stdin` we need for framed sends. */
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as path from "node:path";
|
||||
import { resolveStdioSpawnCommand, StdioTransport, writeFrame } from "@oh-my-pi/pi-coding-agent/mcp/transports/stdio";
|
||||
|
||||
describe("resolveStdioSpawnCommand", () => {
|
||||
it("resolves bare Windows commands through PATHEXT and wraps .cmd shims with cmd.exe", async () => {
|
||||
it("resolves bare Windows commands through PATHEXT and preserves direct .cmd argv", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-stdio-"));
|
||||
try {
|
||||
const shim = path.join(tempDir, "codegraph.cmd");
|
||||
@@ -17,7 +17,6 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
{
|
||||
cwd: tempDir,
|
||||
env: {
|
||||
COMSPEC: "C:\\Windows\\System32\\cmd.exe",
|
||||
PATH: tempDir,
|
||||
PATHEXT: ".cmd",
|
||||
},
|
||||
@@ -25,13 +24,13 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.cmd).toEqual(["C:\\Windows\\System32\\cmd.exe", "/d", "/s", "/c", `"${shim}" "serve" "--mcp"`]);
|
||||
expect(result.cmd).toEqual([shim, "serve", "--mcp"]);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("escapes percent-delimited args before routing .cmd shims through cmd.exe", async () => {
|
||||
it("preserves percent-delimited args when resolving .cmd shims", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-percent-"));
|
||||
try {
|
||||
const shim = path.join(tempDir, "codegraph.cmd");
|
||||
@@ -42,7 +41,6 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
{
|
||||
cwd: tempDir,
|
||||
env: {
|
||||
COMSPEC: "C:\\Windows\\System32\\cmd.exe",
|
||||
PATH: tempDir,
|
||||
PATHEXT: ".cmd",
|
||||
},
|
||||
@@ -50,19 +48,13 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.cmd).toEqual([
|
||||
"C:\\Windows\\System32\\cmd.exe",
|
||||
"/d",
|
||||
"/s",
|
||||
"/c",
|
||||
`"${shim}" "serve" "--header" "Authorization=^%TOKEN^%"`,
|
||||
]);
|
||||
expect(result.cmd).toEqual([shim, "serve", "--header", "Authorization=%TOKEN%"]);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("escapes quoted JSON args before routing .cmd shims through cmd.exe", async () => {
|
||||
it("preserves quoted JSON args when resolving .cmd shims", async () => {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-quotes-"));
|
||||
try {
|
||||
const shim = path.join(tempDir, "codegraph.cmd");
|
||||
@@ -73,7 +65,6 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
{
|
||||
cwd: tempDir,
|
||||
env: {
|
||||
COMSPEC: "C:\\Windows\\System32\\cmd.exe",
|
||||
PATH: tempDir,
|
||||
PATHEXT: ".cmd",
|
||||
},
|
||||
@@ -81,13 +72,7 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.cmd).toEqual([
|
||||
"C:\\Windows\\System32\\cmd.exe",
|
||||
"/d",
|
||||
"/s",
|
||||
"/c",
|
||||
`"${shim}" "--config" "{^"a^":^"b&c|d^"}"`,
|
||||
]);
|
||||
expect(result.cmd).toEqual([shim, "--config", '{"a":"b&c|d"}']);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
@@ -113,19 +98,34 @@ describe("resolveStdioSpawnCommand", () => {
|
||||
{
|
||||
cwd: tempDir,
|
||||
env: {
|
||||
COMSPEC: "C:\\Windows\\System32\\cmd.exe",
|
||||
PATHEXT: ".cmd",
|
||||
},
|
||||
platform: "win32",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.cmd).toEqual(["C:\\Windows\\System32\\cmd.exe", "/d", "/s", "/c", `"${shim}" "serve" "--mcp"`]);
|
||||
expect(result.cmd).toEqual([shim, "serve", "--mcp"]);
|
||||
} finally {
|
||||
await fs.rm(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves explicit Windows .cmd commands as direct argv launches", async () => {
|
||||
const result = await resolveStdioSpawnCommand(
|
||||
{ type: "stdio", command: "codegraph.cmd", args: ["serve", "--mcp"] },
|
||||
{
|
||||
cwd: "C:\\project",
|
||||
env: {
|
||||
PATH: "C:\\Users\\me\\AppData\\Roaming\\npm",
|
||||
PATHEXT: ".COM;.EXE;.BAT;.CMD",
|
||||
},
|
||||
platform: "win32",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.cmd).toEqual(["codegraph.cmd", "serve", "--mcp"]);
|
||||
});
|
||||
|
||||
it("leaves non-Windows commands untouched", async () => {
|
||||
const result = await resolveStdioSpawnCommand(
|
||||
{ type: "stdio", command: "codegraph", args: ["serve", "--mcp"] },
|
||||
|
||||
Reference in New Issue
Block a user