fix: bind pi runtime config at spawn

This commit is contained in:
2026-08-05 05:23:58 +02:00
parent 2703864572
commit 4422568f61
4 changed files with 375 additions and 46 deletions
+134 -1
View File
@@ -3,14 +3,36 @@ import { spawn } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { EventEmitter } from "node:events";
import { chmodSync, writeFileSync } from "node:fs";
import {
chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { PiProcessManager } from "../src/pi/pi-process-manager.js";
import { loadConfig } from "../src/config.js";
import {
PI_MANAGED_CONFIG_ERROR_MESSAGE,
validateDeclarativePiConfig,
} from "../src/pi/managed-config.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FAKE = path.resolve(__dirname, "../../harness/tests/fake_pi/fake_pi_rpc.mjs");
const SCRIPT = path.resolve(__dirname, "../../harness/tests/fake_pi/scripts/f1_disambiguation.json");
const SAFE_AUTH = '{"deepseek":{"type":"api_key","key":"safe-token"}}\n';
const SAFE_MODELS = [
"{",
' "providers": {',
' "local-qwen": {"baseUrl":"http://model.invalid/v1","models":[{"id":"qwen"}]}',
" }",
"}",
"",
].join("\n");
function writeSafeAgentConfig(agentDir: string): void {
writeFileSync(path.join(agentDir, "auth.json"), SAFE_AUTH, { mode: 0o600 });
writeFileSync(path.join(agentDir, "models.json"), SAFE_MODELS, { mode: 0o600 });
}
test("spawnFor avvia un runtime e il bridge emette il widget F1", async () => {
const cfg = loadConfig({ THT_HARNESS_DIR: "../harness" });
const mgr = new PiProcessManager(cfg, { spawnFn: () => spawn("node", [FAKE, SCRIPT]) as any });
@@ -146,6 +168,117 @@ function recordingChild() {
return ch;
}
test.each([
["new", "auth.json", '{"deepseek":{"key":"!runtime-auth-command runtime-secret /private/runtime-auth"}}\n'],
["new", "models.json", '{"providers":{"local-qwen":{"headers":["!runtime-model-command runtime-secret /private/runtime-model"]}}}\n'],
["resume", "auth.json", '{"deepseek":{"key":"!resume-auth-command runtime-secret /private/resume-auth"}}\n'],
["resume", "models.json", '{"providers":{"local-qwen":{"models":[{"apiKey":"!resume-model-command runtime-secret /private/resume-model"}]}}}\n'],
] as const)(
"%s runtime rejects post-admission executable %s before auth resolution or child spawn",
async (mode, changedFile, unsafeRaw) => {
const root = mkdtempSync(path.join(tmpdir(), "tht-runtime-managed-config-"));
const agentDir = path.join(root, "agent");
mkdirSync(agentDir, { mode: 0o700 });
writeSafeAgentConfig(agentDir);
vi.stubEnv("PI_CODING_AGENT_DIR", agentDir);
let authResolutions = 0;
let spawns = 0;
const mgr = new PiProcessManager(loadConfig({}), {
authProviders: () => { authResolutions += 1; return new Set(); },
spawnFn: () => { spawns += 1; throw new Error("SPAWN_BOUNDARY_REACHED"); },
});
try {
// Admission/model validation succeeded while the mounted files were still safe, and the
// session was then persisted. The operator-controlled mount changes before runtime start.
validateDeclarativePiConfig(readFileSync(path.join(agentDir, "auth.json"), "utf8"));
validateDeclarativePiConfig(readFileSync(path.join(agentDir, "models.json"), "utf8"));
writeFileSync(path.join(root, "session-created"), `${mode}\n`);
writeFileSync(path.join(agentDir, changedFile), unsafeRaw, { mode: 0o600 });
let failure: unknown;
try {
if (mode === "new") {
mgr.createFor("post-admission-new", { provider: "local-qwen" });
} else {
await mgr.spawnFor("post-admission-resume", {
provider: "local-qwen", mode: "resume",
});
}
} catch (error) {
failure = error;
}
const message = failure instanceof Error ? failure.message : String(failure);
expect({ message, authResolutions, spawns, runtimes: mgr.count() }).toEqual({
message: PI_MANAGED_CONFIG_ERROR_MESSAGE,
authResolutions: 0,
spawns: 0,
runtimes: 0,
});
expect(message).not.toMatch(/runtime-secret|\/private\/|runtime-(?:auth|model)-command|resume-(?:auth|model)-command/);
} finally {
mgr.teardown("post-admission-new");
mgr.teardown("post-admission-resume");
vi.unstubAllEnvs();
rmSync(root, { recursive: true, force: true });
}
},
);
test("runtime Pi consumes exact validated auth/models snapshots and keeps persistent agent resources", async () => {
const root = mkdtempSync(path.join(tmpdir(), "tht-runtime-agent-snapshot-"));
const agentDir = path.join(root, "agent");
const sessionsDir = path.join(agentDir, "sessions");
const extensionDir = path.join(agentDir, "extensions");
mkdirSync(sessionsDir, { recursive: true, mode: 0o700 });
mkdirSync(extensionDir, { recursive: true, mode: 0o700 });
writeSafeAgentConfig(agentDir);
const settings = '{"quietStartup":true}\n';
writeFileSync(path.join(agentDir, "settings.json"), settings, { mode: 0o600 });
writeFileSync(path.join(extensionDir, "runtime-extension.js"), "export default {};\n");
vi.stubEnv("PI_CODING_AGENT_DIR", agentDir);
vi.stubEnv("PI_CODING_AGENT_SESSION_DIR", "");
const child = recordingChild();
let spawnEnv: NodeJS.ProcessEnv | undefined;
const mgr = new PiProcessManager(loadConfig({}), {
spawnFn: (_command, _args, options) => {
spawnEnv = options.env;
// This mutation happens after validation but before the child can open either source file.
writeFileSync(path.join(agentDir, "auth.json"), '{"deepseek":{"key":"!late-auth-command"}}\n');
writeFileSync(path.join(agentDir, "models.json"), '{"providers":{"late":{"apiKey":"!late-model-command"}}}\n');
return child as any;
},
});
let snapshotDir: string | undefined;
let childExited = false;
try {
await mgr.spawnFor("snapshot-session", { provider: "local-qwen" });
snapshotDir = spawnEnv?.PI_CODING_AGENT_DIR;
expect(snapshotDir).toBeTruthy();
expect(snapshotDir).not.toBe(agentDir);
expect(readFileSync(path.join(snapshotDir!, "auth.json"), "utf8")).toBe(SAFE_AUTH);
expect(readFileSync(path.join(snapshotDir!, "models.json"), "utf8")).toBe(SAFE_MODELS);
expect(readFileSync(path.join(snapshotDir!, "settings.json"), "utf8")).toBe(settings);
expect(readFileSync(path.join(snapshotDir!, "extensions", "runtime-extension.js"), "utf8"))
.toBe("export default {};\n");
expect(spawnEnv?.PI_CODING_AGENT_SESSION_DIR).toBe(sessionsDir);
mgr.teardown("snapshot-session");
child.emit("exit", 0);
childExited = true;
expect(existsSync(snapshotDir!)).toBe(false);
} finally {
mgr.teardown("snapshot-session");
if (!childExited) child.emit("exit", 0);
vi.unstubAllEnvs();
if (snapshotDir && snapshotDir !== agentDir) rmSync(snapshotDir, { recursive: true, force: true });
rmSync(root, { recursive: true, force: true });
}
});
test("createFor kills a spawned child when post-spawn initialization throws", () => {
const child = recordingChild();
child.kill = vi.fn();