Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions plugins/pstack/skills/poteto-mode/scripts/omp/parent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,9 @@ export default function parent(pi: Extension): void {
const output = join(directory, "output.txt");
const receipt = join(directory, "receipt.json");
writeFileSync(prompt, args.prompt, { mode: 0o600 });
const bun = Bun.which("bun");
if (!bun) throw new Error("pstack_task requires Bun on PATH");
const runner = process.env.PSTACK_OMP_RUNNER;
if (!runner) throw new Error("Start OMP through pstack-omp to dispatch workers");
const child = Bun.spawn([bun, runner,
const child = Bun.spawn([runner,
"--parent", "omp", "--provider", "omp", "--model", choice.model,
"--effort", choice.effort, "--mode", args.mode, "--cwd", args.cwd,
"--prompt", prompt, "--output", output, "--receipt", receipt, "--label", args.role,
Expand Down
5 changes: 5 additions & 0 deletions plugins/pstack/skills/poteto-mode/scripts/runner/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bun
export {};
const startedAt = Date.now();
const { main } = await import("./cli.ts");
process.exitCode = await main(process.argv.slice(2), startedAt);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bun
const startedAt = Date.now();
const { main } = await import("./cli.ts");
process.exitCode = await main(process.argv.slice(2), startedAt);
#!/bin/sh
unset BUN_OPTIONS NODE_OPTIONS
case $0 in */*) dir=${0%/*} ;; *) dir=. ;; esac
exec bun --no-env-file --config=/dev/null "$dir/entry.ts" "$@"
29 changes: 14 additions & 15 deletions plugins/pstack/skills/poteto-mode/scripts/runner/run.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ describe("runLane", () => {
const transientMarker = join(scratch, "grok-cancel-unauth.pid");
const preflightLog = join(scratch, "grok-cancel-unauth.log");
const input = options("grok", "grok-preflight-retry-cancelled");
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand Down Expand Up @@ -609,7 +609,7 @@ describe("runLane", () => {
it("does not spawn the model when preflight exhausts the wrapper deadline", async () => {
const modelStarted = join(scratch, "deadline-model.started");
const input = { ...options("claude", "preflight-deadline"), timeoutMs: 300 };
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand All @@ -634,7 +634,7 @@ describe("runLane", () => {

it("lets a delayed wrapper lane finish when timeout is omitted", async () => {
const input = options("claude", "unbounded-default");
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: { ...process.env, FAKE_MODEL_DELAY_MS: "400" },
stdout: "pipe",
Expand All @@ -657,7 +657,7 @@ describe("runLane", () => {
...options("claude", "long-runtime-deadline"),
timeoutMs: 2_147_483_648,
};
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: { ...process.env, FAKE_MODEL_DELAY_MS: "100" },
stdout: "pipe",
Expand Down Expand Up @@ -720,7 +720,7 @@ describe("runLane", () => {
it("bounds a descendant-held pipe by the explicit deadline without fabricating a signal", async () => {
const descendantPidPath = join(scratch, "deadline-descendant.pid");
const input = { ...options("claude", "deadline-drain"), timeoutMs: 700 };
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand Down Expand Up @@ -751,7 +751,7 @@ describe("runLane", () => {
it("does not claim a signal was sent to an already signal-reaped child", async () => {
const descendantPidPath = join(scratch, "signalled-descendant.pid");
const input = { ...options("claude", "signalled-drain"), timeoutMs: 700 };
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand Down Expand Up @@ -782,7 +782,7 @@ describe("runLane", () => {
const descendantPidPath = join(scratch, "cancel-descendant.pid");
const modelExiting = join(scratch, "cancel-model.exiting");
const input = options("claude", "cancel-drain");
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand Down Expand Up @@ -815,7 +815,7 @@ describe("runLane", () => {

it("clears a losing long-deadline timer when the shipped wrapper succeeds", async () => {
const input = { ...options("claude", "long-deadline"), timeoutMs: 60_000 };
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: { ...process.env },
stdout: "pipe",
Expand All @@ -838,7 +838,6 @@ describe("runLane", () => {
cpSync(join(import.meta.dir, "../omp"), join(scratch, "omp"), { recursive: true });
cpSync(join(import.meta.dir, "../harnesses.ts"), join(scratch, "harnesses.ts"));
const runner = Bun.spawn([
process.execPath,
join(isolatedRunner, "pstack-runner"),
...runnerArgs(input).slice(1),
], {
Expand Down Expand Up @@ -872,7 +871,7 @@ describe("runLane", () => {
const input = { ...options("codex", "repeated-cancel"), timeoutMs: 60_000 };
const started = join(scratch, "repeated-child.started");
const terminated = join(scratch, "repeated-child.terminated");
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: {
...process.env,
Expand Down Expand Up @@ -913,7 +912,7 @@ describe("runLane", () => {
FAKE_STARTED_PATH: started,
FAKE_TERMINATED_PATH: terminated,
};
const runner = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const runner = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env,
stdout: "pipe",
Expand All @@ -935,7 +934,7 @@ describe("runLane", () => {
error: { message: "launcher received SIGTERM; signal was sent to child" },
});

const samePaths = Bun.spawn([process.execPath, ...runnerArgs(input)], {
const samePaths = Bun.spawn(runnerArgs(input), {
cwd: scratch,
env: { ...process.env },
stdout: "pipe",
Expand Down Expand Up @@ -984,7 +983,7 @@ describe("runLane", () => {
it("terminalizes catchable failures after reserving output paths", async () => {
const unreadable = options("claude", "unreadable-prompt");
chmodSync(unreadable.promptPath, 0o000);
const unreadableRunner = Bun.spawn([process.execPath, ...runnerArgs(unreadable)], {
const unreadableRunner = Bun.spawn(runnerArgs(unreadable), {
cwd: scratch,
env: { ...process.env },
stdout: "pipe",
Expand All @@ -1005,7 +1004,7 @@ describe("runLane", () => {
error: { message: "launcher failed after reserving output paths" },
});

const samePaths = Bun.spawn([process.execPath, ...runnerArgs(unreadable)], {
const samePaths = Bun.spawn(runnerArgs(unreadable), {
cwd: scratch,
env: { ...process.env },
stdout: "pipe",
Expand All @@ -1019,7 +1018,7 @@ describe("runLane", () => {

const spawnFailure = options("claude", "spawn-failure");
const modelStarted = join(scratch, "spawn-failure-model.started");
const spawnRunner = Bun.spawn([process.execPath, ...runnerArgs(spawnFailure)], {
const spawnRunner = Bun.spawn(runnerArgs(spawnFailure), {
cwd: scratch,
env: {
...process.env,
Expand Down
65 changes: 65 additions & 0 deletions plugins/pstack/skills/poteto-mode/scripts/runner/startup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { afterEach, describe, expect, it } from "bun:test";
import { chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";

const directories: string[] = [];
afterEach(() => {
for (const directory of directories.splice(0)) rmSync(directory, { recursive: true, force: true });
});

describe("runner startup isolation", () => {
for (const source of ["bun options", "node options", "dotenv", "bunfig", "combined"] as const) {
it(`ignores ${source} while preserving exported child variables`, () => {
const cwd = mkdtempSync(join(tmpdir(), "pstack startup "));
directories.push(cwd);
const bin = join(cwd, "bin");
mkdirSync(bin);
const marker = join(cwd, "preload-ran");
const preload = join(cwd, "preload.cjs");
writeFileSync(preload, `require("node:fs").writeFileSync(${JSON.stringify(marker)}, "executed");`);
const executable = join(bin, "codex");
writeFileSync(executable, `#!/bin/sh
if [ "$1" = login ]; then
echo 'Logged in using ChatGPT'
exit 0
fi
env > "$PSTACK_TEST_ENV"
echo '{"type":"item.completed","item":{"type":"agent_message","text":"STARTUP_OK"}}'
echo '{"type":"turn.completed","usage":{"input_tokens":1,"output_tokens":1}}'
`);
chmodSync(executable, 0o755);
if (source === "dotenv" || source === "combined") writeFileSync(join(cwd, ".env"), "PSTACK_DOTENV_SENTINEL=project-only\n");
if (source === "bunfig" || source === "combined") writeFileSync(join(cwd, "bunfig.toml"), `preload = [${JSON.stringify(preload)}]\n`);
const prompt = join(cwd, "prompt.md");
writeFileSync(prompt, "Return STARTUP_OK.");
const output = join(cwd, "output.txt");
const receipt = join(cwd, "receipt.json");
const dump = join(cwd, "child.env");
const env = {
...process.env,
PATH: `${bin}:${dirname(process.execPath)}:/usr/bin:/bin`,
PSTACK_TEST_ENV: dump,
OPENAI_API_KEY: "synthetic-exported-key",
CODEX_THREAD_ID: "synthetic-parent-identity",
BUN_OPTIONS: source === "bun options" || source === "combined" ? `--preload=${JSON.stringify(preload)}` : "",
NODE_OPTIONS: source === "node options" || source === "combined" ? `--require=${JSON.stringify(preload)}` : "",
};
const result = Bun.spawnSync([
join(import.meta.dir, "pstack-runner"),
"--parent", "claude", "--provider", "codex", "--model", "gpt-5.6-sol", "--effort", "max",
"--mode", "read-only", "--cwd", cwd, "--prompt", prompt, "--output", output, "--receipt", receipt,
], { cwd, env });
expect(result.exitCode).toBe(0);
expect(readFileSync(output, "utf8")).toContain("STARTUP_OK");
expect(JSON.parse(readFileSync(receipt, "utf8"))).toMatchObject({ status: "complete", parent: "claude", provider: "codex" });
expect(existsSync(marker)).toBe(false);
const childEnv = readFileSync(dump, "utf8");
expect(childEnv).not.toContain("PSTACK_DOTENV_SENTINEL=");
expect(childEnv).not.toContain("BUN_OPTIONS=");
expect(childEnv).not.toContain("NODE_OPTIONS=");
expect(childEnv).toContain("OPENAI_API_KEY=synthetic-exported-key\n");
expect(childEnv).toContain("CODEX_THREAD_ID=synthetic-parent-identity\n");
});
}
});
2 changes: 1 addition & 1 deletion tests/omp-live.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def do_POST(self):
if scenario == 'missing-model':
selected = 'pstack-test/missing'
effort = 'max' if scenario == 'unsupported-effort' else 'off' if scenario == 'non-reasoning' else 'high'
argv = [bun, str(scripts / 'runner/pstack-runner'), '--parent', 'omp', '--provider', 'omp',
argv = [str(scripts / 'runner/pstack-runner'), '--parent', 'omp', '--provider', 'omp',
'--model', selected, '--effort', effort, '--mode',
'isolated-write' if scenario in ('outside-write', 'inside-write') else 'read-only',
'--cwd', str(work), '--prompt', str(root / 'prompt'), '--output', str(output), '--receipt', str(receipt)]
Expand Down
Loading