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
+51 -37
View File
@@ -7,6 +7,7 @@ import { buildPiChildEnv, canonicalPiProvider } from "./provider-credentials.js"
import { loadPiAuthProviders } from "./auth-providers.js";
import { secretValue } from "../config/secret-bundle.js";
import { clearPrincipalEnvironment, principalEnvironment, type PrincipalContext } from "../auth/principal.js";
import { createPiRuntimeAgentSnapshot } from "./managed-config.js";
export interface SessionRuntime {
rpc: RpcClient;
@@ -37,13 +38,14 @@ export class PiProcessManager {
private spawnFn: (
sessionId: string, author: string, provider: string | undefined, principal?: PrincipalContext,
) => ChildProcessWithoutNullStreams;
private loadAuthProviders: () => ReadonlySet<string>;
private loadAuthProviders: (agentDir: string) => ReadonlySet<string>;
constructor(
private cfg: AppConfig,
opts?: { spawnFn?: SpawnFn; authProviders?: () => ReadonlySet<string> },
opts?: { spawnFn?: SpawnFn; authProviders?: (agentDir: string) => ReadonlySet<string> },
) {
this.loadAuthProviders = opts?.authProviders ?? (() => loadPiAuthProviders());
this.loadAuthProviders = opts?.authProviders
?? ((agentDir) => loadPiAuthProviders({ agentDir }));
if (opts?.spawnFn) {
this.spawnFn = (sessionId, author, provider, principal) =>
this.spawnPi(opts.spawnFn!, sessionId, author, provider, principal);
@@ -56,45 +58,57 @@ export class PiProcessManager {
private spawnPi(
spawnFn: SpawnFn, sessionId: string, author: string, provider: string | undefined, principal?: PrincipalContext,
): ChildProcessWithoutNullStreams {
const env = buildPiChildEnv({
provider,
authProviders: this.loadAuthProviders(),
credentialValue: secretValue(this.cfg, "THT_MODEL_API_KEY"),
credentialFile: this.cfg.modelApiKeyFile,
additions: { THT_SESSION: sessionId, THT_AUTHOR: author },
});
clearPrincipalEnvironment(env);
if (principal) Object.assign(env, principalEnvironment(principal));
// The Thoth gate executes the deterministic `tht` CLI as a Pi tool. Give only
// this managed session process the adapter values already loaded by the core
// entrypoint; the generic provider helper continues to scrub them by default.
for (const name of [
"THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY",
] as const) {
const value = secretValue(this.cfg, name) ?? process.env[name];
if (value !== undefined) env[name] = value;
}
const ca = secretValue(this.cfg, "THT_SSL_CA")
?? secretValue(this.cfg, "THT_CA")
?? process.env.THT_SSL_CA
?? process.env.THT_CA;
if (ca !== undefined) {
env.THT_CA = ca;
env.THT_SSL_CA = ca;
}
delete env.THT_DATA_ROOT;
if (this.cfg.dataRoot !== undefined) env.THT_DATA_ROOT = this.cfg.dataRoot;
// pi 0.73 removed `--approve`: rpc mode is headless and its argv is intentionally minimal.
const child = spawnFn(this.cfg.piBin, ["--mode", "rpc"], {
cwd: this.cfg.harnessDir,
env,
});
// This is the final shared boundary for createFor(), spawnFor(), and resume(). Validate
// before auth-provider inspection, then make Pi consume the exact copied bytes rather than
// reopening mutable mounted auth/models files after this check.
const agent = createPiRuntimeAgentSnapshot();
let child: ChildProcessWithoutNullStreams | undefined;
try {
const env = buildPiChildEnv({
provider,
authProviders: this.loadAuthProviders(agent.agentDir),
credentialValue: secretValue(this.cfg, "THT_MODEL_API_KEY"),
credentialFile: this.cfg.modelApiKeyFile,
additions: { THT_SESSION: sessionId, THT_AUTHOR: author },
});
env.PI_CODING_AGENT_DIR = agent.agentDir;
env.PI_CODING_AGENT_SESSION_DIR = agent.sessionDir;
clearPrincipalEnvironment(env);
if (principal) Object.assign(env, principalEnvironment(principal));
// The Thoth gate executes the deterministic `tht` CLI as a Pi tool. Give only
// this managed session process the adapter values already loaded by the core
// entrypoint; the generic provider helper continues to scrub them by default.
for (const name of [
"THT_DWH_API_KEY", "THT_VEC_API_KEY", "THT_VEC_WRITE_API_KEY",
] as const) {
const value = secretValue(this.cfg, name) ?? process.env[name];
if (value !== undefined) env[name] = value;
}
const ca = secretValue(this.cfg, "THT_SSL_CA")
?? secretValue(this.cfg, "THT_CA")
?? process.env.THT_SSL_CA
?? process.env.THT_CA;
if (ca !== undefined) {
env.THT_CA = ca;
env.THT_SSL_CA = ca;
}
delete env.THT_DATA_ROOT;
if (this.cfg.dataRoot !== undefined) env.THT_DATA_ROOT = this.cfg.dataRoot;
// pi 0.73 removed `--approve`: rpc mode is headless and its argv is intentionally minimal.
child = spawnFn(this.cfg.piBin, ["--mode", "rpc"], {
cwd: this.cfg.harnessDir,
env,
});
child.once("exit", agent.cleanup);
child.once("close", agent.cleanup);
// Log stderr for debugging (was silently drained)
child.stderr.on("data", (d: Buffer) => console.error(`[pi:${sessionId}] stderr:`, d.toString().trim()));
return child;
} catch (error) {
try { child.kill(); } catch { /* preserve the initialization error */ }
if (child) {
try { child.kill(); } catch { /* preserve the initialization error */ }
}
agent.cleanup();
throw error;
}
}