feat: add Pi provider managed resources

This commit is contained in:
2026-08-22 21:48:00 +08:00
parent 81d8ad1b6b
commit 161f3f471b
31 changed files with 1581 additions and 40 deletions

View File

@@ -18,6 +18,27 @@ const DEFAULT_COMMAND_TIMEOUT_MS = 10_000;
const DEFAULT_SHUTDOWN_GRACE_MS = 3_000;
const DEFAULT_DIAGNOSTIC_BYTES = 16_000;
const ANSI_COLOR_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g');
const PI_INHERITED_ENV_KEYS = [
'APPDATA',
'COMSPEC',
'HOME',
'LANG',
'LC_ALL',
'LD_LIBRARY_PATH',
'LOCALAPPDATA',
'NODE_EXTRA_CA_CERTS',
'PATH',
'PATHEXT',
'SSL_CERT_DIR',
'SSL_CERT_FILE',
'SYSTEMROOT',
'TEMP',
'TMP',
'TMPDIR',
'TZ',
'USERPROFILE',
'WINDIR',
] as const;
export type PiWorkerStopResult = {
mode: 'not-started' | 'stdin-close' | 'forced-tree-kill';
@@ -67,13 +88,50 @@ export function sanitizePiDiagnostic(
.replace(ANSI_COLOR_PATTERN, '')
.replace(/(authorization\s*[:=]\s*(?:bearer\s+)?)[^\s,;]+/gi, '$1[REDACTED]')
.replace(/((?:x-api-key|api[_-]?key|token|secret)\s*[:=]\s*)[^\s,;]+/gi, '$1[REDACTED]');
for (const value of sensitiveValues) {
if (value.length < 4) continue;
const uniqueSensitiveValues = [...new Set(sensitiveValues.filter(Boolean))]
.sort((left, right) => right.length - left.length);
for (const value of uniqueSensitiveValues) {
sanitized = sanitized.split(value).join('[REDACTED]');
}
return sanitized;
}
export function buildPiWorkerEnvironment(
configDir: string,
overlay: NodeJS.ProcessEnv = {},
inherited: NodeJS.ProcessEnv = process.env,
): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = {};
for (const key of PI_INHERITED_ENV_KEYS) {
const exact = inherited[key];
if (exact !== undefined) {
env[key] = exact;
continue;
}
const matchingKey = Object.keys(inherited).find((candidate) => candidate.toUpperCase() === key);
if (matchingKey && inherited[matchingKey] !== undefined) env[matchingKey] = inherited[matchingKey];
}
return {
...env,
...overlay,
ELECTRON_RUN_AS_NODE: '1',
PI_CODING_AGENT_DIR: configDir,
PI_OFFLINE: '1',
PI_TELEMETRY: '0',
};
}
function assertSensitiveValuesAbsentFromArgs(
args: readonly string[],
sensitiveValues: readonly string[] = [],
): void {
for (const value of sensitiveValues) {
if (value && args.some((argument) => argument.includes(value))) {
throw new Error('Pi worker arguments contain a sensitive value');
}
}
}
function boundedUtf8Tail(source: string, maxBytes: number): string {
const bytes = Buffer.from(source, 'utf8');
if (bytes.length <= maxBytes) return source;
@@ -158,19 +216,14 @@ export class PiWorkerProcess {
async start(): Promise<this> {
if (this.child) throw new Error('Pi worker process already started');
const generation = this.generationValue;
const args = buildPiRpcArgs(this.options.sessionDir, this.options.additionalArgs);
assertSensitiveValuesAbsentFromArgs(args, this.options.sensitiveValues);
const child = spawn(
this.options.executablePath,
[this.options.cliPath, ...buildPiRpcArgs(this.options.sessionDir, this.options.additionalArgs)],
[this.options.cliPath, ...args],
{
cwd: this.options.cwd,
env: {
...process.env,
...this.options.env,
ELECTRON_RUN_AS_NODE: '1',
PI_CODING_AGENT_DIR: this.options.configDir,
PI_OFFLINE: '1',
PI_TELEMETRY: '0',
},
env: buildPiWorkerEnvironment(this.options.configDir, this.options.env),
stdio: ['pipe', 'pipe', 'pipe'],
windowsHide: true,
detached: platform() !== 'win32',