@h-rig/runtime 0.0.6-alpha.21 → 0.0.6-alpha.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/bin/rig-agent-dispatch.js +588 -28
  2. package/dist/src/control-plane/agent-wrapper.js +592 -28
  3. package/dist/src/control-plane/harness-main.js +142 -17
  4. package/dist/src/control-plane/hooks/completion-verification.js +142 -17
  5. package/dist/src/control-plane/native/harness-cli.js +142 -17
  6. package/dist/src/control-plane/native/pr-automation.js +142 -17
  7. package/dist/src/control-plane/native/pr-review-gate.js +142 -17
  8. package/dist/src/control-plane/native/run-ops.js +1 -1
  9. package/dist/src/control-plane/native/task-ops.js +142 -17
  10. package/dist/src/control-plane/native/verifier.js +142 -17
  11. package/dist/src/control-plane/pi-sessiond/bin.js +793 -0
  12. package/dist/src/control-plane/pi-sessiond/client.js +41 -0
  13. package/dist/src/control-plane/pi-sessiond/event-hub.js +59 -0
  14. package/dist/src/control-plane/pi-sessiond/extension-ui-context.js +198 -0
  15. package/dist/src/control-plane/pi-sessiond/launcher.js +163 -0
  16. package/dist/src/control-plane/pi-sessiond/server.js +802 -0
  17. package/dist/src/control-plane/pi-sessiond/session-service.js +540 -0
  18. package/dist/src/control-plane/pi-sessiond/types.js +1 -0
  19. package/dist/src/control-plane/runtime/index.js +17 -0
  20. package/dist/src/control-plane/runtime/isolation/home.js +17 -0
  21. package/dist/src/control-plane/runtime/isolation/index.js +17 -0
  22. package/dist/src/control-plane/runtime/isolation/runner.js +17 -0
  23. package/dist/src/control-plane/runtime/isolation.js +17 -0
  24. package/dist/src/control-plane/runtime/queue.js +17 -0
  25. package/package.json +7 -6
@@ -567,8 +567,9 @@ var init_backend_bwrap = __esm(() => {
567
567
  });
568
568
 
569
569
  // packages/runtime/src/control-plane/agent-wrapper.ts
570
- import { resolve as resolve35 } from "path";
571
- import { existsSync as existsSync33, mkdirSync as mkdirSync19, writeFileSync as writeFileSync13 } from "fs";
570
+ import { createRequire } from "module";
571
+ import { resolve as resolve36 } from "path";
572
+ import { existsSync as existsSync34, mkdirSync as mkdirSync20, writeFileSync as writeFileSync13 } from "fs";
572
573
 
573
574
  // packages/runtime/src/control-plane/runtime/context.ts
574
575
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -5517,6 +5518,21 @@ var GITHUB_KNOWN_HOSTS = [
5517
5518
  "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk="
5518
5519
  ].join(`
5519
5520
  `);
5521
+ function resolveControlPlaneSourceRoot(projectRoot) {
5522
+ const candidates = [
5523
+ process.env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
5524
+ process.env.RIG_HOST_PROJECT_ROOT?.trim(),
5525
+ resolve25(import.meta.dir, "../../../../.."),
5526
+ projectRoot
5527
+ ].filter((value) => Boolean(value));
5528
+ for (const candidate of candidates) {
5529
+ const root = resolve25(candidate);
5530
+ if (existsSync24(resolve25(root, "packages/runtime/src/control-plane/pi-sessiond/bin.ts"))) {
5531
+ return root;
5532
+ }
5533
+ }
5534
+ return "";
5535
+ }
5520
5536
  async function runtimeEnv(projectRoot, runtime) {
5521
5537
  const bunBinaryPath = resolveBunBinaryPath();
5522
5538
  const bunDir = resolveBunInstallDir(bunBinaryPath);
@@ -5562,9 +5578,11 @@ async function runtimeEnv(projectRoot, runtime) {
5562
5578
  const runtimeRigGit = resolve25(runtime.binDir, runtimeRigGitFileName());
5563
5579
  const preferredShell = existsSync24(runtimeBash) ? runtimeBash : "/bin/bash";
5564
5580
  const nativeRuntimeLibraryPath = await materializeNativeRuntimeLibrary(runtime.binDir);
5581
+ const controlPlaneSourceRoot = resolveControlPlaneSourceRoot(projectRoot);
5565
5582
  const env = {
5566
5583
  PROJECT_RIG_ROOT: projectRoot,
5567
5584
  RIG_HOST_PROJECT_ROOT: projectRoot,
5585
+ ...controlPlaneSourceRoot ? { RIG_CONTROL_PLANE_SOURCE_ROOT: controlPlaneSourceRoot } : {},
5568
5586
  HOME: runtime.homeDir,
5569
5587
  TMPDIR: runtime.tmpDir,
5570
5588
  XDG_CACHE_HOME: runtime.cacheDir,
@@ -8883,7 +8901,165 @@ function formatJsonRpcError(error) {
8883
8901
  return parts.join(" ");
8884
8902
  }
8885
8903
 
8904
+ // packages/runtime/src/control-plane/pi-sessiond/launcher.ts
8905
+ import { randomBytes } from "crypto";
8906
+ import { existsSync as existsSync33, mkdirSync as mkdirSync19, readFileSync as readFileSync16, rmSync as rmSync13 } from "fs";
8907
+ import { dirname as dirname14, resolve as resolve35 } from "path";
8908
+ import { fileURLToPath as fileURLToPath2 } from "url";
8909
+
8910
+ // packages/runtime/src/control-plane/pi-sessiond/client.ts
8911
+ class RigPiSessionDaemonClient {
8912
+ baseUrl;
8913
+ token;
8914
+ constructor(options) {
8915
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
8916
+ this.token = options.token;
8917
+ }
8918
+ static fromConnection(connection, token) {
8919
+ if (connection.mode === "http")
8920
+ return new RigPiSessionDaemonClient({ baseUrl: connection.baseUrl, token });
8921
+ throw new Error("Unix-socket Rig Pi daemon connections are not implemented in this build; use loopback HTTP.");
8922
+ }
8923
+ async request(method, path, body) {
8924
+ const response = await fetch(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`, {
8925
+ method,
8926
+ headers: {
8927
+ authorization: `Bearer ${this.token}`,
8928
+ ...body === undefined ? {} : { "content-type": "application/json" }
8929
+ },
8930
+ body: body === undefined ? undefined : JSON.stringify(body)
8931
+ });
8932
+ const text = await response.text();
8933
+ const payload = text.trim() ? JSON.parse(text) : undefined;
8934
+ if (!response.ok) {
8935
+ const message = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.error === "string" ? payload.error : text || response.statusText;
8936
+ throw new Error(`Rig Pi session daemon request failed (${response.status}): ${message}`);
8937
+ }
8938
+ return payload;
8939
+ }
8940
+ webSocketUrl(path) {
8941
+ const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
8942
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
8943
+ url.searchParams.set("token", this.token);
8944
+ return url.toString();
8945
+ }
8946
+ }
8947
+
8948
+ // packages/runtime/src/control-plane/pi-sessiond/launcher.ts
8949
+ var BUILD_CONFIG2 = {};
8950
+ var BAKED_RIG_SOURCE_ROOT = BUILD_CONFIG2.RIG_SOURCE_ROOT ?? "";
8951
+ async function ensureRigPiSessionDaemon(input) {
8952
+ const rootDir = resolve35(input.rootDir);
8953
+ mkdirSync19(rootDir, { recursive: true });
8954
+ const readyFile = resolve35(rootDir, "ready.json");
8955
+ const existing = readDaemonReadyFile(readyFile);
8956
+ const existingHandle = existing ? await tryReady(existing) : null;
8957
+ if (existingHandle)
8958
+ return existingHandle;
8959
+ try {
8960
+ rmSync13(readyFile, { force: true });
8961
+ } catch {}
8962
+ const token = randomBytes(32).toString("hex");
8963
+ const binPath = resolveRigPiSessionDaemonBinPath(input.env);
8964
+ const bunPath = input.env.RIG_BUN_PATH || process.execPath;
8965
+ const proc = Bun.spawn([bunPath, binPath], {
8966
+ cwd: rootDir,
8967
+ env: {
8968
+ ...input.env,
8969
+ RIG_PI_SESSIOND_ROOT: rootDir,
8970
+ RIG_PI_SESSIOND_TOKEN: token,
8971
+ RIG_PI_SESSIOND_READY_FILE: readyFile,
8972
+ RIG_PI_SESSIOND_HOST: "127.0.0.1",
8973
+ RIG_PI_SESSIOND_PORT: "0",
8974
+ ...input.version ? { RIG_VERSION: input.version } : {},
8975
+ ...input.commit ? { RIG_GIT_COMMIT: input.commit } : {}
8976
+ },
8977
+ stdin: "ignore",
8978
+ stdout: "ignore",
8979
+ stderr: "inherit"
8980
+ });
8981
+ proc.unref();
8982
+ const deadline = Date.now() + (input.timeoutMs ?? 15000);
8983
+ while (Date.now() < deadline) {
8984
+ const ready = readDaemonReadyFile(readyFile);
8985
+ const handle = ready ? await tryReady(ready) : null;
8986
+ if (handle)
8987
+ return handle;
8988
+ await sleep(100);
8989
+ }
8990
+ throw new Error(`Rig Pi session daemon did not become ready at ${readyFile}`);
8991
+ }
8992
+ function privateMetadataForDaemon(input) {
8993
+ return { public: input.publicMetadata, daemonConnection: input.connection };
8994
+ }
8995
+ async function tryReady(ready) {
8996
+ const host = typeof ready.host === "string" ? ready.host : "127.0.0.1";
8997
+ const port = typeof ready.port === "number" ? ready.port : Number(ready.port);
8998
+ const token = typeof ready.token === "string" ? ready.token : "";
8999
+ if (!Number.isFinite(port) || port <= 0 || !token)
9000
+ return null;
9001
+ const baseUrl = `http://${host}:${port}`;
9002
+ const client = new RigPiSessionDaemonClient({ baseUrl, token });
9003
+ try {
9004
+ await client.request("GET", "/health");
9005
+ } catch {
9006
+ return null;
9007
+ }
9008
+ return {
9009
+ client,
9010
+ connection: { mode: "http", baseUrl, tokenRef: tokenRefFromReady(ready) },
9011
+ token,
9012
+ ready
9013
+ };
9014
+ }
9015
+ function tokenRefFromReady(ready) {
9016
+ const token = typeof ready.token === "string" ? ready.token : "";
9017
+ return token ? `inline:${token}` : "missing";
9018
+ }
9019
+ function resolveRigPiSessionDaemonBinPath(env) {
9020
+ const explicit = env.RIG_PI_SESSIOND_BIN?.trim();
9021
+ if (explicit)
9022
+ return explicit;
9023
+ const roots = [
9024
+ env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
9025
+ BAKED_RIG_SOURCE_ROOT.trim(),
9026
+ process.env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
9027
+ process.env.RIG_HOST_PROJECT_ROOT?.trim(),
9028
+ process.env.PROJECT_RIG_ROOT?.trim()
9029
+ ].filter((value) => Boolean(value));
9030
+ for (const root of roots) {
9031
+ const candidate = resolve35(root, "packages/runtime/src/control-plane/pi-sessiond/bin.ts");
9032
+ if (existsSync33(candidate))
9033
+ return candidate;
9034
+ }
9035
+ const moduleCandidate = fileURLToPath2(new URL("./bin.ts", import.meta.url));
9036
+ if (existsSync33(moduleCandidate))
9037
+ return moduleCandidate;
9038
+ throw new Error("Unable to locate rig-pi-sessiond entrypoint. Set RIG_PI_SESSIOND_BIN or RIG_CONTROL_PLANE_SOURCE_ROOT to the Rig source checkout.");
9039
+ }
9040
+ function readDaemonReadyFile(path) {
9041
+ if (!existsSync33(path))
9042
+ return null;
9043
+ try {
9044
+ const parsed = JSON.parse(readFileSync16(path, "utf8"));
9045
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
9046
+ } catch {
9047
+ return null;
9048
+ }
9049
+ }
9050
+ function sleep(ms) {
9051
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
9052
+ }
9053
+ function resolveRigPiSessionDaemonRoot(stateDir) {
9054
+ const root = resolve35(stateDir, "pi-sessiond");
9055
+ mkdirSync19(dirname14(root), { recursive: true });
9056
+ if (!existsSync33(root))
9057
+ mkdirSync19(root, { recursive: true });
9058
+ return root;
9059
+ }
9060
+
8886
9061
  // packages/runtime/src/control-plane/agent-wrapper.ts
9062
+ var requireFromRuntime = createRequire(import.meta.url);
8887
9063
  async function finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, context) {
8888
9064
  try {
8889
9065
  await snapshotSidecar.finalize(providerCommand, exitCode);
@@ -8916,7 +9092,7 @@ async function startOptionalRuntimeSnapshotSidecar(runtime, startSidecar = start
8916
9092
  }
8917
9093
  }
8918
9094
  async function runAgentWrapper(options = {}) {
8919
- const projectRoot = resolve35(options.projectRoot || process.env.PROJECT_RIG_ROOT || process.cwd());
9095
+ const projectRoot = resolve36(options.projectRoot || process.env.PROJECT_RIG_ROOT || process.cwd());
8920
9096
  const monorepoRoot = resolveMonorepoRoot2(projectRoot);
8921
9097
  const argv = options.argv || process.argv.slice(2);
8922
9098
  if (argv.length === 0 || argv[0] === "--version" || argv[0] === "--help" || argv[0] === "help") {
@@ -8979,7 +9155,8 @@ async function runAgentWrapper(options = {}) {
8979
9155
  return 1;
8980
9156
  }
8981
9157
  const providerArgs = buildProviderArgs(provider, runtime, argv);
8982
- const providerCommand = [providerBinary(provider), ...providerArgs];
9158
+ const normalPiDaemonPath = provider === "pi" && process.env.RIG_PI_RPC_FALLBACK !== "1";
9159
+ const providerCommand = normalPiDaemonPath ? ["rig-pi-sessiond", ...providerArgs] : [providerBinary(provider), ...providerArgs];
8983
9160
  emitWrapperEvent("provider.launch", {
8984
9161
  provider,
8985
9162
  runtimeId: runtime.id,
@@ -8991,11 +9168,11 @@ async function runAgentWrapper(options = {}) {
8991
9168
  const bypassOuterRuntimeSandbox = shouldBypassProviderSandboxOnPlatform(provider, process.platform);
8992
9169
  const runClaudeCompatUnsandboxed = provider === "claude-code" && bypassOuterRuntimeSandbox;
8993
9170
  if (runClaudeCompatUnsandboxed && process.env.HOME?.trim()) {
8994
- env.CLAUDE_HOME = resolve35(process.env.HOME.trim(), ".claude");
9171
+ env.CLAUDE_HOME = resolve36(process.env.HOME.trim(), ".claude");
8995
9172
  env.RIG_CLAUDE_RUNTIME_HOME = runtime.claudeHomeDir;
8996
9173
  }
8997
9174
  if (provider === "pi") {
8998
- env.PI_CODING_AGENT_DIR = resolve35(runtime.homeDir, ".pi", "agent");
9175
+ env.PI_CODING_AGENT_DIR = resolve36(runtime.homeDir, ".pi", "agent");
8999
9176
  env.PI_CODING_AGENT_SESSION_DIR = runtime.sessionDir;
9000
9177
  }
9001
9178
  env.RIG_RUNTIME_SANDBOX = "enforce";
@@ -9029,14 +9206,36 @@ async function runAgentWrapper(options = {}) {
9029
9206
  },
9030
9207
  command: providerCommand
9031
9208
  })).command;
9032
- const proc = Bun.spawn(command, {
9033
- cwd: runtime.workspaceDir,
9034
- env,
9035
- stdin: "inherit",
9036
- stdout: "inherit",
9037
- stderr: "inherit"
9038
- });
9039
- exitCode = await proc.exited;
9209
+ if (provider === "pi" && process.env.RIG_PI_RPC_FALLBACK !== "1") {
9210
+ const prompt = await readProcessStdin();
9211
+ exitCode = await runPiSessionDaemonProvider({
9212
+ projectRoot,
9213
+ runtime,
9214
+ env,
9215
+ prompt,
9216
+ runId: process.env.RIG_SERVER_RUN_ID?.trim() || process.env.RIG_RUN_ID?.trim() || undefined,
9217
+ sessionName: process.env.RIG_SERVER_RUN_ID?.trim() ? `Rig ${process.env.RIG_SERVER_RUN_ID.trim()}` : `Rig ${runtime.taskId}`
9218
+ });
9219
+ } else if (provider === "pi" && isPiRpcArgs(providerArgs)) {
9220
+ const prompt = await readProcessStdin();
9221
+ exitCode = await runPiRpcProviderFallback({
9222
+ command,
9223
+ cwd: runtime.workspaceDir,
9224
+ env,
9225
+ prompt,
9226
+ runId: process.env.RIG_SERVER_RUN_ID?.trim() || undefined,
9227
+ sessionName: process.env.RIG_SERVER_RUN_ID?.trim() ? `Rig ${process.env.RIG_SERVER_RUN_ID.trim()}` : `Rig ${runtime.taskId}`
9228
+ });
9229
+ } else {
9230
+ const proc = Bun.spawn(command, {
9231
+ cwd: runtime.workspaceDir,
9232
+ env,
9233
+ stdin: "inherit",
9234
+ stdout: "inherit",
9235
+ stderr: "inherit"
9236
+ });
9237
+ exitCode = await proc.exited;
9238
+ }
9040
9239
  }
9041
9240
  if (snapshotSidecar) {
9042
9241
  await finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, {
@@ -9073,6 +9272,337 @@ async function runAgentWrapper(options = {}) {
9073
9272
  }
9074
9273
  return finalExitCode;
9075
9274
  }
9275
+ function parseJsonRecord(line) {
9276
+ try {
9277
+ const parsed = JSON.parse(line);
9278
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
9279
+ } catch {
9280
+ return null;
9281
+ }
9282
+ }
9283
+ async function readProcessStdin() {
9284
+ if (process.stdin.isTTY)
9285
+ return "";
9286
+ return await new Promise((resolveRead) => {
9287
+ let data = "";
9288
+ process.stdin.setEncoding("utf8");
9289
+ process.stdin.on("data", (chunk) => {
9290
+ data += String(chunk);
9291
+ });
9292
+ process.stdin.on("end", () => resolveRead(data));
9293
+ process.stdin.resume();
9294
+ });
9295
+ }
9296
+ async function pumpReadableLines(stream, onLine) {
9297
+ if (!stream)
9298
+ return;
9299
+ const reader = stream.getReader();
9300
+ const decoder = new TextDecoder;
9301
+ let buffer = "";
9302
+ try {
9303
+ while (true) {
9304
+ const { done, value } = await reader.read();
9305
+ if (done)
9306
+ break;
9307
+ buffer += decoder.decode(value, { stream: true });
9308
+ const parts = buffer.split(/\r?\n/);
9309
+ buffer = parts.pop() ?? "";
9310
+ for (const part of parts)
9311
+ onLine(part);
9312
+ }
9313
+ buffer += decoder.decode();
9314
+ if (buffer)
9315
+ onLine(buffer);
9316
+ } finally {
9317
+ reader.releaseLock();
9318
+ }
9319
+ }
9320
+ function isBlockingPiRpcUiRequest(record) {
9321
+ if (record.type !== "extension_ui_request")
9322
+ return false;
9323
+ return record.method === "select" || record.method === "confirm" || record.method === "input" || record.method === "editor";
9324
+ }
9325
+ function writeRpcCommand(stdin, command) {
9326
+ stdin.write(`${JSON.stringify(command)}
9327
+ `);
9328
+ }
9329
+ function joinUrl(baseUrl, pathname) {
9330
+ return `${baseUrl.replace(/\/+$/, "")}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
9331
+ }
9332
+ async function readQueuedSteeringFromServer(input) {
9333
+ if (!input.serverUrl || !input.runId)
9334
+ return [];
9335
+ const headers = {};
9336
+ if (input.authToken)
9337
+ headers.authorization = `Bearer ${input.authToken}`;
9338
+ const response = await fetch(joinUrl(input.serverUrl, `/api/runs/${encodeURIComponent(input.runId)}/steering?ack=1`), { headers });
9339
+ if (!response.ok)
9340
+ return [];
9341
+ const payload = await response.json().catch(() => null);
9342
+ const messages = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.messages : null;
9343
+ return Array.isArray(messages) ? messages.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
9344
+ }
9345
+ function steeringMessageText(entry) {
9346
+ const message = typeof entry.message === "string" ? entry.message.trim() : "";
9347
+ return message || null;
9348
+ }
9349
+ function isPiRpcArgs(args) {
9350
+ return cliOptionValue(args, "--mode") === "rpc";
9351
+ }
9352
+ async function runPiSessionDaemonProvider(input) {
9353
+ const stdout = input.stdout ?? process.stdout;
9354
+ const stderr = input.stderr ?? process.stderr;
9355
+ const runId = input.runId ?? input.runtime.taskId;
9356
+ emitWrapperEvent("pi.sessiond.starting", {
9357
+ runId,
9358
+ runtimeId: input.runtime.id,
9359
+ workspaceDir: input.runtime.workspaceDir
9360
+ });
9361
+ const daemon = await ensureRigPiSessionDaemon({
9362
+ rootDir: resolveRigPiSessionDaemonRoot(input.runtime.stateDir),
9363
+ env: input.env,
9364
+ version: process.env.RIG_VERSION?.trim() || "dev",
9365
+ commit: process.env.RIG_GIT_COMMIT?.trim() || undefined
9366
+ });
9367
+ emitWrapperEvent("pi.sessiond.ready", {
9368
+ runId,
9369
+ runtimeId: input.runtime.id,
9370
+ connection: daemon.connection,
9371
+ ready: daemon.ready
9372
+ });
9373
+ const start = await daemon.client.request("POST", "/sessions", {
9374
+ runId,
9375
+ cwd: input.runtime.workspaceDir,
9376
+ agentDir: input.env.PI_CODING_AGENT_DIR || resolve36(input.runtime.homeDir, ".pi", "agent"),
9377
+ sessionDir: input.runtime.sessionDir,
9378
+ sessionName: input.sessionName
9379
+ });
9380
+ const privateMetadata = privateMetadataForDaemon({ publicMetadata: start.metadata, connection: daemon.connection });
9381
+ emitWrapperEvent("pi.session.ready", {
9382
+ runId,
9383
+ runtimeId: input.runtime.id,
9384
+ metadata: start.metadata,
9385
+ privateMetadata
9386
+ });
9387
+ const eventStream = waitForPiSessionEvents({
9388
+ url: daemon.client.webSocketUrl(`/sessions/${encodeURIComponent(start.metadata.sessionId)}/events`),
9389
+ stdout,
9390
+ stderr,
9391
+ runId
9392
+ });
9393
+ emitWrapperEvent("pi.session.event_stream.connected", { runId, sessionId: start.metadata.sessionId });
9394
+ const forwardSigterm = () => {
9395
+ daemon.client.request("POST", `/sessions/${encodeURIComponent(start.metadata.sessionId)}/abort`).catch(() => {
9396
+ return;
9397
+ });
9398
+ eventStream.close();
9399
+ };
9400
+ process.once("SIGTERM", forwardSigterm);
9401
+ try {
9402
+ if (input.prompt.trim()) {
9403
+ await daemon.client.request("POST", `/sessions/${encodeURIComponent(start.metadata.sessionId)}/prompt`, { text: input.prompt });
9404
+ emitWrapperEvent("pi.prompt.sent", { runId, sessionId: start.metadata.sessionId, bytes: Buffer.byteLength(input.prompt) });
9405
+ } else {
9406
+ emitWrapperEvent("pi.prompt.waiting", { runId, sessionId: start.metadata.sessionId, reason: "empty-initial-prompt" });
9407
+ }
9408
+ const result = await eventStream.done;
9409
+ if (result.error) {
9410
+ stderr.write(`[rig-agent] Pi session daemon stream failed: ${result.error}
9411
+ `);
9412
+ return 1;
9413
+ }
9414
+ return 0;
9415
+ } finally {
9416
+ process.off("SIGTERM", forwardSigterm);
9417
+ eventStream.close();
9418
+ }
9419
+ }
9420
+ function waitForPiSessionEvents(input) {
9421
+ let closed = false;
9422
+ let resolved = false;
9423
+ let socket = null;
9424
+ let resolveDone = () => {
9425
+ return;
9426
+ };
9427
+ const done = new Promise((resolveDoneInner) => {
9428
+ resolveDone = resolveDoneInner;
9429
+ });
9430
+ const finish = (value) => {
9431
+ if (resolved)
9432
+ return;
9433
+ resolved = true;
9434
+ resolveDone(value);
9435
+ };
9436
+ socket = new WebSocket(input.url);
9437
+ socket.addEventListener("message", (message) => {
9438
+ const text = typeof message.data === "string" ? message.data : Buffer.from(message.data).toString("utf8");
9439
+ input.stdout.write(`${text}
9440
+ `);
9441
+ const envelope = parseJsonRecord(text);
9442
+ if (!envelope)
9443
+ return;
9444
+ if (envelope.type === "pi.event") {
9445
+ const event = envelope.event && typeof envelope.event === "object" && !Array.isArray(envelope.event) ? envelope.event : null;
9446
+ if (event?.type === "agent_end") {
9447
+ emitWrapperEvent("pi.session.agent_end", { runId: input.runId, sessionId: envelope.sessionId });
9448
+ finish({});
9449
+ }
9450
+ }
9451
+ if (envelope.type === "error") {
9452
+ emitWrapperEvent("pi.session.error", { runId: input.runId, message: envelope.message, detail: envelope.detail ?? null });
9453
+ finish({ error: envelope.message });
9454
+ }
9455
+ });
9456
+ socket.addEventListener("error", () => {
9457
+ if (!closed)
9458
+ finish({ error: "WebSocket error" });
9459
+ });
9460
+ socket.addEventListener("close", () => {
9461
+ if (!closed && !resolved)
9462
+ finish({ error: "WebSocket closed before agent_end" });
9463
+ });
9464
+ return {
9465
+ done,
9466
+ close: () => {
9467
+ closed = true;
9468
+ try {
9469
+ socket?.close();
9470
+ } catch {}
9471
+ }
9472
+ };
9473
+ }
9474
+ async function runPiRpcProviderFallback(input) {
9475
+ const stdout = input.stdout ?? process.stdout;
9476
+ const stderr = input.stderr ?? process.stderr;
9477
+ const proc = Bun.spawn(input.command, {
9478
+ cwd: input.cwd,
9479
+ env: input.env,
9480
+ stdin: "pipe",
9481
+ stdout: "pipe",
9482
+ stderr: "pipe"
9483
+ });
9484
+ let sawAgentEnd = false;
9485
+ let promptError = null;
9486
+ let stdinOpen = true;
9487
+ let steeringPollStopped = false;
9488
+ const closeStdin = () => {
9489
+ if (!stdinOpen)
9490
+ return;
9491
+ stdinOpen = false;
9492
+ try {
9493
+ steeringPollStopped = true;
9494
+ proc.stdin.end();
9495
+ } catch {}
9496
+ };
9497
+ const send = (command) => {
9498
+ if (!stdinOpen)
9499
+ return;
9500
+ try {
9501
+ writeRpcCommand(proc.stdin, command);
9502
+ } catch (error) {
9503
+ promptError ??= error instanceof Error ? error.message : String(error);
9504
+ }
9505
+ };
9506
+ const forwardSigterm = () => {
9507
+ try {
9508
+ proc.kill("SIGTERM");
9509
+ } catch {}
9510
+ };
9511
+ process.once("SIGTERM", forwardSigterm);
9512
+ const pollSteering = async () => {
9513
+ const serverUrl = input.env.RIG_SERVER_URL || input.env.RIG_SERVER_BASE_URL;
9514
+ const authToken = input.env.RIG_AUTH_TOKEN || input.env.RIG_SERVER_AUTH_TOKEN;
9515
+ while (!steeringPollStopped && stdinOpen) {
9516
+ try {
9517
+ const messages = await readQueuedSteeringFromServer({ serverUrl, authToken, runId: input.runId });
9518
+ for (const message of messages) {
9519
+ const text = steeringMessageText(message);
9520
+ if (!text || !stdinOpen)
9521
+ continue;
9522
+ send({
9523
+ id: typeof message.id === "string" ? `rig_steer_${message.id}` : `rig_steer_${Date.now()}`,
9524
+ type: "prompt",
9525
+ message: text,
9526
+ streamingBehavior: "steer"
9527
+ });
9528
+ emitWrapperEvent("pi.rpc.steering.delivered", {
9529
+ runId: input.runId ?? null,
9530
+ steeringId: typeof message.id === "string" ? message.id : null,
9531
+ actor: typeof message.actor === "string" ? message.actor : "operator",
9532
+ message: text
9533
+ });
9534
+ }
9535
+ } catch (error) {
9536
+ emitWrapperEvent("pi.rpc.steering.poll.failed", {
9537
+ runId: input.runId ?? null,
9538
+ error: error instanceof Error ? error.message : String(error)
9539
+ });
9540
+ }
9541
+ await sleep2(1000);
9542
+ }
9543
+ };
9544
+ const stdoutPump = pumpReadableLines(proc.stdout, (line) => {
9545
+ stdout.write(`${line}
9546
+ `);
9547
+ const record = parseJsonRecord(line.trim());
9548
+ if (!record)
9549
+ return;
9550
+ if (record.type === "agent_end") {
9551
+ sawAgentEnd = true;
9552
+ closeStdin();
9553
+ return;
9554
+ }
9555
+ if (record.type === "response" && record.command === "prompt" && record.success === false) {
9556
+ promptError = typeof record.error === "string" ? record.error : "Pi RPC prompt failed.";
9557
+ closeStdin();
9558
+ return;
9559
+ }
9560
+ if (isBlockingPiRpcUiRequest(record)) {
9561
+ const id = typeof record.id === "string" ? record.id : "";
9562
+ if (id) {
9563
+ send({ type: "extension_ui_response", id, cancelled: true });
9564
+ emitWrapperEvent("pi.rpc.extension_ui.cancelled", {
9565
+ id,
9566
+ method: record.method,
9567
+ reason: "noninteractive Rig worker RPC session"
9568
+ });
9569
+ }
9570
+ }
9571
+ });
9572
+ const stderrPump = pumpReadableLines(proc.stderr, (line) => {
9573
+ stderr.write(`${line}
9574
+ `);
9575
+ });
9576
+ if (input.sessionName?.trim()) {
9577
+ send({ id: "rig_set_session_name", type: "set_session_name", name: input.sessionName.trim() });
9578
+ }
9579
+ const steeringPollPromise = pollSteering();
9580
+ if (input.prompt.trim()) {
9581
+ send({ id: "rig_initial_prompt", type: "prompt", message: input.prompt });
9582
+ emitWrapperEvent("pi.rpc.prompt.sent", {
9583
+ runId: input.runId ?? null,
9584
+ kind: "initial",
9585
+ bytes: Buffer.byteLength(input.prompt)
9586
+ });
9587
+ } else {
9588
+ closeStdin();
9589
+ }
9590
+ const exitCode = await proc.exited;
9591
+ process.off("SIGTERM", forwardSigterm);
9592
+ steeringPollStopped = true;
9593
+ await Promise.all([stdoutPump, stderrPump, steeringPollPromise]);
9594
+ if (promptError) {
9595
+ stderr.write(`[rig-agent] Pi RPC prompt failed: ${promptError}
9596
+ `);
9597
+ return exitCode === 0 ? 1 : exitCode;
9598
+ }
9599
+ if (input.prompt.trim() && !sawAgentEnd && exitCode === 0) {
9600
+ stderr.write(`[rig-agent] Pi RPC exited before emitting agent_end.
9601
+ `);
9602
+ return 1;
9603
+ }
9604
+ return exitCode;
9605
+ }
9076
9606
  function resolveFinalProviderExitCode(input) {
9077
9607
  if (input.providerExitCode !== 0) {
9078
9608
  return input.providerExitCode;
@@ -9104,6 +9634,9 @@ function buildProviderArgs(provider, runtime, argv) {
9104
9634
  }
9105
9635
  if (provider === "pi") {
9106
9636
  const piArgs = [...argv];
9637
+ if (piArgs.includes("__rig_pi_session_daemon__")) {
9638
+ return piArgs.filter((arg) => arg !== "__rig_pi_session_daemon__");
9639
+ }
9107
9640
  const piProvider = cliOptionValue(piArgs, "--provider") || process.env.RIG_PI_PROVIDER?.trim() || "openai-codex";
9108
9641
  if (!hasCliOption(piArgs, "--provider")) {
9109
9642
  piArgs.unshift(piProvider);
@@ -9116,6 +9649,12 @@ function buildProviderArgs(provider, runtime, argv) {
9116
9649
  piArgs.unshift(normalizePiModelForProvider(model, piProvider));
9117
9650
  piArgs.unshift("--model");
9118
9651
  }
9652
+ if (!hasCliOption(piArgs, "--mode")) {
9653
+ piArgs.push("--mode", process.env.RIG_PI_TRANSPORT?.trim() === "print" ? "json" : "rpc");
9654
+ if (process.env.RIG_PI_TRANSPORT?.trim() === "print" && !hasCliOption(piArgs, "--print")) {
9655
+ piArgs.push("--print");
9656
+ }
9657
+ }
9119
9658
  return piArgs;
9120
9659
  }
9121
9660
  return [
@@ -9213,23 +9752,44 @@ function normalizePiModelForProvider(model, provider) {
9213
9752
  }
9214
9753
  return model;
9215
9754
  }
9755
+ function resolveFromShellPath(binary) {
9756
+ const resolved = Bun.spawnSync(["sh", "-lc", `command -v ${binary}`], {
9757
+ stdout: "pipe",
9758
+ stderr: "ignore",
9759
+ stdin: "ignore",
9760
+ env: process.env
9761
+ });
9762
+ if (resolved.exitCode !== 0)
9763
+ return null;
9764
+ const path = resolved.stdout.toString().trim().split(/\r?\n/)[0]?.trim();
9765
+ return path || null;
9766
+ }
9767
+ function resolveBundledPiBinary() {
9768
+ try {
9769
+ const packageJson = requireFromRuntime.resolve("@earendil-works/pi-coding-agent/package.json");
9770
+ const binaryPath = resolve36(packageJson, "..", "dist", "cli.js");
9771
+ return existsSync34(binaryPath) ? binaryPath : null;
9772
+ } catch {
9773
+ return null;
9774
+ }
9775
+ }
9216
9776
  function providerBinary(provider) {
9217
9777
  if (provider === "codex") {
9218
- return Bun.which("codex") || "codex";
9778
+ return resolveFromShellPath("codex") || Bun.which("codex") || "codex";
9219
9779
  }
9220
9780
  if (provider === "pi") {
9221
- return Bun.which("pi") || "pi";
9781
+ return process.env.RIG_PI_BINARY?.trim() || resolveBundledPiBinary() || resolveFromShellPath("pi") || Bun.which("pi") || "pi";
9222
9782
  }
9223
9783
  try {
9224
9784
  return resolveClaudeBinaryPath();
9225
9785
  } catch {
9226
- return Bun.which("claude") || "claude";
9786
+ return resolveFromShellPath("claude") || Bun.which("claude") || "claude";
9227
9787
  }
9228
9788
  }
9229
9789
  function emitWrapperEvent(type, payload) {
9230
9790
  console.log(`__RIG_WRAPPER_EVENT__${JSON.stringify({ type, payload, at: new Date().toISOString() })}`);
9231
9791
  }
9232
- function sleep(ms) {
9792
+ function sleep2(ms) {
9233
9793
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
9234
9794
  }
9235
9795
  async function waitForDirtyBaselineReady(runtime, taskId) {
@@ -9244,11 +9804,11 @@ async function waitForDirtyBaselineReady(runtime, taskId) {
9244
9804
  workspaceDir: runtime.workspaceDir,
9245
9805
  readyFile
9246
9806
  });
9247
- while (!existsSync33(readyFile)) {
9807
+ while (!existsSync34(readyFile)) {
9248
9808
  if (Date.now() >= deadline) {
9249
9809
  throw new Error(`Timed out waiting for dirty baseline ready file: ${readyFile}`);
9250
9810
  }
9251
- await sleep(50);
9811
+ await sleep2(50);
9252
9812
  }
9253
9813
  emitWrapperEvent("runtime.baseline.completed", {
9254
9814
  runtimeId: runtime.id,
@@ -9312,9 +9872,9 @@ async function readPluginTaskStatus(projectRoot, taskId) {
9312
9872
  }
9313
9873
  }
9314
9874
  function recordRuntimeHandoff(hostProjectRoot, runtime, taskId, exitCode) {
9315
- const handoffDir = resolve35(hostProjectRoot, ".rig/runtime/handoffs");
9316
- mkdirSync19(handoffDir, { recursive: true });
9317
- const handoffPath = resolve35(handoffDir, `${taskId}-${Date.now()}.json`);
9875
+ const handoffDir = resolve36(hostProjectRoot, ".rig/runtime/handoffs");
9876
+ mkdirSync20(handoffDir, { recursive: true });
9877
+ const handoffPath = resolve36(handoffDir, `${taskId}-${Date.now()}.json`);
9318
9878
  const handoff = {
9319
9879
  taskId,
9320
9880
  runtimeId: runtime.id,
@@ -9380,13 +9940,13 @@ async function readTaskMetadata2(taskRoot, taskId) {
9380
9940
  async function readTaskConfigHints(taskRoot, taskId) {
9381
9941
  const runtimeContext = loadRuntimeContextFromEnv();
9382
9942
  const candidates = [
9383
- runtimeContext?.monorepoMainRoot ? resolve35(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
9384
- process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve35(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
9385
- resolve35(taskRoot, ".rig", "task-config.json"),
9386
- resolve35(taskRoot, "rig", "task-config.json")
9943
+ runtimeContext?.monorepoMainRoot ? resolve36(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
9944
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve36(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
9945
+ resolve36(taskRoot, ".rig", "task-config.json"),
9946
+ resolve36(taskRoot, "rig", "task-config.json")
9387
9947
  ].filter(Boolean);
9388
9948
  for (const configPath of candidates) {
9389
- if (!existsSync33(configPath)) {
9949
+ if (!existsSync34(configPath)) {
9390
9950
  continue;
9391
9951
  }
9392
9952
  try {