Skip to content

Commit 1639ca8

Browse files
committed
feat: US-070 - Make Pi PTY helper-tool bootstrap compatible with sandbox command routing
1 parent cf1561b commit 1639ca8

5 files changed

Lines changed: 458 additions & 255 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/**
2+
* PTY bootstrap regression for Pi's helper-tool setup.
3+
*
4+
* Uses the real Pi CLI through kernel.openShell() without mock provider
5+
* redirects. The sandbox only exposes `tar`; Pi must rely on preseeded
6+
* upstream `fd` / `rg` binaries rather than the sandbox command surface.
7+
*/
8+
9+
import { existsSync } from 'node:fs';
10+
import { chmod, copyFile, mkdtemp, rm } from 'node:fs/promises';
11+
import { tmpdir } from 'node:os';
12+
import path from 'node:path';
13+
import { afterEach, describe, expect, it } from 'vitest';
14+
import {
15+
allowAllChildProcess,
16+
allowAllEnv,
17+
allowAllFs,
18+
allowAllNetwork,
19+
createKernel,
20+
} from '../../../core/src/index.ts';
21+
import type { Kernel, ShellHandle } from '../../../core/src/index.ts';
22+
import {
23+
createNodeHostNetworkAdapter,
24+
createNodeRuntime,
25+
} from '../../../nodejs/src/index.ts';
26+
import { createWasmVmRuntime } from '../../../wasmvm/src/index.ts';
27+
import {
28+
buildPiInteractiveCode,
29+
createHybridVfs,
30+
SECURE_EXEC_ROOT,
31+
seedPiManagedTools,
32+
skipUnlessPiInstalled,
33+
WASM_COMMANDS_DIR,
34+
} from './pi-pty-helpers.ts';
35+
36+
function getSkipReason(): string | false {
37+
const piSkip = skipUnlessPiInstalled();
38+
if (piSkip) return piSkip;
39+
40+
if (!existsSync(path.join(WASM_COMMANDS_DIR, 'tar'))) {
41+
return 'WasmVM tar command not built (expected native/wasmvm/.../commands/tar)';
42+
}
43+
44+
return false;
45+
}
46+
47+
async function waitForBootstrap(
48+
shell: ShellHandle,
49+
getOutput: () => string,
50+
timeoutMs: number,
51+
): Promise<void> {
52+
const deadline = Date.now() + timeoutMs;
53+
54+
while (Date.now() < deadline) {
55+
const output = getOutput();
56+
const visibleOutput = output
57+
.replace(/\u001b\][^\u0007]*\u0007/g, '')
58+
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '')
59+
.replace(/\r/g, '');
60+
if (
61+
output.includes('\u001b[?2004h') &&
62+
visibleOutput.includes('drop files to attach')
63+
) {
64+
return;
65+
}
66+
67+
const exitCode = await Promise.race([
68+
shell.wait(),
69+
new Promise<null>((resolve) => setTimeout(() => resolve(null), 50)),
70+
]);
71+
if (exitCode !== null) {
72+
throw new Error(
73+
`Pi exited before bootstrap completed (code ${exitCode}).\nRaw PTY:\n${output}`,
74+
);
75+
}
76+
}
77+
78+
throw new Error(
79+
`Pi helper bootstrap timed out after ${timeoutMs}ms.\nRaw PTY:\n${getOutput()}`,
80+
);
81+
}
82+
83+
const skipReason = getSkipReason();
84+
85+
describe.skipIf(skipReason)('Pi PTY helper bootstrap (sandbox)', () => {
86+
let kernel: Kernel | undefined;
87+
let shell: ShellHandle | undefined;
88+
let workDir: string | undefined;
89+
let tarRuntimeDir: string | undefined;
90+
91+
afterEach(async () => {
92+
try {
93+
shell?.kill();
94+
} catch {
95+
// Shell may have already exited.
96+
}
97+
shell = undefined;
98+
await kernel?.dispose();
99+
kernel = undefined;
100+
if (workDir) {
101+
await rm(workDir, { recursive: true, force: true });
102+
workDir = undefined;
103+
}
104+
if (tarRuntimeDir) {
105+
await rm(tarRuntimeDir, { recursive: true, force: true });
106+
tarRuntimeDir = undefined;
107+
}
108+
});
109+
110+
it('reaches the Pi TUI with tar-only sandbox commands and preseeded upstream helpers', async () => {
111+
workDir = await mkdtemp(path.join(tmpdir(), 'pi-pty-helper-bootstrap-'));
112+
tarRuntimeDir = await mkdtemp(path.join(tmpdir(), 'pi-pty-tar-runtime-'));
113+
const helperBinDir = await seedPiManagedTools(workDir);
114+
await copyFile(path.join(WASM_COMMANDS_DIR, 'tar'), path.join(tarRuntimeDir, 'tar'));
115+
await chmod(path.join(tarRuntimeDir, 'tar'), 0o755);
116+
117+
const permissions = {
118+
...allowAllFs,
119+
...allowAllNetwork,
120+
...allowAllChildProcess,
121+
...allowAllEnv,
122+
};
123+
124+
kernel = createKernel({
125+
filesystem: createHybridVfs(workDir),
126+
hostNetworkAdapter: createNodeHostNetworkAdapter(),
127+
permissions,
128+
});
129+
await kernel.mount(
130+
createNodeRuntime({
131+
permissions,
132+
}),
133+
);
134+
await kernel.mount(createWasmVmRuntime({ commandDirs: [tarRuntimeDir] }));
135+
136+
shell = kernel.openShell({
137+
command: 'node',
138+
args: ['-e', buildPiInteractiveCode({ workDir, providerApiKey: 'test-key' })],
139+
cwd: SECURE_EXEC_ROOT,
140+
env: {
141+
HOME: workDir,
142+
NO_COLOR: '1',
143+
ANTHROPIC_API_KEY: 'test-key',
144+
PATH: `${helperBinDir}:${process.env.PATH ?? '/usr/bin:/bin'}`,
145+
},
146+
});
147+
148+
const decoder = new TextDecoder();
149+
let rawOutput = '';
150+
shell.onData = (data) => {
151+
rawOutput += decoder.decode(data);
152+
};
153+
154+
await waitForBootstrap(shell, () => rawOutput, 30_000);
155+
156+
const visibleOutput = rawOutput
157+
.replace(/\u001b\][^\u0007]*\u0007/g, '')
158+
.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '')
159+
.replace(/\r/g, '');
160+
161+
expect(visibleOutput).not.toContain('ENOENT: command not found: tar');
162+
expect(visibleOutput).not.toContain('fd 0.1.0 (secure-exec)');
163+
expect(visibleOutput).not.toContain("rg: unrecognized option '--version'");
164+
165+
shell.kill();
166+
const exitCode = await Promise.race([
167+
shell.wait(),
168+
new Promise<number>((_, reject) =>
169+
setTimeout(() => reject(new Error('Pi did not terminate after bootstrap probe')), 20_000),
170+
),
171+
]);
172+
173+
expect(exitCode).not.toBeNull();
174+
}, 60_000);
175+
});

0 commit comments

Comments
 (0)