@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";
@@ -5785,6 +5786,21 @@ var GITHUB_KNOWN_HOSTS = [
5785
5786
  "github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk="
5786
5787
  ].join(`
5787
5788
  `);
5789
+ function resolveControlPlaneSourceRoot(projectRoot) {
5790
+ const candidates = [
5791
+ process.env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
5792
+ process.env.RIG_HOST_PROJECT_ROOT?.trim(),
5793
+ resolve25(import.meta.dir, "../../../../.."),
5794
+ projectRoot
5795
+ ].filter((value) => Boolean(value));
5796
+ for (const candidate of candidates) {
5797
+ const root = resolve25(candidate);
5798
+ if (existsSync24(resolve25(root, "packages/runtime/src/control-plane/pi-sessiond/bin.ts"))) {
5799
+ return root;
5800
+ }
5801
+ }
5802
+ return "";
5803
+ }
5788
5804
  async function runtimeEnv(projectRoot, runtime) {
5789
5805
  const bunBinaryPath = resolveBunBinaryPath();
5790
5806
  const bunDir = resolveBunInstallDir(bunBinaryPath);
@@ -5830,9 +5846,11 @@ async function runtimeEnv(projectRoot, runtime) {
5830
5846
  const runtimeRigGit = resolve25(runtime.binDir, runtimeRigGitFileName());
5831
5847
  const preferredShell = existsSync24(runtimeBash) ? runtimeBash : "/bin/bash";
5832
5848
  const nativeRuntimeLibraryPath = await materializeNativeRuntimeLibrary(runtime.binDir);
5849
+ const controlPlaneSourceRoot = resolveControlPlaneSourceRoot(projectRoot);
5833
5850
  const env = {
5834
5851
  PROJECT_RIG_ROOT: projectRoot,
5835
5852
  RIG_HOST_PROJECT_ROOT: projectRoot,
5853
+ ...controlPlaneSourceRoot ? { RIG_CONTROL_PLANE_SOURCE_ROOT: controlPlaneSourceRoot } : {},
5836
5854
  HOME: runtime.homeDir,
5837
5855
  TMPDIR: runtime.tmpDir,
5838
5856
  XDG_CACHE_HOME: runtime.cacheDir,
@@ -9151,7 +9169,165 @@ function formatJsonRpcError(error) {
9151
9169
  return parts.join(" ");
9152
9170
  }
9153
9171
 
9172
+ // packages/runtime/src/control-plane/pi-sessiond/launcher.ts
9173
+ import { randomBytes } from "crypto";
9174
+ import { existsSync as existsSync33, mkdirSync as mkdirSync19, readFileSync as readFileSync16, rmSync as rmSync13 } from "fs";
9175
+ import { dirname as dirname14, resolve as resolve35 } from "path";
9176
+ import { fileURLToPath as fileURLToPath2 } from "url";
9177
+
9178
+ // packages/runtime/src/control-plane/pi-sessiond/client.ts
9179
+ class RigPiSessionDaemonClient {
9180
+ baseUrl;
9181
+ token;
9182
+ constructor(options) {
9183
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
9184
+ this.token = options.token;
9185
+ }
9186
+ static fromConnection(connection, token) {
9187
+ if (connection.mode === "http")
9188
+ return new RigPiSessionDaemonClient({ baseUrl: connection.baseUrl, token });
9189
+ throw new Error("Unix-socket Rig Pi daemon connections are not implemented in this build; use loopback HTTP.");
9190
+ }
9191
+ async request(method, path, body) {
9192
+ const response = await fetch(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`, {
9193
+ method,
9194
+ headers: {
9195
+ authorization: `Bearer ${this.token}`,
9196
+ ...body === undefined ? {} : { "content-type": "application/json" }
9197
+ },
9198
+ body: body === undefined ? undefined : JSON.stringify(body)
9199
+ });
9200
+ const text = await response.text();
9201
+ const payload = text.trim() ? JSON.parse(text) : undefined;
9202
+ if (!response.ok) {
9203
+ const message = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.error === "string" ? payload.error : text || response.statusText;
9204
+ throw new Error(`Rig Pi session daemon request failed (${response.status}): ${message}`);
9205
+ }
9206
+ return payload;
9207
+ }
9208
+ webSocketUrl(path) {
9209
+ const url = new URL(`${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`);
9210
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
9211
+ url.searchParams.set("token", this.token);
9212
+ return url.toString();
9213
+ }
9214
+ }
9215
+
9216
+ // packages/runtime/src/control-plane/pi-sessiond/launcher.ts
9217
+ var BUILD_CONFIG2 = {};
9218
+ var BAKED_RIG_SOURCE_ROOT = BUILD_CONFIG2.RIG_SOURCE_ROOT ?? "";
9219
+ async function ensureRigPiSessionDaemon(input) {
9220
+ const rootDir = resolve35(input.rootDir);
9221
+ mkdirSync19(rootDir, { recursive: true });
9222
+ const readyFile = resolve35(rootDir, "ready.json");
9223
+ const existing = readDaemonReadyFile(readyFile);
9224
+ const existingHandle = existing ? await tryReady(existing) : null;
9225
+ if (existingHandle)
9226
+ return existingHandle;
9227
+ try {
9228
+ rmSync13(readyFile, { force: true });
9229
+ } catch {}
9230
+ const token = randomBytes(32).toString("hex");
9231
+ const binPath = resolveRigPiSessionDaemonBinPath(input.env);
9232
+ const bunPath = input.env.RIG_BUN_PATH || process.execPath;
9233
+ const proc = Bun.spawn([bunPath, binPath], {
9234
+ cwd: rootDir,
9235
+ env: {
9236
+ ...input.env,
9237
+ RIG_PI_SESSIOND_ROOT: rootDir,
9238
+ RIG_PI_SESSIOND_TOKEN: token,
9239
+ RIG_PI_SESSIOND_READY_FILE: readyFile,
9240
+ RIG_PI_SESSIOND_HOST: "127.0.0.1",
9241
+ RIG_PI_SESSIOND_PORT: "0",
9242
+ ...input.version ? { RIG_VERSION: input.version } : {},
9243
+ ...input.commit ? { RIG_GIT_COMMIT: input.commit } : {}
9244
+ },
9245
+ stdin: "ignore",
9246
+ stdout: "ignore",
9247
+ stderr: "inherit"
9248
+ });
9249
+ proc.unref();
9250
+ const deadline = Date.now() + (input.timeoutMs ?? 15000);
9251
+ while (Date.now() < deadline) {
9252
+ const ready = readDaemonReadyFile(readyFile);
9253
+ const handle = ready ? await tryReady(ready) : null;
9254
+ if (handle)
9255
+ return handle;
9256
+ await sleep(100);
9257
+ }
9258
+ throw new Error(`Rig Pi session daemon did not become ready at ${readyFile}`);
9259
+ }
9260
+ function privateMetadataForDaemon(input) {
9261
+ return { public: input.publicMetadata, daemonConnection: input.connection };
9262
+ }
9263
+ async function tryReady(ready) {
9264
+ const host = typeof ready.host === "string" ? ready.host : "127.0.0.1";
9265
+ const port = typeof ready.port === "number" ? ready.port : Number(ready.port);
9266
+ const token = typeof ready.token === "string" ? ready.token : "";
9267
+ if (!Number.isFinite(port) || port <= 0 || !token)
9268
+ return null;
9269
+ const baseUrl = `http://${host}:${port}`;
9270
+ const client = new RigPiSessionDaemonClient({ baseUrl, token });
9271
+ try {
9272
+ await client.request("GET", "/health");
9273
+ } catch {
9274
+ return null;
9275
+ }
9276
+ return {
9277
+ client,
9278
+ connection: { mode: "http", baseUrl, tokenRef: tokenRefFromReady(ready) },
9279
+ token,
9280
+ ready
9281
+ };
9282
+ }
9283
+ function tokenRefFromReady(ready) {
9284
+ const token = typeof ready.token === "string" ? ready.token : "";
9285
+ return token ? `inline:${token}` : "missing";
9286
+ }
9287
+ function resolveRigPiSessionDaemonBinPath(env) {
9288
+ const explicit = env.RIG_PI_SESSIOND_BIN?.trim();
9289
+ if (explicit)
9290
+ return explicit;
9291
+ const roots = [
9292
+ env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
9293
+ BAKED_RIG_SOURCE_ROOT.trim(),
9294
+ process.env.RIG_CONTROL_PLANE_SOURCE_ROOT?.trim(),
9295
+ process.env.RIG_HOST_PROJECT_ROOT?.trim(),
9296
+ process.env.PROJECT_RIG_ROOT?.trim()
9297
+ ].filter((value) => Boolean(value));
9298
+ for (const root of roots) {
9299
+ const candidate = resolve35(root, "packages/runtime/src/control-plane/pi-sessiond/bin.ts");
9300
+ if (existsSync33(candidate))
9301
+ return candidate;
9302
+ }
9303
+ const moduleCandidate = fileURLToPath2(new URL("./bin.ts", import.meta.url));
9304
+ if (existsSync33(moduleCandidate))
9305
+ return moduleCandidate;
9306
+ 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.");
9307
+ }
9308
+ function readDaemonReadyFile(path) {
9309
+ if (!existsSync33(path))
9310
+ return null;
9311
+ try {
9312
+ const parsed = JSON.parse(readFileSync16(path, "utf8"));
9313
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
9314
+ } catch {
9315
+ return null;
9316
+ }
9317
+ }
9318
+ function sleep(ms) {
9319
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
9320
+ }
9321
+ function resolveRigPiSessionDaemonRoot(stateDir) {
9322
+ const root = resolve35(stateDir, "pi-sessiond");
9323
+ mkdirSync19(dirname14(root), { recursive: true });
9324
+ if (!existsSync33(root))
9325
+ mkdirSync19(root, { recursive: true });
9326
+ return root;
9327
+ }
9328
+
9154
9329
  // packages/runtime/src/control-plane/agent-wrapper.ts
9330
+ var requireFromRuntime = createRequire(import.meta.url);
9155
9331
  async function finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, context) {
9156
9332
  try {
9157
9333
  await snapshotSidecar.finalize(providerCommand, exitCode);
@@ -9184,7 +9360,7 @@ async function startOptionalRuntimeSnapshotSidecar(runtime, startSidecar = start
9184
9360
  }
9185
9361
  }
9186
9362
  async function runAgentWrapper(options = {}) {
9187
- const projectRoot = resolve35(options.projectRoot || process.env.PROJECT_RIG_ROOT || process.cwd());
9363
+ const projectRoot = resolve36(options.projectRoot || process.env.PROJECT_RIG_ROOT || process.cwd());
9188
9364
  const monorepoRoot = resolveMonorepoRoot2(projectRoot);
9189
9365
  const argv = options.argv || process.argv.slice(2);
9190
9366
  if (argv.length === 0 || argv[0] === "--version" || argv[0] === "--help" || argv[0] === "help") {
@@ -9247,7 +9423,8 @@ async function runAgentWrapper(options = {}) {
9247
9423
  return 1;
9248
9424
  }
9249
9425
  const providerArgs = buildProviderArgs(provider, runtime, argv);
9250
- const providerCommand = [providerBinary(provider), ...providerArgs];
9426
+ const normalPiDaemonPath = provider === "pi" && process.env.RIG_PI_RPC_FALLBACK !== "1";
9427
+ const providerCommand = normalPiDaemonPath ? ["rig-pi-sessiond", ...providerArgs] : [providerBinary(provider), ...providerArgs];
9251
9428
  emitWrapperEvent("provider.launch", {
9252
9429
  provider,
9253
9430
  runtimeId: runtime.id,
@@ -9259,11 +9436,11 @@ async function runAgentWrapper(options = {}) {
9259
9436
  const bypassOuterRuntimeSandbox = shouldBypassProviderSandboxOnPlatform(provider, process.platform);
9260
9437
  const runClaudeCompatUnsandboxed = provider === "claude-code" && bypassOuterRuntimeSandbox;
9261
9438
  if (runClaudeCompatUnsandboxed && process.env.HOME?.trim()) {
9262
- env.CLAUDE_HOME = resolve35(process.env.HOME.trim(), ".claude");
9439
+ env.CLAUDE_HOME = resolve36(process.env.HOME.trim(), ".claude");
9263
9440
  env.RIG_CLAUDE_RUNTIME_HOME = runtime.claudeHomeDir;
9264
9441
  }
9265
9442
  if (provider === "pi") {
9266
- env.PI_CODING_AGENT_DIR = resolve35(runtime.homeDir, ".pi", "agent");
9443
+ env.PI_CODING_AGENT_DIR = resolve36(runtime.homeDir, ".pi", "agent");
9267
9444
  env.PI_CODING_AGENT_SESSION_DIR = runtime.sessionDir;
9268
9445
  }
9269
9446
  env.RIG_RUNTIME_SANDBOX = "enforce";
@@ -9297,14 +9474,36 @@ async function runAgentWrapper(options = {}) {
9297
9474
  },
9298
9475
  command: providerCommand
9299
9476
  })).command;
9300
- const proc = Bun.spawn(command, {
9301
- cwd: runtime.workspaceDir,
9302
- env,
9303
- stdin: "inherit",
9304
- stdout: "inherit",
9305
- stderr: "inherit"
9306
- });
9307
- exitCode = await proc.exited;
9477
+ if (provider === "pi" && process.env.RIG_PI_RPC_FALLBACK !== "1") {
9478
+ const prompt = await readProcessStdin();
9479
+ exitCode = await runPiSessionDaemonProvider({
9480
+ projectRoot,
9481
+ runtime,
9482
+ env,
9483
+ prompt,
9484
+ runId: process.env.RIG_SERVER_RUN_ID?.trim() || process.env.RIG_RUN_ID?.trim() || undefined,
9485
+ sessionName: process.env.RIG_SERVER_RUN_ID?.trim() ? `Rig ${process.env.RIG_SERVER_RUN_ID.trim()}` : `Rig ${runtime.taskId}`
9486
+ });
9487
+ } else if (provider === "pi" && isPiRpcArgs(providerArgs)) {
9488
+ const prompt = await readProcessStdin();
9489
+ exitCode = await runPiRpcProviderFallback({
9490
+ command,
9491
+ cwd: runtime.workspaceDir,
9492
+ env,
9493
+ prompt,
9494
+ runId: process.env.RIG_SERVER_RUN_ID?.trim() || undefined,
9495
+ sessionName: process.env.RIG_SERVER_RUN_ID?.trim() ? `Rig ${process.env.RIG_SERVER_RUN_ID.trim()}` : `Rig ${runtime.taskId}`
9496
+ });
9497
+ } else {
9498
+ const proc = Bun.spawn(command, {
9499
+ cwd: runtime.workspaceDir,
9500
+ env,
9501
+ stdin: "inherit",
9502
+ stdout: "inherit",
9503
+ stderr: "inherit"
9504
+ });
9505
+ exitCode = await proc.exited;
9506
+ }
9308
9507
  }
9309
9508
  if (snapshotSidecar) {
9310
9509
  await finalizeRuntimeSnapshot(snapshotSidecar, providerCommand, exitCode, {
@@ -9341,6 +9540,338 @@ async function runAgentWrapper(options = {}) {
9341
9540
  }
9342
9541
  return finalExitCode;
9343
9542
  }
9543
+ function parseJsonRecord(line) {
9544
+ try {
9545
+ const parsed = JSON.parse(line);
9546
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
9547
+ } catch {
9548
+ return null;
9549
+ }
9550
+ }
9551
+ async function readProcessStdin() {
9552
+ if (process.stdin.isTTY)
9553
+ return "";
9554
+ return await new Promise((resolveRead) => {
9555
+ let data = "";
9556
+ process.stdin.setEncoding("utf8");
9557
+ process.stdin.on("data", (chunk) => {
9558
+ data += String(chunk);
9559
+ });
9560
+ process.stdin.on("end", () => resolveRead(data));
9561
+ process.stdin.resume();
9562
+ });
9563
+ }
9564
+ async function pumpReadableLines(stream, onLine) {
9565
+ if (!stream)
9566
+ return;
9567
+ const reader = stream.getReader();
9568
+ const decoder = new TextDecoder;
9569
+ let buffer = "";
9570
+ try {
9571
+ while (true) {
9572
+ const { done, value } = await reader.read();
9573
+ if (done)
9574
+ break;
9575
+ buffer += decoder.decode(value, { stream: true });
9576
+ const parts = buffer.split(/\r?\n/);
9577
+ buffer = parts.pop() ?? "";
9578
+ for (const part of parts)
9579
+ onLine(part);
9580
+ }
9581
+ buffer += decoder.decode();
9582
+ if (buffer)
9583
+ onLine(buffer);
9584
+ } finally {
9585
+ reader.releaseLock();
9586
+ }
9587
+ }
9588
+ function isBlockingPiRpcUiRequest(record) {
9589
+ if (record.type !== "extension_ui_request")
9590
+ return false;
9591
+ return record.method === "select" || record.method === "confirm" || record.method === "input" || record.method === "editor";
9592
+ }
9593
+ function writeRpcCommand(stdin, command) {
9594
+ stdin.write(`${JSON.stringify(command)}
9595
+ `);
9596
+ }
9597
+ function joinUrl(baseUrl, pathname) {
9598
+ return `${baseUrl.replace(/\/+$/, "")}${pathname.startsWith("/") ? pathname : `/${pathname}`}`;
9599
+ }
9600
+ async function readQueuedSteeringFromServer(input) {
9601
+ if (!input.serverUrl || !input.runId)
9602
+ return [];
9603
+ const headers = {};
9604
+ if (input.authToken)
9605
+ headers.authorization = `Bearer ${input.authToken}`;
9606
+ const response = await fetch(joinUrl(input.serverUrl, `/api/runs/${encodeURIComponent(input.runId)}/steering?ack=1`), { headers });
9607
+ if (!response.ok)
9608
+ return [];
9609
+ const payload = await response.json().catch(() => null);
9610
+ const messages = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.messages : null;
9611
+ return Array.isArray(messages) ? messages.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
9612
+ }
9613
+ function steeringMessageText(entry) {
9614
+ const message = typeof entry.message === "string" ? entry.message.trim() : "";
9615
+ return message || null;
9616
+ }
9617
+ function isPiRpcArgs(args) {
9618
+ return cliOptionValue(args, "--mode") === "rpc";
9619
+ }
9620
+ async function runPiSessionDaemonProvider(input) {
9621
+ const stdout = input.stdout ?? process.stdout;
9622
+ const stderr = input.stderr ?? process.stderr;
9623
+ const runId = input.runId ?? input.runtime.taskId;
9624
+ emitWrapperEvent("pi.sessiond.starting", {
9625
+ runId,
9626
+ runtimeId: input.runtime.id,
9627
+ workspaceDir: input.runtime.workspaceDir
9628
+ });
9629
+ const daemon = await ensureRigPiSessionDaemon({
9630
+ rootDir: resolveRigPiSessionDaemonRoot(input.runtime.stateDir),
9631
+ env: input.env,
9632
+ version: process.env.RIG_VERSION?.trim() || "dev",
9633
+ commit: process.env.RIG_GIT_COMMIT?.trim() || undefined
9634
+ });
9635
+ emitWrapperEvent("pi.sessiond.ready", {
9636
+ runId,
9637
+ runtimeId: input.runtime.id,
9638
+ connection: daemon.connection,
9639
+ ready: daemon.ready
9640
+ });
9641
+ const start = await daemon.client.request("POST", "/sessions", {
9642
+ runId,
9643
+ cwd: input.runtime.workspaceDir,
9644
+ agentDir: input.env.PI_CODING_AGENT_DIR || resolve36(input.runtime.homeDir, ".pi", "agent"),
9645
+ sessionDir: input.runtime.sessionDir,
9646
+ sessionName: input.sessionName
9647
+ });
9648
+ const privateMetadata = privateMetadataForDaemon({ publicMetadata: start.metadata, connection: daemon.connection });
9649
+ emitWrapperEvent("pi.session.ready", {
9650
+ runId,
9651
+ runtimeId: input.runtime.id,
9652
+ metadata: start.metadata,
9653
+ privateMetadata
9654
+ });
9655
+ const eventStream = waitForPiSessionEvents({
9656
+ url: daemon.client.webSocketUrl(`/sessions/${encodeURIComponent(start.metadata.sessionId)}/events`),
9657
+ stdout,
9658
+ stderr,
9659
+ runId
9660
+ });
9661
+ emitWrapperEvent("pi.session.event_stream.connected", { runId, sessionId: start.metadata.sessionId });
9662
+ const forwardSigterm = () => {
9663
+ daemon.client.request("POST", `/sessions/${encodeURIComponent(start.metadata.sessionId)}/abort`).catch(() => {
9664
+ return;
9665
+ });
9666
+ eventStream.close();
9667
+ };
9668
+ process.once("SIGTERM", forwardSigterm);
9669
+ try {
9670
+ if (input.prompt.trim()) {
9671
+ await daemon.client.request("POST", `/sessions/${encodeURIComponent(start.metadata.sessionId)}/prompt`, { text: input.prompt });
9672
+ emitWrapperEvent("pi.prompt.sent", { runId, sessionId: start.metadata.sessionId, bytes: Buffer.byteLength(input.prompt) });
9673
+ } else {
9674
+ emitWrapperEvent("pi.prompt.waiting", { runId, sessionId: start.metadata.sessionId, reason: "empty-initial-prompt" });
9675
+ }
9676
+ const result = await eventStream.done;
9677
+ if (result.error) {
9678
+ stderr.write(`[rig-agent] Pi session daemon stream failed: ${result.error}
9679
+ `);
9680
+ return 1;
9681
+ }
9682
+ return 0;
9683
+ } finally {
9684
+ process.off("SIGTERM", forwardSigterm);
9685
+ eventStream.close();
9686
+ }
9687
+ }
9688
+ function waitForPiSessionEvents(input) {
9689
+ let closed = false;
9690
+ let resolved = false;
9691
+ let socket = null;
9692
+ let resolveDone = () => {
9693
+ return;
9694
+ };
9695
+ const done = new Promise((resolveDoneInner) => {
9696
+ resolveDone = resolveDoneInner;
9697
+ });
9698
+ const finish = (value) => {
9699
+ if (resolved)
9700
+ return;
9701
+ resolved = true;
9702
+ resolveDone(value);
9703
+ };
9704
+ socket = new WebSocket(input.url);
9705
+ socket.addEventListener("message", (message) => {
9706
+ const text = typeof message.data === "string" ? message.data : Buffer.from(message.data).toString("utf8");
9707
+ input.stdout.write(`${text}
9708
+ `);
9709
+ const envelope = parseJsonRecord(text);
9710
+ if (!envelope)
9711
+ return;
9712
+ if (envelope.type === "pi.event") {
9713
+ const event = envelope.event && typeof envelope.event === "object" && !Array.isArray(envelope.event) ? envelope.event : null;
9714
+ if (event?.type === "agent_end") {
9715
+ emitWrapperEvent("pi.session.agent_end", { runId: input.runId, sessionId: envelope.sessionId });
9716
+ finish({});
9717
+ }
9718
+ }
9719
+ if (envelope.type === "error") {
9720
+ emitWrapperEvent("pi.session.error", { runId: input.runId, message: envelope.message, detail: envelope.detail ?? null });
9721
+ finish({ error: envelope.message });
9722
+ }
9723
+ });
9724
+ socket.addEventListener("error", () => {
9725
+ if (!closed)
9726
+ finish({ error: "WebSocket error" });
9727
+ });
9728
+ socket.addEventListener("close", () => {
9729
+ if (!closed && !resolved)
9730
+ finish({ error: "WebSocket closed before agent_end" });
9731
+ });
9732
+ return {
9733
+ done,
9734
+ close: () => {
9735
+ closed = true;
9736
+ try {
9737
+ socket?.close();
9738
+ } catch {}
9739
+ }
9740
+ };
9741
+ }
9742
+ async function runPiRpcProviderFallback(input) {
9743
+ const stdout = input.stdout ?? process.stdout;
9744
+ const stderr = input.stderr ?? process.stderr;
9745
+ const proc = Bun.spawn(input.command, {
9746
+ cwd: input.cwd,
9747
+ env: input.env,
9748
+ stdin: "pipe",
9749
+ stdout: "pipe",
9750
+ stderr: "pipe"
9751
+ });
9752
+ let sawAgentEnd = false;
9753
+ let promptError = null;
9754
+ let stdinOpen = true;
9755
+ let steeringPollStopped = false;
9756
+ const closeStdin = () => {
9757
+ if (!stdinOpen)
9758
+ return;
9759
+ stdinOpen = false;
9760
+ try {
9761
+ steeringPollStopped = true;
9762
+ proc.stdin.end();
9763
+ } catch {}
9764
+ };
9765
+ const send = (command) => {
9766
+ if (!stdinOpen)
9767
+ return;
9768
+ try {
9769
+ writeRpcCommand(proc.stdin, command);
9770
+ } catch (error) {
9771
+ promptError ??= error instanceof Error ? error.message : String(error);
9772
+ }
9773
+ };
9774
+ const forwardSigterm = () => {
9775
+ try {
9776
+ proc.kill("SIGTERM");
9777
+ } catch {}
9778
+ };
9779
+ process.once("SIGTERM", forwardSigterm);
9780
+ const pollSteering = async () => {
9781
+ const serverUrl = input.env.RIG_SERVER_URL || input.env.RIG_SERVER_BASE_URL;
9782
+ const authToken = input.env.RIG_AUTH_TOKEN || input.env.RIG_SERVER_AUTH_TOKEN;
9783
+ while (!steeringPollStopped && stdinOpen) {
9784
+ try {
9785
+ const messages = await readQueuedSteeringFromServer({ serverUrl, authToken, runId: input.runId });
9786
+ for (const message of messages) {
9787
+ const text = steeringMessageText(message);
9788
+ if (!text || !stdinOpen)
9789
+ continue;
9790
+ send({
9791
+ id: typeof message.id === "string" ? `rig_steer_${message.id}` : `rig_steer_${Date.now()}`,
9792
+ type: "prompt",
9793
+ message: text,
9794
+ streamingBehavior: "steer"
9795
+ });
9796
+ emitWrapperEvent("pi.rpc.steering.delivered", {
9797
+ runId: input.runId ?? null,
9798
+ steeringId: typeof message.id === "string" ? message.id : null,
9799
+ actor: typeof message.actor === "string" ? message.actor : "operator",
9800
+ message: text
9801
+ });
9802
+ }
9803
+ } catch (error) {
9804
+ emitWrapperEvent("pi.rpc.steering.poll.failed", {
9805
+ runId: input.runId ?? null,
9806
+ error: error instanceof Error ? error.message : String(error)
9807
+ });
9808
+ }
9809
+ await sleep2(1000);
9810
+ }
9811
+ };
9812
+ const stdoutPump = pumpReadableLines(proc.stdout, (line) => {
9813
+ stdout.write(`${line}
9814
+ `);
9815
+ const record = parseJsonRecord(line.trim());
9816
+ if (!record)
9817
+ return;
9818
+ if (record.type === "agent_end") {
9819
+ sawAgentEnd = true;
9820
+ closeStdin();
9821
+ return;
9822
+ }
9823
+ if (record.type === "response" && record.command === "prompt" && record.success === false) {
9824
+ promptError = typeof record.error === "string" ? record.error : "Pi RPC prompt failed.";
9825
+ closeStdin();
9826
+ return;
9827
+ }
9828
+ if (isBlockingPiRpcUiRequest(record)) {
9829
+ const id = typeof record.id === "string" ? record.id : "";
9830
+ if (id) {
9831
+ send({ type: "extension_ui_response", id, cancelled: true });
9832
+ emitWrapperEvent("pi.rpc.extension_ui.cancelled", {
9833
+ id,
9834
+ method: record.method,
9835
+ reason: "noninteractive Rig worker RPC session"
9836
+ });
9837
+ }
9838
+ }
9839
+ });
9840
+ const stderrPump = pumpReadableLines(proc.stderr, (line) => {
9841
+ stderr.write(`${line}
9842
+ `);
9843
+ });
9844
+ if (input.sessionName?.trim()) {
9845
+ send({ id: "rig_set_session_name", type: "set_session_name", name: input.sessionName.trim() });
9846
+ }
9847
+ const steeringPollPromise = pollSteering();
9848
+ if (input.prompt.trim()) {
9849
+ send({ id: "rig_initial_prompt", type: "prompt", message: input.prompt });
9850
+ emitWrapperEvent("pi.rpc.prompt.sent", {
9851
+ runId: input.runId ?? null,
9852
+ kind: "initial",
9853
+ bytes: Buffer.byteLength(input.prompt)
9854
+ });
9855
+ } else {
9856
+ closeStdin();
9857
+ }
9858
+ const exitCode = await proc.exited;
9859
+ process.off("SIGTERM", forwardSigterm);
9860
+ steeringPollStopped = true;
9861
+ await Promise.all([stdoutPump, stderrPump, steeringPollPromise]);
9862
+ if (promptError) {
9863
+ stderr.write(`[rig-agent] Pi RPC prompt failed: ${promptError}
9864
+ `);
9865
+ return exitCode === 0 ? 1 : exitCode;
9866
+ }
9867
+ if (input.prompt.trim() && !sawAgentEnd && exitCode === 0) {
9868
+ stderr.write(`[rig-agent] Pi RPC exited before emitting agent_end.
9869
+ `);
9870
+ return 1;
9871
+ }
9872
+ return exitCode;
9873
+ }
9874
+ var runPiRpcProvider = runPiRpcProviderFallback;
9344
9875
  function resolveFinalProviderExitCode(input) {
9345
9876
  if (input.providerExitCode !== 0) {
9346
9877
  return input.providerExitCode;
@@ -9372,6 +9903,9 @@ function buildProviderArgs(provider, runtime, argv) {
9372
9903
  }
9373
9904
  if (provider === "pi") {
9374
9905
  const piArgs = [...argv];
9906
+ if (piArgs.includes("__rig_pi_session_daemon__")) {
9907
+ return piArgs.filter((arg) => arg !== "__rig_pi_session_daemon__");
9908
+ }
9375
9909
  const piProvider = cliOptionValue(piArgs, "--provider") || process.env.RIG_PI_PROVIDER?.trim() || "openai-codex";
9376
9910
  if (!hasCliOption(piArgs, "--provider")) {
9377
9911
  piArgs.unshift(piProvider);
@@ -9384,6 +9918,12 @@ function buildProviderArgs(provider, runtime, argv) {
9384
9918
  piArgs.unshift(normalizePiModelForProvider(model, piProvider));
9385
9919
  piArgs.unshift("--model");
9386
9920
  }
9921
+ if (!hasCliOption(piArgs, "--mode")) {
9922
+ piArgs.push("--mode", process.env.RIG_PI_TRANSPORT?.trim() === "print" ? "json" : "rpc");
9923
+ if (process.env.RIG_PI_TRANSPORT?.trim() === "print" && !hasCliOption(piArgs, "--print")) {
9924
+ piArgs.push("--print");
9925
+ }
9926
+ }
9387
9927
  return piArgs;
9388
9928
  }
9389
9929
  return [
@@ -9481,23 +10021,44 @@ function normalizePiModelForProvider(model, provider) {
9481
10021
  }
9482
10022
  return model;
9483
10023
  }
10024
+ function resolveFromShellPath(binary) {
10025
+ const resolved = Bun.spawnSync(["sh", "-lc", `command -v ${binary}`], {
10026
+ stdout: "pipe",
10027
+ stderr: "ignore",
10028
+ stdin: "ignore",
10029
+ env: process.env
10030
+ });
10031
+ if (resolved.exitCode !== 0)
10032
+ return null;
10033
+ const path = resolved.stdout.toString().trim().split(/\r?\n/)[0]?.trim();
10034
+ return path || null;
10035
+ }
10036
+ function resolveBundledPiBinary() {
10037
+ try {
10038
+ const packageJson = requireFromRuntime.resolve("@earendil-works/pi-coding-agent/package.json");
10039
+ const binaryPath = resolve36(packageJson, "..", "dist", "cli.js");
10040
+ return existsSync34(binaryPath) ? binaryPath : null;
10041
+ } catch {
10042
+ return null;
10043
+ }
10044
+ }
9484
10045
  function providerBinary(provider) {
9485
10046
  if (provider === "codex") {
9486
- return Bun.which("codex") || "codex";
10047
+ return resolveFromShellPath("codex") || Bun.which("codex") || "codex";
9487
10048
  }
9488
10049
  if (provider === "pi") {
9489
- return Bun.which("pi") || "pi";
10050
+ return process.env.RIG_PI_BINARY?.trim() || resolveBundledPiBinary() || resolveFromShellPath("pi") || Bun.which("pi") || "pi";
9490
10051
  }
9491
10052
  try {
9492
10053
  return resolveClaudeBinaryPath();
9493
10054
  } catch {
9494
- return Bun.which("claude") || "claude";
10055
+ return resolveFromShellPath("claude") || Bun.which("claude") || "claude";
9495
10056
  }
9496
10057
  }
9497
10058
  function emitWrapperEvent(type, payload) {
9498
10059
  console.log(`__RIG_WRAPPER_EVENT__${JSON.stringify({ type, payload, at: new Date().toISOString() })}`);
9499
10060
  }
9500
- function sleep(ms) {
10061
+ function sleep2(ms) {
9501
10062
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
9502
10063
  }
9503
10064
  async function waitForDirtyBaselineReady(runtime, taskId) {
@@ -9512,11 +10073,11 @@ async function waitForDirtyBaselineReady(runtime, taskId) {
9512
10073
  workspaceDir: runtime.workspaceDir,
9513
10074
  readyFile
9514
10075
  });
9515
- while (!existsSync33(readyFile)) {
10076
+ while (!existsSync34(readyFile)) {
9516
10077
  if (Date.now() >= deadline) {
9517
10078
  throw new Error(`Timed out waiting for dirty baseline ready file: ${readyFile}`);
9518
10079
  }
9519
- await sleep(50);
10080
+ await sleep2(50);
9520
10081
  }
9521
10082
  emitWrapperEvent("runtime.baseline.completed", {
9522
10083
  runtimeId: runtime.id,
@@ -9615,9 +10176,9 @@ async function updateTaskSourceAfterRun(projectRoot, taskId, runtime) {
9615
10176
  }
9616
10177
  }
9617
10178
  function recordRuntimeHandoff(hostProjectRoot, runtime, taskId, exitCode) {
9618
- const handoffDir = resolve35(hostProjectRoot, ".rig/runtime/handoffs");
9619
- mkdirSync19(handoffDir, { recursive: true });
9620
- const handoffPath = resolve35(handoffDir, `${taskId}-${Date.now()}.json`);
10179
+ const handoffDir = resolve36(hostProjectRoot, ".rig/runtime/handoffs");
10180
+ mkdirSync20(handoffDir, { recursive: true });
10181
+ const handoffPath = resolve36(handoffDir, `${taskId}-${Date.now()}.json`);
9621
10182
  const handoff = {
9622
10183
  taskId,
9623
10184
  runtimeId: runtime.id,
@@ -9683,13 +10244,13 @@ async function readTaskMetadata2(taskRoot, taskId) {
9683
10244
  async function readTaskConfigHints(taskRoot, taskId) {
9684
10245
  const runtimeContext = loadRuntimeContextFromEnv();
9685
10246
  const candidates = [
9686
- runtimeContext?.monorepoMainRoot ? resolve35(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
9687
- process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve35(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
9688
- resolve35(taskRoot, ".rig", "task-config.json"),
9689
- resolve35(taskRoot, "rig", "task-config.json")
10247
+ runtimeContext?.monorepoMainRoot ? resolve36(runtimeContext.monorepoMainRoot, ".rig", "task-config.json") : "",
10248
+ process.env.MONOREPO_MAIN_ROOT?.trim() ? resolve36(process.env.MONOREPO_MAIN_ROOT.trim(), ".rig", "task-config.json") : "",
10249
+ resolve36(taskRoot, ".rig", "task-config.json"),
10250
+ resolve36(taskRoot, "rig", "task-config.json")
9690
10251
  ].filter(Boolean);
9691
10252
  for (const configPath of candidates) {
9692
- if (!existsSync33(configPath)) {
10253
+ if (!existsSync34(configPath)) {
9693
10254
  continue;
9694
10255
  }
9695
10256
  try {
@@ -9726,6 +10287,9 @@ export {
9726
10287
  updateTaskSourceAfterRun,
9727
10288
  startOptionalRuntimeSnapshotSidecar,
9728
10289
  shouldBypassProviderSandboxOnPlatform,
10290
+ runPiSessionDaemonProvider,
10291
+ runPiRpcProviderFallback,
10292
+ runPiRpcProvider,
9729
10293
  runAgentWrapper,
9730
10294
  resolveTaskFromBeads,
9731
10295
  resolveFinalProviderExitCode,