@h-rig/runtime 0.0.6-alpha.0 → 0.0.6-alpha.10

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 (42) hide show
  1. package/dist/bin/rig-agent-dispatch.js +133 -14
  2. package/dist/bin/rig-agent.js +83 -26
  3. package/dist/src/control-plane/agent-wrapper.js +133 -14
  4. package/dist/src/control-plane/harness-main.js +83 -26
  5. package/dist/src/control-plane/hooks/completion-verification.js +85 -28
  6. package/dist/src/control-plane/hooks/inject-context.js +2 -2
  7. package/dist/src/control-plane/hooks/submodule-branch.js +26 -3
  8. package/dist/src/control-plane/hooks/task-runtime-start.js +26 -3
  9. package/dist/src/control-plane/native/git-ops.js +81 -24
  10. package/dist/src/control-plane/native/harness-cli.js +83 -26
  11. package/dist/src/control-plane/native/pr-automation.js +88 -17
  12. package/dist/src/control-plane/native/run-ops.js +23 -6
  13. package/dist/src/control-plane/native/task-ops.js +2 -2
  14. package/dist/src/control-plane/native/validator.js +2 -2
  15. package/dist/src/control-plane/native/verifier.js +2 -2
  16. package/dist/src/control-plane/runtime/index.js +38 -9
  17. package/dist/src/control-plane/runtime/isolation/home.js +31 -6
  18. package/dist/src/control-plane/runtime/isolation/index.js +38 -9
  19. package/dist/src/control-plane/runtime/isolation/runner.js +31 -6
  20. package/dist/src/control-plane/runtime/isolation/shared.js +9 -6
  21. package/dist/src/control-plane/runtime/isolation.js +38 -9
  22. package/dist/src/control-plane/runtime/queue.js +38 -9
  23. package/dist/src/control-plane/tasks/source-aware-task-config-source.js +14 -2
  24. package/dist/src/control-plane/tasks/source-lifecycle.js +2 -2
  25. package/dist/src/index.js +15 -13
  26. package/dist/src/local-server.js +20 -14
  27. package/native/darwin-arm64/{bin/rig-git → rig-git} +0 -0
  28. package/native/darwin-arm64/rig-git.build-manifest.json +4 -0
  29. package/native/darwin-arm64/{bin/rig-shell → rig-shell} +0 -0
  30. package/native/darwin-arm64/rig-shell.build-manifest.json +4 -0
  31. package/native/darwin-arm64/{bin/rig-tools → rig-tools} +0 -0
  32. package/native/darwin-arm64/rig-tools.build-manifest.json +4 -0
  33. package/native/darwin-arm64/{lib/runtime-native.dylib → runtime-native.dylib} +0 -0
  34. package/package.json +8 -7
  35. package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
  36. package/native/darwin-arm64/manifest.json +0 -1
  37. package/native/linux-x64/bin/rig-git +0 -0
  38. package/native/linux-x64/bin/rig-shell +0 -0
  39. package/native/linux-x64/bin/rig-tools +0 -0
  40. package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
  41. package/native/linux-x64/lib/runtime-native.so +0 -0
  42. package/native/linux-x64/manifest.json +0 -1
@@ -5,7 +5,7 @@
5
5
  import { appendFileSync as appendFileSync2, existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
6
6
  import { resolve as resolve24 } from "path";
7
7
  import {
8
- escapeRegExp,
8
+ escapeRegExp as escapeRegExp2,
9
9
  resolveBunCli,
10
10
  resolveBunCliInvocation,
11
11
  resolveProjectRoot,
@@ -2645,8 +2645,8 @@ function ensureStatusLabel(bin, repo, spawnFn, label) {
2645
2645
  }
2646
2646
  }
2647
2647
  function selectedGitHubEnv() {
2648
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
2649
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
2648
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
2649
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
2650
2650
  }
2651
2651
  function ghSpawnOptions() {
2652
2652
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -5993,12 +5993,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
5993
5993
  "task-result.json",
5994
5994
  "validation-summary.json"
5995
5995
  ]);
5996
- function resolveHostRigBinDir(root) {
5997
- return resolve23(root, ".rig", "bin");
5998
- }
5999
5996
  function isRuntimeGatewayGitPath(candidate) {
6000
5997
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
6001
5998
  }
5999
+ function isRuntimeGatewayGhPath(candidate) {
6000
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
6001
+ }
6002
6002
  function resolveOptionalMonorepoRoot(projectRoot) {
6003
6003
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
6004
6004
  if (runtimeWorkspace && existsSync20(resolve23(runtimeWorkspace, ".git"))) {
@@ -6033,6 +6033,9 @@ function resolveGitBinary(projectRoot) {
6033
6033
  }
6034
6034
  return "git";
6035
6035
  }
6036
+ function escapeRegExp(value) {
6037
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6038
+ }
6036
6039
  function safeCurrentTaskId(projectRoot) {
6037
6040
  try {
6038
6041
  const taskId = currentTaskId(projectRoot);
@@ -6101,17 +6104,15 @@ function gitOpenPr(options) {
6101
6104
  const target = options.target || (taskId ? "monorepo" : "project");
6102
6105
  let repoRoot = options.projectRoot;
6103
6106
  let repoLabel = "project-rig";
6104
- let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
6107
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
6105
6108
  if (target === "monorepo") {
6106
6109
  repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
6107
6110
  repoLabel = "monorepo";
6108
- defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
6109
6111
  if (taskId) {
6110
6112
  gitSyncBranch(options.projectRoot, taskId, "monorepo");
6111
6113
  }
6112
6114
  } else if (taskId) {
6113
6115
  gitSyncBranch(options.projectRoot, taskId, "project");
6114
- defaultBase = inferProjectBase(options.projectRoot, defaultBase);
6115
6116
  }
6116
6117
  if (!existsSync20(resolve23(repoRoot, ".git"))) {
6117
6118
  throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
@@ -6120,9 +6121,9 @@ function gitOpenPr(options) {
6120
6121
  if (!branch || branch === "HEAD") {
6121
6122
  throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
6122
6123
  }
6123
- const base = options.base || defaultBase;
6124
6124
  const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
6125
6125
  const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
6126
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
6126
6127
  refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
6127
6128
  let reviewer = (options.reviewer || "").trim();
6128
6129
  let reviewerSource = reviewer ? "flag" : undefined;
@@ -6158,6 +6159,7 @@ function gitOpenPr(options) {
6158
6159
  "",
6159
6160
  "## Task",
6160
6161
  `- beads: ${taskId || "n/a"}`,
6162
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
6161
6163
  "",
6162
6164
  "## Review",
6163
6165
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -6249,6 +6251,29 @@ function gitOpenPr(options) {
6249
6251
  }
6250
6252
  return result;
6251
6253
  }
6254
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
6255
+ const lines = [];
6256
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
6257
+ if (runId) {
6258
+ lines.push(`- Run: ${runId}`);
6259
+ }
6260
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
6261
+ if (closeout) {
6262
+ lines.push(`- ${closeout}`);
6263
+ }
6264
+ return lines;
6265
+ }
6266
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
6267
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
6268
+ if (sourceIssueId) {
6269
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
6270
+ if (match) {
6271
+ const [, sourceRepo, issueNumber] = match;
6272
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
6273
+ }
6274
+ }
6275
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
6276
+ }
6252
6277
  function resolveTaskBranchRef(projectRoot, taskId) {
6253
6278
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
6254
6279
  }
@@ -6495,32 +6520,19 @@ function resolveGithubCliBinary(projectRoot) {
6495
6520
  if (explicit) {
6496
6521
  candidates.add(explicit);
6497
6522
  }
6523
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
6524
+ candidates.add(candidate);
6525
+ }
6498
6526
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
6499
6527
  for (const entry of explicitPathEntries) {
6500
6528
  candidates.add(resolve23(entry, "gh"));
6501
6529
  }
6502
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
6503
- if (hostProjectRoot) {
6504
- candidates.add(resolve23(resolveHostRigBinDir(hostProjectRoot), "gh"));
6505
- }
6506
- candidates.add(resolve23(resolveHostRigBinDir(projectRoot), "gh"));
6507
- const runtimeContext = loadRuntimeContextFromEnv();
6508
- if (runtimeContext?.binDir) {
6509
- candidates.add(resolve23(runtimeContext.binDir, "gh"));
6510
- }
6511
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
6512
- if (runtimeHome) {
6513
- candidates.add(resolve23(runtimeHome, "bin", "gh"));
6514
- }
6515
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
6516
- candidates.add(candidate);
6517
- }
6518
6530
  const bunResolved = Bun.which("gh");
6519
6531
  if (bunResolved) {
6520
6532
  candidates.add(bunResolved);
6521
6533
  }
6522
6534
  for (const candidate of candidates) {
6523
- if (candidate && existsSync20(candidate)) {
6535
+ if (candidate && existsSync20(candidate) && !isRuntimeGatewayGhPath(candidate)) {
6524
6536
  return candidate;
6525
6537
  }
6526
6538
  }
@@ -6669,6 +6681,32 @@ function withGhRepo(command, repoNameWithOwner) {
6669
6681
  }
6670
6682
  return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
6671
6683
  }
6684
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
6685
+ const remote = remoteName || "origin";
6686
+ const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
6687
+ if (symbolic.exitCode === 0) {
6688
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
6689
+ if (ref && ref !== "HEAD") {
6690
+ return ref;
6691
+ }
6692
+ }
6693
+ const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
6694
+ if (lsRemote.exitCode === 0) {
6695
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
6696
+ if (match?.[1]) {
6697
+ return match[1];
6698
+ }
6699
+ }
6700
+ const gh = resolveGithubCliBinary(projectRoot);
6701
+ if (gh && repoNameWithOwner) {
6702
+ const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
6703
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
6704
+ if (branch) {
6705
+ return branch;
6706
+ }
6707
+ }
6708
+ return fallback;
6709
+ }
6672
6710
  function inferProjectBase(projectRoot, fallback) {
6673
6711
  const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
6674
6712
  if (containing.exitCode !== 0) {
@@ -7013,6 +7051,10 @@ function runtimeGitEnv(projectRoot) {
7013
7051
  }
7014
7052
  env[key] = value;
7015
7053
  }
7054
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
7055
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
7056
+ env.GITHUB_TOKEN = rigGithubToken;
7057
+ }
7016
7058
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
7017
7059
  env.GITHUB_TOKEN = env.GH_TOKEN;
7018
7060
  }
@@ -7036,6 +7078,13 @@ function runtimeGitEnv(projectRoot) {
7036
7078
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
7037
7079
  env.GH_TOKEN = env.GITHUB_TOKEN;
7038
7080
  }
7081
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
7082
+ if (gitHubToken) {
7083
+ env.RIG_GITHUB_TOKEN = gitHubToken;
7084
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
7085
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
7086
+ applyGitHubCredentialHelperEnv(env);
7087
+ }
7039
7088
  if (runtimeKnownHosts && existsSync20(runtimeKnownHosts)) {
7040
7089
  const sshParts = [
7041
7090
  "ssh",
@@ -7052,6 +7101,14 @@ function runtimeGitEnv(projectRoot) {
7052
7101
  }
7053
7102
  return Object.keys(env).length > 0 ? env : undefined;
7054
7103
  }
7104
+ function applyGitHubCredentialHelperEnv(env) {
7105
+ env.GIT_TERMINAL_PROMPT = "0";
7106
+ env.GIT_CONFIG_COUNT = "2";
7107
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
7108
+ env.GIT_CONFIG_VALUE_0 = "";
7109
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
7110
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
7111
+ }
7055
7112
  function loadPersistedRuntimeSecrets(runtimeRoot) {
7056
7113
  if (!runtimeRoot) {
7057
7114
  return {};
@@ -7493,7 +7550,7 @@ async function recordVerifierFailure(projectRoot, taskId, paths) {
7493
7550
  let attempts = 1;
7494
7551
  if (existsSync21(failedApproachesPath)) {
7495
7552
  const content = readFileSync12(failedApproachesPath, "utf-8");
7496
- attempts = (content.match(new RegExp(`^## ${escapeRegExp(taskId)}\\b`, "gm")) || []).length + 1;
7553
+ attempts = (content.match(new RegExp(`^## ${escapeRegExp2(taskId)}\\b`, "gm")) || []).length + 1;
7497
7554
  } else {
7498
7555
  mkdirSync11(resolve24(failedApproachesPath, ".."), { recursive: true });
7499
7556
  writeFileSync11(failedApproachesPath, `# Failed Approaches
@@ -2484,8 +2484,8 @@ function githubStatusFor(issue) {
2484
2484
  return "open";
2485
2485
  }
2486
2486
  function selectedGitHubEnv() {
2487
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
2488
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
2487
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
2488
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
2489
2489
  }
2490
2490
  function ghSpawnOptions() {
2491
2491
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -1706,8 +1706,8 @@ function githubStatusFor(issue) {
1706
1706
  return "open";
1707
1707
  }
1708
1708
  function selectedGitHubEnv() {
1709
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1710
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1709
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1710
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1711
1711
  }
1712
1712
  function ghSpawnOptions() {
1713
1713
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -4509,6 +4509,10 @@ function runtimeGitEnv(projectRoot) {
4509
4509
  }
4510
4510
  env[key] = value;
4511
4511
  }
4512
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
4513
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
4514
+ env.GITHUB_TOKEN = rigGithubToken;
4515
+ }
4512
4516
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
4513
4517
  env.GITHUB_TOKEN = env.GH_TOKEN;
4514
4518
  }
@@ -4532,6 +4536,13 @@ function runtimeGitEnv(projectRoot) {
4532
4536
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
4533
4537
  env.GH_TOKEN = env.GITHUB_TOKEN;
4534
4538
  }
4539
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
4540
+ if (gitHubToken) {
4541
+ env.RIG_GITHUB_TOKEN = gitHubToken;
4542
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
4543
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
4544
+ applyGitHubCredentialHelperEnv(env);
4545
+ }
4535
4546
  if (runtimeKnownHosts && existsSync22(runtimeKnownHosts)) {
4536
4547
  const sshParts = [
4537
4548
  "ssh",
@@ -4548,6 +4559,14 @@ function runtimeGitEnv(projectRoot) {
4548
4559
  }
4549
4560
  return Object.keys(env).length > 0 ? env : undefined;
4550
4561
  }
4562
+ function applyGitHubCredentialHelperEnv(env) {
4563
+ env.GIT_TERMINAL_PROMPT = "0";
4564
+ env.GIT_CONFIG_COUNT = "2";
4565
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
4566
+ env.GIT_CONFIG_VALUE_0 = "";
4567
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
4568
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
4569
+ }
4551
4570
  function loadPersistedRuntimeSecrets(runtimeRoot) {
4552
4571
  if (!runtimeRoot) {
4553
4572
  return {};
@@ -7603,7 +7622,11 @@ async function ensureAgentRuntime(options) {
7603
7622
  mkdirSync21(runtime.binDir, { recursive: true });
7604
7623
  mkdirSync21(workspaceLayout.distDir, { recursive: true });
7605
7624
  prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
7606
- await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
7625
+ if (options.preserveTaskArtifacts) {
7626
+ console.log(`[rig-agent] Preserving runtime task artifacts for resume of ${options.taskId}.`);
7627
+ } else {
7628
+ await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
7629
+ }
7607
7630
  const ctx = {
7608
7631
  runtimeId: options.id,
7609
7632
  taskId: options.taskId,
@@ -1706,8 +1706,8 @@ function githubStatusFor(issue) {
1706
1706
  return "open";
1707
1707
  }
1708
1708
  function selectedGitHubEnv() {
1709
- const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
1710
- return { GH_TOKEN: token, GITHUB_TOKEN: token };
1709
+ const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() || process.env.RIG_GITHUB_TOKEN?.trim() || "";
1710
+ return { GH_TOKEN: token, GITHUB_TOKEN: token, RIG_GITHUB_TOKEN: token };
1711
1711
  }
1712
1712
  function ghSpawnOptions() {
1713
1713
  return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
@@ -4509,6 +4509,10 @@ function runtimeGitEnv(projectRoot) {
4509
4509
  }
4510
4510
  env[key] = value;
4511
4511
  }
4512
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
4513
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
4514
+ env.GITHUB_TOKEN = rigGithubToken;
4515
+ }
4512
4516
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
4513
4517
  env.GITHUB_TOKEN = env.GH_TOKEN;
4514
4518
  }
@@ -4532,6 +4536,13 @@ function runtimeGitEnv(projectRoot) {
4532
4536
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
4533
4537
  env.GH_TOKEN = env.GITHUB_TOKEN;
4534
4538
  }
4539
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
4540
+ if (gitHubToken) {
4541
+ env.RIG_GITHUB_TOKEN = gitHubToken;
4542
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
4543
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
4544
+ applyGitHubCredentialHelperEnv(env);
4545
+ }
4535
4546
  if (runtimeKnownHosts && existsSync22(runtimeKnownHosts)) {
4536
4547
  const sshParts = [
4537
4548
  "ssh",
@@ -4548,6 +4559,14 @@ function runtimeGitEnv(projectRoot) {
4548
4559
  }
4549
4560
  return Object.keys(env).length > 0 ? env : undefined;
4550
4561
  }
4562
+ function applyGitHubCredentialHelperEnv(env) {
4563
+ env.GIT_TERMINAL_PROMPT = "0";
4564
+ env.GIT_CONFIG_COUNT = "2";
4565
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
4566
+ env.GIT_CONFIG_VALUE_0 = "";
4567
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
4568
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
4569
+ }
4551
4570
  function loadPersistedRuntimeSecrets(runtimeRoot) {
4552
4571
  if (!runtimeRoot) {
4553
4572
  return {};
@@ -7603,7 +7622,11 @@ async function ensureAgentRuntime(options) {
7603
7622
  mkdirSync21(runtime.binDir, { recursive: true });
7604
7623
  mkdirSync21(workspaceLayout.distDir, { recursive: true });
7605
7624
  prepareRuntimeWorkspace(options.projectRoot, workspaceDir);
7606
- await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
7625
+ if (options.preserveTaskArtifacts) {
7626
+ console.log(`[rig-agent] Preserving runtime task artifacts for resume of ${options.taskId}.`);
7627
+ } else {
7628
+ await resetEphemeralTaskArtifacts(workspaceDir, options.taskId);
7629
+ }
7607
7630
  const ctx = {
7608
7631
  runtimeId: options.id,
7609
7632
  taskId: options.taskId,
@@ -1575,12 +1575,12 @@ var TASK_ARTIFACT_STAGE_FALLBACK = new Set([
1575
1575
  "task-result.json",
1576
1576
  "validation-summary.json"
1577
1577
  ]);
1578
- function resolveHostRigBinDir(root) {
1579
- return resolve11(root, ".rig", "bin");
1580
- }
1581
1578
  function isRuntimeGatewayGitPath(candidate) {
1582
1579
  return /\/\.rig\/bin\/git$/.test(candidate.replace(/\\/g, "/"));
1583
1580
  }
1581
+ function isRuntimeGatewayGhPath(candidate) {
1582
+ return /\/\.rig\/bin\/gh$/.test(candidate.replace(/\\/g, "/"));
1583
+ }
1584
1584
  function resolveOptionalMonorepoRoot(projectRoot) {
1585
1585
  const runtimeWorkspace = process.env.RIG_TASK_WORKSPACE?.trim();
1586
1586
  if (runtimeWorkspace && existsSync9(resolve11(runtimeWorkspace, ".git"))) {
@@ -1615,6 +1615,9 @@ function resolveGitBinary(projectRoot) {
1615
1615
  }
1616
1616
  return "git";
1617
1617
  }
1618
+ function escapeRegExp(value) {
1619
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1620
+ }
1618
1621
  function safeCurrentTaskId(projectRoot) {
1619
1622
  try {
1620
1623
  const taskId = currentTaskId(projectRoot);
@@ -1773,17 +1776,15 @@ function gitOpenPr(options) {
1773
1776
  const target = options.target || (taskId ? "monorepo" : "project");
1774
1777
  let repoRoot = options.projectRoot;
1775
1778
  let repoLabel = "project-rig";
1776
- let defaultBase = process.env.RIG_PR_BASE_PROJECT || "main";
1779
+ const envBase = target === "monorepo" ? process.env.RIG_PR_BASE_MONOREPO?.trim() || "" : process.env.RIG_PR_BASE_PROJECT?.trim() || "";
1777
1780
  if (target === "monorepo") {
1778
1781
  repoRoot = resolveOptionalMonorepoRoot(options.projectRoot) || resolveMonorepoRoot2(options.projectRoot);
1779
1782
  repoLabel = "monorepo";
1780
- defaultBase = process.env.RIG_PR_BASE_MONOREPO || "main";
1781
1783
  if (taskId) {
1782
1784
  gitSyncBranch(options.projectRoot, taskId, "monorepo");
1783
1785
  }
1784
1786
  } else if (taskId) {
1785
1787
  gitSyncBranch(options.projectRoot, taskId, "project");
1786
- defaultBase = inferProjectBase(options.projectRoot, defaultBase);
1787
1788
  }
1788
1789
  if (!existsSync9(resolve11(repoRoot, ".git"))) {
1789
1790
  throw new Error(`Repository not available for open-pr target ${target}: ${repoRoot}`);
@@ -1792,9 +1793,9 @@ function gitOpenPr(options) {
1792
1793
  if (!branch || branch === "HEAD") {
1793
1794
  throw new Error(`Cannot open PR from detached HEAD in ${repoLabel}. Checkout a branch first.`);
1794
1795
  }
1795
- const base = options.base || defaultBase;
1796
1796
  const repoNameWithOwner = resolveRepoNameWithOwner(options.projectRoot, repoRoot);
1797
1797
  const networkRemote = resolveNetworkRemoteName(options.projectRoot, repoRoot, repoNameWithOwner);
1798
+ const base = options.base || envBase || inferRepositoryDefaultBase(options.projectRoot, repoRoot, repoNameWithOwner, networkRemote, target === "project" ? inferProjectBase(options.projectRoot, "main") : "main");
1798
1799
  refreshRemoteBaseRef(options.projectRoot, repoRoot, base);
1799
1800
  let reviewer = (options.reviewer || "").trim();
1800
1801
  let reviewerSource = reviewer ? "flag" : undefined;
@@ -1830,6 +1831,7 @@ function gitOpenPr(options) {
1830
1831
  "",
1831
1832
  "## Task",
1832
1833
  `- beads: ${taskId || "n/a"}`,
1834
+ ...defaultPrRunLines(taskId, repoNameWithOwner),
1833
1835
  "",
1834
1836
  "## Review",
1835
1837
  "- Completion verification will run validation, verifier review, and PR policy checks.",
@@ -1921,6 +1923,29 @@ function gitOpenPr(options) {
1921
1923
  }
1922
1924
  return result;
1923
1925
  }
1926
+ function defaultPrRunLines(taskId, repoNameWithOwner) {
1927
+ const lines = [];
1928
+ const runId = process.env.RIG_SERVER_RUN_ID?.trim();
1929
+ if (runId) {
1930
+ lines.push(`- Run: ${runId}`);
1931
+ }
1932
+ const closeout = defaultPrCloseoutLine(taskId, repoNameWithOwner);
1933
+ if (closeout) {
1934
+ lines.push(`- ${closeout}`);
1935
+ }
1936
+ return lines;
1937
+ }
1938
+ function defaultPrCloseoutLine(taskId, repoNameWithOwner) {
1939
+ const sourceIssueId = loadRuntimeContextFromEnv()?.sourceTask?.sourceIssueId;
1940
+ if (sourceIssueId) {
1941
+ const match = sourceIssueId.match(/^([^#]+)#(\d+)$/);
1942
+ if (match) {
1943
+ const [, sourceRepo, issueNumber] = match;
1944
+ return sourceRepo.toLowerCase() === repoNameWithOwner.toLowerCase() ? `Closes #${issueNumber}` : `Closes ${sourceRepo}#${issueNumber}`;
1945
+ }
1946
+ }
1947
+ return /^\d+$/.test(taskId) ? `Closes #${taskId}` : "";
1948
+ }
1924
1949
  function resolveTaskBranchRef(projectRoot, taskId) {
1925
1950
  return `rig/${resolveTaskBranchId(projectRoot, taskId)}`;
1926
1951
  }
@@ -2171,32 +2196,19 @@ function resolveGithubCliBinary(projectRoot) {
2171
2196
  if (explicit) {
2172
2197
  candidates.add(explicit);
2173
2198
  }
2199
+ for (const candidate of ["/usr/bin/gh", "/opt/homebrew/bin/gh", "/usr/local/bin/gh"]) {
2200
+ candidates.add(candidate);
2201
+ }
2174
2202
  const explicitPathEntries = (process.env.PATH || "").split(":").map((entry) => entry.trim()).filter(Boolean);
2175
2203
  for (const entry of explicitPathEntries) {
2176
2204
  candidates.add(resolve11(entry, "gh"));
2177
2205
  }
2178
- const hostProjectRoot = process.env.RIG_HOST_PROJECT_ROOT?.trim();
2179
- if (hostProjectRoot) {
2180
- candidates.add(resolve11(resolveHostRigBinDir(hostProjectRoot), "gh"));
2181
- }
2182
- candidates.add(resolve11(resolveHostRigBinDir(projectRoot), "gh"));
2183
- const runtimeContext = loadRuntimeContextFromEnv();
2184
- if (runtimeContext?.binDir) {
2185
- candidates.add(resolve11(runtimeContext.binDir, "gh"));
2186
- }
2187
- const runtimeHome = process.env.RIG_RUNTIME_HOME?.trim();
2188
- if (runtimeHome) {
2189
- candidates.add(resolve11(runtimeHome, "bin", "gh"));
2190
- }
2191
- for (const candidate of ["/opt/homebrew/bin/gh", "/usr/local/bin/gh", "/usr/bin/gh"]) {
2192
- candidates.add(candidate);
2193
- }
2194
2206
  const bunResolved = Bun.which("gh");
2195
2207
  if (bunResolved) {
2196
2208
  candidates.add(bunResolved);
2197
2209
  }
2198
2210
  for (const candidate of candidates) {
2199
- if (candidate && existsSync9(candidate)) {
2211
+ if (candidate && existsSync9(candidate) && !isRuntimeGatewayGhPath(candidate)) {
2200
2212
  return candidate;
2201
2213
  }
2202
2214
  }
@@ -2345,6 +2357,32 @@ function withGhRepo(command, repoNameWithOwner) {
2345
2357
  }
2346
2358
  return [command[0], command[1], command[2], ...ghRepoArgs(repoNameWithOwner), ...command.slice(3)];
2347
2359
  }
2360
+ function inferRepositoryDefaultBase(projectRoot, repoRoot, repoNameWithOwner, remoteName, fallback) {
2361
+ const remote = remoteName || "origin";
2362
+ const symbolic = runCapture2(gitCmd(projectRoot, repoRoot, "symbolic-ref", "--short", `refs/remotes/${remote}/HEAD`), projectRoot);
2363
+ if (symbolic.exitCode === 0) {
2364
+ const ref = symbolic.stdout.trim().replace(new RegExp(`^${escapeRegExp(remote)}/`), "");
2365
+ if (ref && ref !== "HEAD") {
2366
+ return ref;
2367
+ }
2368
+ }
2369
+ const lsRemote = runCapture2(gitCmd(projectRoot, repoRoot, "ls-remote", "--symref", remote, "HEAD"), projectRoot);
2370
+ if (lsRemote.exitCode === 0) {
2371
+ const match = lsRemote.stdout.match(/^ref:\s+refs\/heads\/([^\t\r\n]+)\s+HEAD/m);
2372
+ if (match?.[1]) {
2373
+ return match[1];
2374
+ }
2375
+ }
2376
+ const gh = resolveGithubCliBinary(projectRoot);
2377
+ if (gh && repoNameWithOwner) {
2378
+ const api = runCapture2(withGhRepo([gh, "repo", "view", "--json", "defaultBranchRef", "--jq", ".defaultBranchRef.name"], repoNameWithOwner), repoRoot);
2379
+ const branch = api.exitCode === 0 ? api.stdout.trim() : "";
2380
+ if (branch) {
2381
+ return branch;
2382
+ }
2383
+ }
2384
+ return fallback;
2385
+ }
2348
2386
  function inferProjectBase(projectRoot, fallback) {
2349
2387
  const containing = runCapture2(gitCmd(projectRoot, projectRoot, "branch", "-r", "--contains", "HEAD"), projectRoot);
2350
2388
  if (containing.exitCode !== 0) {
@@ -2726,6 +2764,10 @@ function runtimeGitEnv(projectRoot) {
2726
2764
  }
2727
2765
  env[key] = value;
2728
2766
  }
2767
+ const rigGithubToken = process.env.RIG_GITHUB_TOKEN?.trim() || "";
2768
+ if (rigGithubToken && !env.GITHUB_TOKEN && !env.GH_TOKEN) {
2769
+ env.GITHUB_TOKEN = rigGithubToken;
2770
+ }
2729
2771
  if (!env.GITHUB_TOKEN && env.GH_TOKEN) {
2730
2772
  env.GITHUB_TOKEN = env.GH_TOKEN;
2731
2773
  }
@@ -2749,6 +2791,13 @@ function runtimeGitEnv(projectRoot) {
2749
2791
  if (!env.GH_TOKEN && env.GITHUB_TOKEN) {
2750
2792
  env.GH_TOKEN = env.GITHUB_TOKEN;
2751
2793
  }
2794
+ const gitHubToken = env.GITHUB_TOKEN || env.GH_TOKEN || env.RIG_GITHUB_TOKEN || rigGithubToken;
2795
+ if (gitHubToken) {
2796
+ env.RIG_GITHUB_TOKEN = gitHubToken;
2797
+ env.GITHUB_TOKEN = env.GITHUB_TOKEN || gitHubToken;
2798
+ env.GH_TOKEN = env.GH_TOKEN || gitHubToken;
2799
+ applyGitHubCredentialHelperEnv(env);
2800
+ }
2752
2801
  if (runtimeKnownHosts && existsSync9(runtimeKnownHosts)) {
2753
2802
  const sshParts = [
2754
2803
  "ssh",
@@ -2765,6 +2814,14 @@ function runtimeGitEnv(projectRoot) {
2765
2814
  }
2766
2815
  return Object.keys(env).length > 0 ? env : undefined;
2767
2816
  }
2817
+ function applyGitHubCredentialHelperEnv(env) {
2818
+ env.GIT_TERMINAL_PROMPT = "0";
2819
+ env.GIT_CONFIG_COUNT = "2";
2820
+ env.GIT_CONFIG_KEY_0 = "credential.helper";
2821
+ env.GIT_CONFIG_VALUE_0 = "";
2822
+ env.GIT_CONFIG_KEY_1 = "credential.helper";
2823
+ env.GIT_CONFIG_VALUE_1 = '!f() { test "$1" = get || exit 0; token="${GITHUB_TOKEN:-${GH_TOKEN:-${RIG_GITHUB_TOKEN:-}}}"; test -n "$token" || exit 0; echo username=x-access-token; echo password="$token"; }; f';
2824
+ }
2768
2825
  function loadPersistedRuntimeSecrets(runtimeRoot) {
2769
2826
  if (!runtimeRoot) {
2770
2827
  return {};