windows: unify non-core spawn handling across acp qmd and docker (openclaw#31750) thanks @Takhoffman

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check (fails on pre-existing unrelated src/slack/monitor/events/messages.ts typing errors)
- pnpm vitest run src/acp/client.test.ts src/memory/qmd-manager.test.ts src/agents/sandbox/docker.execDockerRaw.enoent.test.ts src/agents/sandbox/docker.windows.test.ts extensions/acpx/src/runtime-internals/process.test.ts

Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Tak Hoffman
2026-03-02 08:05:39 -06:00
committed by GitHub
parent 32c7242974
commit cd653c55d7
7 changed files with 305 additions and 8 deletions

View File

@@ -899,6 +899,8 @@ describe("QmdMemoryManager", () => {
expect(qmdCalls.length).toBeGreaterThan(0);
for (const call of qmdCalls) {
expect(call[0]).toBe("qmd.cmd");
const options = call[2] as { shell?: boolean } | undefined;
expect(options?.shell).toBe(true);
}
await manager.close();
@@ -1408,6 +1410,8 @@ describe("QmdMemoryManager", () => {
);
expect(mcporterCall).toBeDefined();
expect(mcporterCall?.[0]).toBe("mcporter.cmd");
const options = mcporterCall?.[2] as { shell?: boolean } | undefined;
expect(options?.shell).toBe(true);
await manager.close();
} finally {

View File

@@ -7,6 +7,10 @@ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import {
materializeWindowsSpawnProgram,
resolveWindowsSpawnProgram,
} from "../plugin-sdk/windows-spawn.js";
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
import { deriveQmdScopeChannel, deriveQmdScopeChatType, isQmdScopeAllowed } from "./qmd-scope.js";
import {
@@ -65,6 +69,23 @@ function resolveWindowsCommandShim(command: string): string {
return command;
}
function resolveSpawnInvocation(params: {
command: string;
args: string[];
env: NodeJS.ProcessEnv;
packageName: string;
}) {
const program = resolveWindowsSpawnProgram({
command: resolveWindowsCommandShim(params.command),
platform: process.platform,
env: params.env,
execPath: process.execPath,
packageName: params.packageName,
allowShellFallback: true,
});
return materializeWindowsSpawnProgram(program, params.args);
}
function hasHanScript(value: string): boolean {
return HAN_SCRIPT_RE.test(value);
}
@@ -1066,9 +1087,17 @@ export class QmdMemoryManager implements MemorySearchManager {
opts?: { timeoutMs?: number; discardOutput?: boolean },
): Promise<{ stdout: string; stderr: string }> {
return await new Promise((resolve, reject) => {
const child = spawn(resolveWindowsCommandShim(this.qmd.command), args, {
const spawnInvocation = resolveSpawnInvocation({
command: this.qmd.command,
args,
env: this.env,
packageName: "qmd",
});
const child = spawn(spawnInvocation.command, spawnInvocation.argv, {
env: this.env,
cwd: this.workspaceDir,
shell: spawnInvocation.shell,
windowsHide: spawnInvocation.windowsHide,
});
let stdout = "";
let stderr = "";
@@ -1164,10 +1193,18 @@ export class QmdMemoryManager implements MemorySearchManager {
opts?: { timeoutMs?: number },
): Promise<{ stdout: string; stderr: string }> {
return await new Promise((resolve, reject) => {
const child = spawn(resolveWindowsCommandShim("mcporter"), args, {
const spawnInvocation = resolveSpawnInvocation({
command: "mcporter",
args,
env: this.env,
packageName: "mcporter",
});
const child = spawn(spawnInvocation.command, spawnInvocation.argv, {
// Keep mcporter and direct qmd commands on the same agent-scoped XDG state.
env: this.env,
cwd: this.workspaceDir,
shell: spawnInvocation.shell,
windowsHide: spawnInvocation.windowsHide,
});
let stdout = "";
let stderr = "";